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