1 | /* |
2 | * Copyright (C) 2016-2019 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "JSWebAssemblyInstance.h" |
28 | |
29 | #if ENABLE(WEBASSEMBLY) |
30 | |
31 | #include "AbstractModuleRecord.h" |
32 | #include "JSCInlines.h" |
33 | #include "JSModuleEnvironment.h" |
34 | #include "JSModuleNamespaceObject.h" |
35 | #include "JSWebAssemblyHelpers.h" |
36 | #include "JSWebAssemblyLinkError.h" |
37 | #include "JSWebAssemblyMemory.h" |
38 | #include "JSWebAssemblyModule.h" |
39 | #include "WebAssemblyModuleRecord.h" |
40 | #include "WebAssemblyToJSCallee.h" |
41 | #include <wtf/StdLibExtras.h> |
42 | |
43 | namespace JSC { |
44 | |
45 | const ClassInfo JSWebAssemblyInstance::s_info = { "WebAssembly.Instance" , &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWebAssemblyInstance) }; |
46 | |
47 | Structure* JSWebAssemblyInstance::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) |
48 | { |
49 | return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); |
50 | } |
51 | |
52 | JSWebAssemblyInstance::JSWebAssemblyInstance(VM& vm, Structure* structure, Ref<Wasm::Instance>&& instance) |
53 | : Base(vm, structure) |
54 | , m_instance(WTFMove(instance)) |
55 | , m_vm(&vm) |
56 | , m_tables(m_instance->module().moduleInformation().tableCount()) |
57 | { |
58 | for (unsigned i = 0; i < this->instance().numImportFunctions(); ++i) |
59 | new (this->instance().importFunction<WriteBarrier<JSObject>>(i)) WriteBarrier<JSObject>(); |
60 | } |
61 | |
62 | void JSWebAssemblyInstance::finishCreation(VM& vm, JSWebAssemblyModule* module, JSModuleNamespaceObject* moduleNamespaceObject) |
63 | { |
64 | Base::finishCreation(vm); |
65 | ASSERT(inherits(vm, info())); |
66 | |
67 | m_module.set(vm, this, module); |
68 | m_moduleNamespaceObject.set(vm, this, moduleNamespaceObject); |
69 | m_callee.set(vm, this, module->callee()); |
70 | |
71 | vm.heap.reportExtraMemoryAllocated(m_instance->extraMemoryAllocated()); |
72 | } |
73 | |
74 | void JSWebAssemblyInstance::destroy(JSCell* cell) |
75 | { |
76 | static_cast<JSWebAssemblyInstance*>(cell)->JSWebAssemblyInstance::~JSWebAssemblyInstance(); |
77 | } |
78 | |
79 | void JSWebAssemblyInstance::visitChildren(JSCell* cell, SlotVisitor& visitor) |
80 | { |
81 | auto* thisObject = jsCast<JSWebAssemblyInstance*>(cell); |
82 | ASSERT_GC_OBJECT_INHERITS(thisObject, info()); |
83 | |
84 | Base::visitChildren(thisObject, visitor); |
85 | visitor.append(thisObject->m_module); |
86 | visitor.append(thisObject->m_codeBlock); |
87 | visitor.append(thisObject->m_moduleNamespaceObject); |
88 | visitor.append(thisObject->m_memory); |
89 | for (unsigned i = 0; i < thisObject->instance().module().moduleInformation().tableCount(); ++i) |
90 | visitor.append(thisObject->m_tables[i]); |
91 | visitor.append(thisObject->m_callee); |
92 | visitor.reportExtraMemoryVisited(thisObject->m_instance->extraMemoryAllocated()); |
93 | for (unsigned i = 0; i < thisObject->instance().numImportFunctions(); ++i) |
94 | visitor.append(*thisObject->instance().importFunction<WriteBarrier<JSObject>>(i)); // This also keeps the functions' JSWebAssemblyInstance alive. |
95 | |
96 | for (size_t i : thisObject->instance().globalsToMark()) |
97 | visitor.appendUnbarriered(JSValue::decode(thisObject->instance().loadI64Global(i))); |
98 | |
99 | auto locker = holdLock(cell->cellLock()); |
100 | for (auto& wrapper : thisObject->instance().functionWrappers()) |
101 | visitor.appendUnbarriered(wrapper.get()); |
102 | } |
103 | |
104 | void JSWebAssemblyInstance::finalizeCreation(VM& vm, ExecState* exec, Ref<Wasm::CodeBlock>&& wasmCodeBlock, JSObject* importObject, Wasm::CreationMode creationMode) |
105 | { |
106 | m_instance->finalizeCreation(this, wasmCodeBlock.copyRef()); |
107 | |
108 | auto scope = DECLARE_THROW_SCOPE(vm); |
109 | |
110 | if (!wasmCodeBlock->runnable()) { |
111 | throwException(exec, scope, JSWebAssemblyLinkError::create(exec, vm, globalObject(vm)->webAssemblyLinkErrorStructure(), wasmCodeBlock->errorMessage())); |
112 | return; |
113 | } |
114 | |
115 | RELEASE_ASSERT(wasmCodeBlock->isSafeToRun(memoryMode())); |
116 | JSWebAssemblyCodeBlock* jsCodeBlock = m_module->codeBlock(memoryMode()); |
117 | if (jsCodeBlock) { |
118 | // A CodeBlock might have already been compiled. If so, it means |
119 | // that the CodeBlock we are trying to compile must be the same |
120 | // because we will never compile a CodeBlock again once it's |
121 | // runnable. |
122 | ASSERT(&jsCodeBlock->codeBlock() == wasmCodeBlock.ptr()); |
123 | m_codeBlock.set(vm, this, jsCodeBlock); |
124 | } else { |
125 | jsCodeBlock = JSWebAssemblyCodeBlock::create(vm, WTFMove(wasmCodeBlock), module()->module().moduleInformation()); |
126 | if (UNLIKELY(!jsCodeBlock->runnable())) { |
127 | throwException(exec, scope, JSWebAssemblyLinkError::create(exec, vm, globalObject(vm)->webAssemblyLinkErrorStructure(), jsCodeBlock->errorMessage())); |
128 | return; |
129 | } |
130 | m_codeBlock.set(vm, this, jsCodeBlock); |
131 | m_module->setCodeBlock(vm, memoryMode(), jsCodeBlock); |
132 | } |
133 | |
134 | for (unsigned importFunctionNum = 0; importFunctionNum < instance().numImportFunctions(); ++importFunctionNum) { |
135 | auto* info = instance().importFunctionInfo(importFunctionNum); |
136 | info->wasmToEmbedderStub = m_codeBlock->wasmToEmbedderStub(importFunctionNum); |
137 | } |
138 | |
139 | auto* moduleRecord = jsCast<WebAssemblyModuleRecord*>(m_moduleNamespaceObject->moduleRecord()); |
140 | moduleRecord->prepareLink(vm, this); |
141 | |
142 | if (creationMode == Wasm::CreationMode::FromJS) { |
143 | moduleRecord->link(exec, jsNull(), importObject, creationMode); |
144 | RETURN_IF_EXCEPTION(scope, void()); |
145 | |
146 | JSValue startResult = moduleRecord->evaluate(exec); |
147 | UNUSED_PARAM(startResult); |
148 | RETURN_IF_EXCEPTION(scope, void()); |
149 | } |
150 | } |
151 | |
152 | Identifier JSWebAssemblyInstance::createPrivateModuleKey() |
153 | { |
154 | return Identifier::fromUid(PrivateName(PrivateName::Description, "WebAssemblyInstance" )); |
155 | } |
156 | |
157 | JSWebAssemblyInstance* JSWebAssemblyInstance::create(VM& vm, ExecState* exec, const Identifier& moduleKey, JSWebAssemblyModule* jsModule, JSObject* importObject, Structure* instanceStructure, Ref<Wasm::Module>&& module, Wasm::CreationMode creationMode) |
158 | { |
159 | auto throwScope = DECLARE_THROW_SCOPE(vm); |
160 | auto* globalObject = exec->lexicalGlobalObject(); |
161 | |
162 | const Wasm::ModuleInformation& moduleInformation = jsModule->moduleInformation(); |
163 | |
164 | auto exception = [&] (JSObject* error) { |
165 | throwException(exec, throwScope, error); |
166 | return nullptr; |
167 | }; |
168 | |
169 | if (!globalObject->webAssemblyEnabled()) |
170 | return exception(createEvalError(exec, globalObject->webAssemblyDisabledErrorMessage())); |
171 | |
172 | auto importFailMessage = [&] (const Wasm::Import& import, const char* before, const char* after) { |
173 | return makeString(before, " " , String::fromUTF8(import.module), ":" , String::fromUTF8(import.field), " " , after); |
174 | }; |
175 | |
176 | WebAssemblyModuleRecord* moduleRecord = WebAssemblyModuleRecord::create(exec, vm, globalObject->webAssemblyModuleRecordStructure(), moduleKey, moduleInformation); |
177 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
178 | |
179 | JSModuleNamespaceObject* moduleNamespace = moduleRecord->getModuleNamespace(exec); |
180 | |
181 | auto storeTopCallFrame = [&vm] (void* topCallFrame) { |
182 | vm.topCallFrame = bitwise_cast<ExecState*>(topCallFrame); |
183 | }; |
184 | |
185 | // FIXME: These objects could be pretty big we should try to throw OOM here. |
186 | auto* jsInstance = new (NotNull, allocateCell<JSWebAssemblyInstance>(vm.heap)) JSWebAssemblyInstance(vm, instanceStructure, |
187 | Wasm::Instance::create(&vm.wasmContext, WTFMove(module), &vm.topEntryFrame, vm.addressOfSoftStackLimit(), WTFMove(storeTopCallFrame))); |
188 | jsInstance->finishCreation(vm, jsModule, moduleNamespace); |
189 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
190 | |
191 | // Let funcs, memories and tables be initially-empty lists of callable JavaScript objects, WebAssembly.Memory objects and WebAssembly.Table objects, respectively. |
192 | // Let imports be an initially-empty list of external values. |
193 | bool hasMemoryImport = false; |
194 | |
195 | if (creationMode == Wasm::CreationMode::FromJS) { |
196 | // If the list of module.imports is not empty and Type(importObject) is not Object, a TypeError is thrown. |
197 | if (moduleInformation.imports.size() && !importObject) |
198 | return exception(createTypeError(exec, "can't make WebAssembly.Instance because there is no imports Object and the WebAssembly.Module requires imports"_s )); |
199 | } |
200 | |
201 | // For each import i in module.imports: |
202 | for (auto& import : moduleInformation.imports) { |
203 | Identifier moduleName = Identifier::fromString(&vm, String::fromUTF8(import.module)); |
204 | Identifier fieldName = Identifier::fromString(&vm, String::fromUTF8(import.field)); |
205 | moduleRecord->appendRequestedModule(moduleName); |
206 | moduleRecord->addImportEntry(WebAssemblyModuleRecord::ImportEntry { |
207 | WebAssemblyModuleRecord::ImportEntryType::Single, |
208 | moduleName, |
209 | fieldName, |
210 | Identifier::fromUid(PrivateName(PrivateName::Description, "WebAssemblyImportName" )), |
211 | }); |
212 | |
213 | // Skip Wasm::ExternalKind::Function validation here. It will be done in WebAssemblyModuleRecord::link. |
214 | // Eventually we will move all the linking code here to WebAssemblyModuleRecord::link. |
215 | switch (import.kind) { |
216 | case Wasm::ExternalKind::Function: |
217 | case Wasm::ExternalKind::Global: |
218 | case Wasm::ExternalKind::Table: |
219 | continue; |
220 | case Wasm::ExternalKind::Memory: |
221 | break; |
222 | } |
223 | |
224 | JSValue value; |
225 | if (creationMode == Wasm::CreationMode::FromJS) { |
226 | // 1. Let o be the resultant value of performing Get(importObject, i.module_name). |
227 | JSValue importModuleValue = importObject->get(exec, moduleName); |
228 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
229 | // 2. If Type(o) is not Object, throw a TypeError. |
230 | if (!importModuleValue.isObject()) |
231 | return exception(createTypeError(exec, importFailMessage(import, "import" , "must be an object" ), defaultSourceAppender, runtimeTypeForValue(vm, importModuleValue))); |
232 | |
233 | // 3. Let v be the value of performing Get(o, i.item_name) |
234 | JSObject* object = jsCast<JSObject*>(importModuleValue); |
235 | value = object->get(exec, fieldName); |
236 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
237 | } |
238 | if (!value) |
239 | value = jsUndefined(); |
240 | |
241 | switch (import.kind) { |
242 | case Wasm::ExternalKind::Function: |
243 | case Wasm::ExternalKind::Global: |
244 | case Wasm::ExternalKind::Table: |
245 | break; |
246 | |
247 | case Wasm::ExternalKind::Memory: { |
248 | // 6. If i is a memory import: |
249 | RELEASE_ASSERT(!hasMemoryImport); // This should be guaranteed by a validation failure. |
250 | RELEASE_ASSERT(moduleInformation.memory); |
251 | hasMemoryImport = true; |
252 | JSWebAssemblyMemory* memory = jsDynamicCast<JSWebAssemblyMemory*>(vm, value); |
253 | // i. If v is not a WebAssembly.Memory object, throw a WebAssembly.LinkError. |
254 | if (!memory) |
255 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "is not an instance of WebAssembly.Memory" ))); |
256 | |
257 | Wasm::PageCount declaredInitial = moduleInformation.memory.initial(); |
258 | Wasm::PageCount importedInitial = memory->memory().initial(); |
259 | if (importedInitial < declaredInitial) |
260 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "provided an 'initial' that is smaller than the module's declared 'initial' import memory size" ))); |
261 | |
262 | if (Wasm::PageCount declaredMaximum = moduleInformation.memory.maximum()) { |
263 | Wasm::PageCount importedMaximum = memory->memory().maximum(); |
264 | if (!importedMaximum) |
265 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "did not have a 'maximum' but the module requires that it does" ))); |
266 | |
267 | if (importedMaximum > declaredMaximum) |
268 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "provided a 'maximum' that is larger than the module's declared 'maximum' import memory size" ))); |
269 | } |
270 | |
271 | // ii. Append v to memories. |
272 | // iii. Append v.[[Memory]] to imports. |
273 | jsInstance->setMemory(vm, memory); |
274 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
275 | break; |
276 | } |
277 | } |
278 | } |
279 | ASSERT(moduleRecord->importEntries().size() == moduleInformation.imports.size()); |
280 | |
281 | { |
282 | if (!!moduleInformation.memory && moduleInformation.memory.isImport()) { |
283 | // We should either have a Memory import or we should have thrown an exception. |
284 | RELEASE_ASSERT(hasMemoryImport); |
285 | } |
286 | |
287 | if (moduleInformation.memory && !hasMemoryImport) { |
288 | // We create a memory when it's a memory definition. |
289 | RELEASE_ASSERT(!moduleInformation.memory.isImport()); |
290 | |
291 | auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, globalObject->webAssemblyMemoryStructure()); |
292 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
293 | |
294 | RefPtr<Wasm::Memory> memory = Wasm::Memory::tryCreate(moduleInformation.memory.initial(), moduleInformation.memory.maximum(), |
295 | [&vm] (Wasm::Memory::NotifyPressure) { vm.heap.collectAsync(CollectionScope::Full); }, |
296 | [&vm] (Wasm::Memory::SyncTryToReclaim) { vm.heap.collectSync(CollectionScope::Full); }, |
297 | [&vm, jsMemory] (Wasm::Memory::GrowSuccess, Wasm::PageCount oldPageCount, Wasm::PageCount newPageCount) { jsMemory->growSuccessCallback(vm, oldPageCount, newPageCount); }); |
298 | if (!memory) |
299 | return exception(createOutOfMemoryError(exec)); |
300 | |
301 | jsMemory->adopt(memory.releaseNonNull()); |
302 | jsInstance->setMemory(vm, jsMemory); |
303 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
304 | } |
305 | } |
306 | |
307 | if (!jsInstance->memory()) { |
308 | // Make sure we have a dummy memory, so that wasm -> wasm thunks avoid checking for a nullptr Memory when trying to set pinned registers. |
309 | auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, globalObject->webAssemblyMemoryStructure()); |
310 | jsMemory->adopt(Wasm::Memory::create()); |
311 | jsInstance->setMemory(vm, jsMemory); |
312 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
313 | } |
314 | |
315 | return jsInstance; |
316 | } |
317 | |
318 | } // namespace JSC |
319 | |
320 | #endif // ENABLE(WEBASSEMBLY) |
321 | |