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 | { |
57 | for (unsigned i = 0; i < this->instance().numImportFunctions(); ++i) |
58 | new (this->instance().importFunction<WriteBarrier<JSObject>>(i)) WriteBarrier<JSObject>(); |
59 | } |
60 | |
61 | void JSWebAssemblyInstance::finishCreation(VM& vm, JSWebAssemblyModule* module, JSModuleNamespaceObject* moduleNamespaceObject) |
62 | { |
63 | Base::finishCreation(vm); |
64 | ASSERT(inherits(vm, info())); |
65 | |
66 | m_module.set(vm, this, module); |
67 | m_moduleNamespaceObject.set(vm, this, moduleNamespaceObject); |
68 | m_callee.set(vm, this, module->callee()); |
69 | |
70 | vm.heap.reportExtraMemoryAllocated(m_instance->extraMemoryAllocated()); |
71 | } |
72 | |
73 | void JSWebAssemblyInstance::destroy(JSCell* cell) |
74 | { |
75 | static_cast<JSWebAssemblyInstance*>(cell)->JSWebAssemblyInstance::~JSWebAssemblyInstance(); |
76 | } |
77 | |
78 | void JSWebAssemblyInstance::visitChildren(JSCell* cell, SlotVisitor& visitor) |
79 | { |
80 | auto* thisObject = jsCast<JSWebAssemblyInstance*>(cell); |
81 | ASSERT_GC_OBJECT_INHERITS(thisObject, info()); |
82 | |
83 | Base::visitChildren(thisObject, visitor); |
84 | visitor.append(thisObject->m_module); |
85 | visitor.append(thisObject->m_codeBlock); |
86 | visitor.append(thisObject->m_moduleNamespaceObject); |
87 | visitor.append(thisObject->m_memory); |
88 | visitor.append(thisObject->m_table); |
89 | visitor.append(thisObject->m_callee); |
90 | visitor.reportExtraMemoryVisited(thisObject->m_instance->extraMemoryAllocated()); |
91 | for (unsigned i = 0; i < thisObject->instance().numImportFunctions(); ++i) |
92 | visitor.append(*thisObject->instance().importFunction<WriteBarrier<JSObject>>(i)); // This also keeps the functions' JSWebAssemblyInstance alive. |
93 | |
94 | for (size_t i : thisObject->instance().globalsToMark()) { |
95 | // FIXME: We need to box wasm Funcrefs once they are supported here. |
96 | // <https://bugs.webkit.org/show_bug.cgi?id=198157> |
97 | visitor.appendUnbarriered(JSValue::decode(thisObject->instance().loadI64Global(i))); |
98 | } |
99 | } |
100 | |
101 | void JSWebAssemblyInstance::finalizeCreation(VM& vm, ExecState* exec, Ref<Wasm::CodeBlock>&& wasmCodeBlock, JSObject* importObject, Wasm::CreationMode creationMode) |
102 | { |
103 | m_instance->finalizeCreation(this, wasmCodeBlock.copyRef()); |
104 | |
105 | auto scope = DECLARE_THROW_SCOPE(vm); |
106 | |
107 | if (!wasmCodeBlock->runnable()) { |
108 | throwException(exec, scope, JSWebAssemblyLinkError::create(exec, vm, globalObject(vm)->webAssemblyLinkErrorStructure(), wasmCodeBlock->errorMessage())); |
109 | return; |
110 | } |
111 | |
112 | RELEASE_ASSERT(wasmCodeBlock->isSafeToRun(memoryMode())); |
113 | JSWebAssemblyCodeBlock* jsCodeBlock = m_module->codeBlock(memoryMode()); |
114 | if (jsCodeBlock) { |
115 | // A CodeBlock might have already been compiled. If so, it means |
116 | // that the CodeBlock we are trying to compile must be the same |
117 | // because we will never compile a CodeBlock again once it's |
118 | // runnable. |
119 | ASSERT(&jsCodeBlock->codeBlock() == wasmCodeBlock.ptr()); |
120 | m_codeBlock.set(vm, this, jsCodeBlock); |
121 | } else { |
122 | jsCodeBlock = JSWebAssemblyCodeBlock::create(vm, WTFMove(wasmCodeBlock), module()->module().moduleInformation()); |
123 | if (UNLIKELY(!jsCodeBlock->runnable())) { |
124 | throwException(exec, scope, JSWebAssemblyLinkError::create(exec, vm, globalObject(vm)->webAssemblyLinkErrorStructure(), jsCodeBlock->errorMessage())); |
125 | return; |
126 | } |
127 | m_codeBlock.set(vm, this, jsCodeBlock); |
128 | m_module->setCodeBlock(vm, memoryMode(), jsCodeBlock); |
129 | } |
130 | |
131 | for (unsigned importFunctionNum = 0; importFunctionNum < instance().numImportFunctions(); ++importFunctionNum) { |
132 | auto* info = instance().importFunctionInfo(importFunctionNum); |
133 | info->wasmToEmbedderStub = m_codeBlock->wasmToEmbedderStub(importFunctionNum); |
134 | } |
135 | |
136 | auto* moduleRecord = jsCast<WebAssemblyModuleRecord*>(m_moduleNamespaceObject->moduleRecord()); |
137 | moduleRecord->prepareLink(vm, this); |
138 | |
139 | if (creationMode == Wasm::CreationMode::FromJS) { |
140 | moduleRecord->link(exec, jsNull(), importObject, creationMode); |
141 | RETURN_IF_EXCEPTION(scope, void()); |
142 | |
143 | JSValue startResult = moduleRecord->evaluate(exec); |
144 | UNUSED_PARAM(startResult); |
145 | RETURN_IF_EXCEPTION(scope, void()); |
146 | } |
147 | } |
148 | |
149 | Identifier JSWebAssemblyInstance::createPrivateModuleKey() |
150 | { |
151 | return Identifier::fromUid(PrivateName(PrivateName::Description, "WebAssemblyInstance" )); |
152 | } |
153 | |
154 | JSWebAssemblyInstance* JSWebAssemblyInstance::create(VM& vm, ExecState* exec, const Identifier& moduleKey, JSWebAssemblyModule* jsModule, JSObject* importObject, Structure* instanceStructure, Ref<Wasm::Module>&& module, Wasm::CreationMode creationMode) |
155 | { |
156 | auto throwScope = DECLARE_THROW_SCOPE(vm); |
157 | auto* globalObject = exec->lexicalGlobalObject(); |
158 | |
159 | const Wasm::ModuleInformation& moduleInformation = jsModule->moduleInformation(); |
160 | |
161 | auto exception = [&] (JSObject* error) { |
162 | throwException(exec, throwScope, error); |
163 | return nullptr; |
164 | }; |
165 | |
166 | if (!globalObject->webAssemblyEnabled()) |
167 | return exception(createEvalError(exec, globalObject->webAssemblyDisabledErrorMessage())); |
168 | |
169 | auto importFailMessage = [&] (const Wasm::Import& import, const char* before, const char* after) { |
170 | return makeString(before, " " , String::fromUTF8(import.module), ":" , String::fromUTF8(import.field), " " , after); |
171 | }; |
172 | |
173 | WebAssemblyModuleRecord* moduleRecord = WebAssemblyModuleRecord::create(exec, vm, globalObject->webAssemblyModuleRecordStructure(), moduleKey, moduleInformation); |
174 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
175 | |
176 | JSModuleNamespaceObject* moduleNamespace = moduleRecord->getModuleNamespace(exec); |
177 | |
178 | auto storeTopCallFrame = [&vm] (void* topCallFrame) { |
179 | vm.topCallFrame = bitwise_cast<ExecState*>(topCallFrame); |
180 | }; |
181 | |
182 | // FIXME: These objects could be pretty big we should try to throw OOM here. |
183 | auto* jsInstance = new (NotNull, allocateCell<JSWebAssemblyInstance>(vm.heap)) JSWebAssemblyInstance(vm, instanceStructure, |
184 | Wasm::Instance::create(&vm.wasmContext, WTFMove(module), &vm.topEntryFrame, vm.addressOfSoftStackLimit(), WTFMove(storeTopCallFrame))); |
185 | jsInstance->finishCreation(vm, jsModule, moduleNamespace); |
186 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
187 | |
188 | // Let funcs, memories and tables be initially-empty lists of callable JavaScript objects, WebAssembly.Memory objects and WebAssembly.Table objects, respectively. |
189 | // Let imports be an initially-empty list of external values. |
190 | bool hasMemoryImport = false; |
191 | |
192 | if (creationMode == Wasm::CreationMode::FromJS) { |
193 | // If the list of module.imports is not empty and Type(importObject) is not Object, a TypeError is thrown. |
194 | if (moduleInformation.imports.size() && !importObject) |
195 | return exception(createTypeError(exec, "can't make WebAssembly.Instance because there is no imports Object and the WebAssembly.Module requires imports"_s )); |
196 | } |
197 | |
198 | // For each import i in module.imports: |
199 | for (auto& import : moduleInformation.imports) { |
200 | Identifier moduleName = Identifier::fromString(&vm, String::fromUTF8(import.module)); |
201 | Identifier fieldName = Identifier::fromString(&vm, String::fromUTF8(import.field)); |
202 | moduleRecord->appendRequestedModule(moduleName); |
203 | moduleRecord->addImportEntry(WebAssemblyModuleRecord::ImportEntry { |
204 | WebAssemblyModuleRecord::ImportEntryType::Single, |
205 | moduleName, |
206 | fieldName, |
207 | Identifier::fromUid(PrivateName(PrivateName::Description, "WebAssemblyImportName" )), |
208 | }); |
209 | |
210 | // Skip Wasm::ExternalKind::Function validation here. It will be done in WebAssemblyModuleRecord::link. |
211 | // Eventually we will move all the linking code here to WebAssemblyModuleRecord::link. |
212 | switch (import.kind) { |
213 | case Wasm::ExternalKind::Function: |
214 | case Wasm::ExternalKind::Global: |
215 | case Wasm::ExternalKind::Table: |
216 | continue; |
217 | case Wasm::ExternalKind::Memory: |
218 | break; |
219 | } |
220 | |
221 | JSValue value; |
222 | if (creationMode == Wasm::CreationMode::FromJS) { |
223 | // 1. Let o be the resultant value of performing Get(importObject, i.module_name). |
224 | JSValue importModuleValue = importObject->get(exec, moduleName); |
225 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
226 | // 2. If Type(o) is not Object, throw a TypeError. |
227 | if (!importModuleValue.isObject()) |
228 | return exception(createTypeError(exec, importFailMessage(import, "import" , "must be an object" ), defaultSourceAppender, runtimeTypeForValue(vm, importModuleValue))); |
229 | |
230 | // 3. Let v be the value of performing Get(o, i.item_name) |
231 | JSObject* object = jsCast<JSObject*>(importModuleValue); |
232 | value = object->get(exec, fieldName); |
233 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
234 | } |
235 | if (!value) |
236 | value = jsUndefined(); |
237 | |
238 | switch (import.kind) { |
239 | case Wasm::ExternalKind::Function: |
240 | case Wasm::ExternalKind::Global: |
241 | case Wasm::ExternalKind::Table: |
242 | break; |
243 | |
244 | case Wasm::ExternalKind::Memory: { |
245 | // 6. If i is a memory import: |
246 | RELEASE_ASSERT(!hasMemoryImport); // This should be guaranteed by a validation failure. |
247 | RELEASE_ASSERT(moduleInformation.memory); |
248 | hasMemoryImport = true; |
249 | JSWebAssemblyMemory* memory = jsDynamicCast<JSWebAssemblyMemory*>(vm, value); |
250 | // i. If v is not a WebAssembly.Memory object, throw a WebAssembly.LinkError. |
251 | if (!memory) |
252 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "is not an instance of WebAssembly.Memory" ))); |
253 | |
254 | Wasm::PageCount declaredInitial = moduleInformation.memory.initial(); |
255 | Wasm::PageCount importedInitial = memory->memory().initial(); |
256 | if (importedInitial < declaredInitial) |
257 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "provided an 'initial' that is smaller than the module's declared 'initial' import memory size" ))); |
258 | |
259 | if (Wasm::PageCount declaredMaximum = moduleInformation.memory.maximum()) { |
260 | Wasm::PageCount importedMaximum = memory->memory().maximum(); |
261 | if (!importedMaximum) |
262 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "did not have a 'maximum' but the module requires that it does" ))); |
263 | |
264 | if (importedMaximum > declaredMaximum) |
265 | return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import" , "provided a 'maximum' that is larger than the module's declared 'maximum' import memory size" ))); |
266 | } |
267 | |
268 | // ii. Append v to memories. |
269 | // iii. Append v.[[Memory]] to imports. |
270 | jsInstance->setMemory(vm, memory); |
271 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
272 | break; |
273 | } |
274 | } |
275 | } |
276 | ASSERT(moduleRecord->importEntries().size() == moduleInformation.imports.size()); |
277 | |
278 | { |
279 | if (!!moduleInformation.memory && moduleInformation.memory.isImport()) { |
280 | // We should either have a Memory import or we should have thrown an exception. |
281 | RELEASE_ASSERT(hasMemoryImport); |
282 | } |
283 | |
284 | if (moduleInformation.memory && !hasMemoryImport) { |
285 | // We create a memory when it's a memory definition. |
286 | RELEASE_ASSERT(!moduleInformation.memory.isImport()); |
287 | |
288 | auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, globalObject->webAssemblyMemoryStructure()); |
289 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
290 | |
291 | RefPtr<Wasm::Memory> memory = Wasm::Memory::tryCreate(moduleInformation.memory.initial(), moduleInformation.memory.maximum(), |
292 | [&vm] (Wasm::Memory::NotifyPressure) { vm.heap.collectAsync(CollectionScope::Full); }, |
293 | [&vm] (Wasm::Memory::SyncTryToReclaim) { vm.heap.collectSync(CollectionScope::Full); }, |
294 | [&vm, jsMemory] (Wasm::Memory::GrowSuccess, Wasm::PageCount oldPageCount, Wasm::PageCount newPageCount) { jsMemory->growSuccessCallback(vm, oldPageCount, newPageCount); }); |
295 | if (!memory) |
296 | return exception(createOutOfMemoryError(exec)); |
297 | |
298 | jsMemory->adopt(memory.releaseNonNull()); |
299 | jsInstance->setMemory(vm, jsMemory); |
300 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
301 | } |
302 | } |
303 | |
304 | if (!jsInstance->memory()) { |
305 | // Make sure we have a dummy memory, so that wasm -> wasm thunks avoid checking for a nullptr Memory when trying to set pinned registers. |
306 | auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, globalObject->webAssemblyMemoryStructure()); |
307 | jsMemory->adopt(Wasm::Memory::create()); |
308 | jsInstance->setMemory(vm, jsMemory); |
309 | RETURN_IF_EXCEPTION(throwScope, nullptr); |
310 | } |
311 | |
312 | return jsInstance; |
313 | } |
314 | |
315 | } // namespace JSC |
316 | |
317 | #endif // ENABLE(WEBASSEMBLY) |
318 | |