[WebAssembly][Modules] Prototype wasm import
[WebKit-https.git] / Source / JavaScriptCore / wasm / js / JSWebAssemblyInstance.cpp
1 /*
2  * Copyright (C) 2016-2018 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<PoisonedBarrier<JSObject>>(i)) PoisonedBarrier<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     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<PoisonedBarrier<JSObject>>(i)); // This also keeps the functions' JSWebAssemblyInstance alive.
92 }
93
94 void JSWebAssemblyInstance::finalizeCreation(VM& vm, ExecState* exec, Ref<Wasm::CodeBlock>&& wasmCodeBlock, ModuleRunMode moduleRunMode)
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()->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()->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 (moduleRunMode == ModuleRunMode::Run) {
133         moduleRecord->link(exec, jsNull());
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)
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     // If the list of module.imports is not empty and Type(importObject) is not Object, a TypeError is thrown.
167     if (moduleInformation.imports.size() && !importObject)
168         return exception(createTypeError(exec, ASCIILiteral("can't make WebAssembly.Instance because there is no imports Object and the WebAssembly.Module requires imports")));
169
170     WebAssemblyModuleRecord* moduleRecord = WebAssemblyModuleRecord::create(exec, vm, globalObject->webAssemblyModuleRecordStructure(), moduleKey, moduleInformation);
171     RETURN_IF_EXCEPTION(throwScope, nullptr);
172
173     JSModuleNamespaceObject* moduleNamespace = moduleRecord->getModuleNamespace(exec);
174
175     auto storeTopCallFrame = [&vm] (void* topCallFrame) {
176         vm.topCallFrame = bitwise_cast<ExecState*>(topCallFrame);
177     };
178
179     // FIXME: These objects could be pretty big we should try to throw OOM here.
180     auto* jsInstance = new (NotNull, allocateCell<JSWebAssemblyInstance>(vm.heap)) JSWebAssemblyInstance(vm, instanceStructure, 
181         Wasm::Instance::create(&vm.wasmContext, WTFMove(module), &vm.topEntryFrame, vm.addressOfSoftStackLimit(), WTFMove(storeTopCallFrame)));
182     jsInstance->finishCreation(vm, jsModule, moduleNamespace);
183     RETURN_IF_EXCEPTION(throwScope, nullptr);
184
185     // Let funcs, memories and tables be initially-empty lists of callable JavaScript objects, WebAssembly.Memory objects and WebAssembly.Table objects, respectively.
186     // Let imports be an initially-empty list of external values.
187     unsigned numImportFunctions = 0;
188     unsigned numImportGlobals = 0;
189
190     bool hasMemoryImport = false;
191     bool hasTableImport = false;
192     // For each import i in module.imports:
193     for (auto& import : moduleInformation.imports) {
194         // 1. Let o be the resultant value of performing Get(importObject, i.module_name).
195         JSValue importModuleValue = importObject->get(exec, Identifier::fromString(&vm, String::fromUTF8(import.module)));
196         RETURN_IF_EXCEPTION(throwScope, nullptr);
197         // 2. If Type(o) is not Object, throw a TypeError.
198         if (!importModuleValue.isObject())
199             return exception(createTypeError(exec, importFailMessage(import, "import", "must be an object"), defaultSourceAppender, runtimeTypeForValue(importModuleValue)));
200
201         // 3. Let v be the value of performing Get(o, i.item_name)
202         JSObject* object = jsCast<JSObject*>(importModuleValue);
203         JSValue value = object->get(exec, Identifier::fromString(&vm, String::fromUTF8(import.field)));
204         RETURN_IF_EXCEPTION(throwScope, nullptr);
205
206         switch (import.kind) {
207         case Wasm::ExternalKind::Function: {
208             // 4. If i is a function import:
209             // i. If IsCallable(v) is false, throw a WebAssembly.LinkError.
210             if (!value.isFunction())
211                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "import function", "must be callable")));
212
213             Wasm::Instance* calleeInstance = nullptr;
214             WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = nullptr;
215             JSObject* function = jsCast<JSObject*>(value);
216
217             // ii. If v is an Exported Function Exotic Object:
218             WebAssemblyFunction* wasmFunction;
219             WebAssemblyWrapperFunction* wasmWrapperFunction;
220             if (isWebAssemblyHostFunction(vm, function, wasmFunction, wasmWrapperFunction)) {
221                 // a. If the signature of v does not match the signature of i, throw a WebAssembly.LinkError.
222                 Wasm::SignatureIndex importedSignatureIndex;
223                 if (wasmFunction) {
224                     importedSignatureIndex = wasmFunction->signatureIndex();
225                     calleeInstance = &wasmFunction->instance()->instance();
226                     entrypointLoadLocation = wasmFunction->entrypointLoadLocation();
227                 }
228                 else {
229                     importedSignatureIndex = wasmWrapperFunction->signatureIndex();
230                     // b. Let closure be v.[[Closure]].
231                     function = wasmWrapperFunction->function();
232                 }
233                 Wasm::SignatureIndex expectedSignatureIndex = moduleInformation.importFunctionSignatureIndices[import.kindIndex];
234                 if (importedSignatureIndex != expectedSignatureIndex)
235                     return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported function", "signature doesn't match the provided WebAssembly function's signature")));
236             }
237             // iii. Otherwise:
238             // a. Let closure be a new host function of the given signature which calls v by coercing WebAssembly arguments to JavaScript arguments via ToJSValue and returns the result, if any, by coercing via ToWebAssemblyValue.
239             // Note: done as part of Plan compilation.
240             // iv. Append v to funcs.
241             // Note: adding the JSCell to the instance list fulfills closure requirements b. above (the WebAssembly.Instance wil be kept alive) and v. below (the JSFunction).
242
243             ASSERT(numImportFunctions == import.kindIndex);
244             auto* info = jsInstance->instance().importFunctionInfo(numImportFunctions);
245             info->targetInstance = calleeInstance;
246             info->wasmEntrypointLoadLocation = entrypointLoadLocation;
247             jsInstance->instance().importFunction<PoisonedBarrier<JSObject>>(numImportFunctions)->set(vm, jsInstance, function);
248             ++numImportFunctions;
249             // v. Append closure to imports.
250             break;
251         }
252         case Wasm::ExternalKind::Table: {
253             RELEASE_ASSERT(!hasTableImport); // This should be guaranteed by a validation failure.
254             // 7. Otherwise (i is a table import):
255             hasTableImport = true;
256             JSWebAssemblyTable* table = jsDynamicCast<JSWebAssemblyTable*>(vm, value);
257             // i. If v is not a WebAssembly.Table object, throw a WebAssembly.LinkError.
258             if (!table)
259                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Table import", "is not an instance of WebAssembly.Table")));
260
261             uint32_t expectedInitial = moduleInformation.tableInformation.initial();
262             uint32_t actualInitial = table->length();
263             if (actualInitial < expectedInitial)
264                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Table import", "provided an 'initial' that is too small")));
265
266             if (std::optional<uint32_t> expectedMaximum = moduleInformation.tableInformation.maximum()) {
267                 std::optional<uint32_t> actualMaximum = table->maximum();
268                 if (!actualMaximum)
269                     return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Table import", "does not have a 'maximum' but the module requires that it does")));
270                 if (*actualMaximum > *expectedMaximum)
271                     return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Imported Table", "'maximum' is larger than the module's expected 'maximum'")));
272             }
273
274             // ii. Append v to tables.
275             // iii. Append v.[[Table]] to imports.
276             jsInstance->setTable(vm, table);
277             RETURN_IF_EXCEPTION(throwScope, nullptr);
278             break;
279         }
280
281         case Wasm::ExternalKind::Memory: {
282             // 6. If i is a memory import:
283             RELEASE_ASSERT(!hasMemoryImport); // This should be guaranteed by a validation failure.
284             RELEASE_ASSERT(moduleInformation.memory);
285             hasMemoryImport = true;
286             JSWebAssemblyMemory* memory = jsDynamicCast<JSWebAssemblyMemory*>(vm, value);
287             // i. If v is not a WebAssembly.Memory object, throw a WebAssembly.LinkError.
288             if (!memory)
289                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import", "is not an instance of WebAssembly.Memory")));
290
291             Wasm::PageCount declaredInitial = moduleInformation.memory.initial();
292             Wasm::PageCount importedInitial = memory->memory().initial();
293             if (importedInitial < declaredInitial)
294                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import", "provided an 'initial' that is smaller than the module's declared 'initial' import memory size")));
295
296             if (Wasm::PageCount declaredMaximum = moduleInformation.memory.maximum()) {
297                 Wasm::PageCount importedMaximum = memory->memory().maximum();
298                 if (!importedMaximum)
299                     return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import", "did not have a 'maximum' but the module requires that it does")));
300
301                 if (importedMaximum > declaredMaximum)
302                     return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Memory import", "provided a 'maximum' that is larger than the module's declared 'maximum' import memory size")));
303             }
304
305             // ii. Append v to memories.
306             // iii. Append v.[[Memory]] to imports.
307             jsInstance->setMemory(vm, memory);
308             RETURN_IF_EXCEPTION(throwScope, nullptr);
309             break;
310         }
311         case Wasm::ExternalKind::Global: {
312             // 5. If i is a global import:
313             // i. If i is not an immutable global, throw a TypeError.
314             ASSERT(moduleInformation.globals[import.kindIndex].mutability == Wasm::Global::Immutable);
315             // ii. If the global_type of i is i64 or Type(v) is not Number, throw a WebAssembly.LinkError.
316             if (moduleInformation.globals[import.kindIndex].type == Wasm::I64)
317                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported global", "cannot be an i64")));
318             if (!value.isNumber())
319                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported global", "must be a number")));
320             // iii. Append ToWebAssemblyValue(v) to imports.
321             ASSERT(numImportGlobals == import.kindIndex);
322             switch (moduleInformation.globals[import.kindIndex].type) {
323             case Wasm::I32:
324                 jsInstance->instance().setGlobal(numImportGlobals++, value.toInt32(exec));
325                 break;
326             case Wasm::F32:
327                 jsInstance->instance().setGlobal(numImportGlobals++, bitwise_cast<uint32_t>(value.toFloat(exec)));
328                 break;
329             case Wasm::F64:
330                 jsInstance->instance().setGlobal(numImportGlobals++, bitwise_cast<uint64_t>(value.asNumber()));
331                 break;
332             default:
333                 RELEASE_ASSERT_NOT_REACHED();
334             }
335             throwScope.assertNoException();
336             break;
337         }
338         }
339     }
340
341     {
342         if (!!moduleInformation.memory && moduleInformation.memory.isImport()) {
343             // We should either have a Memory import or we should have thrown an exception.
344             RELEASE_ASSERT(hasMemoryImport);
345         }
346
347         if (moduleInformation.memory && !hasMemoryImport) {
348             // We create a memory when it's a memory definition.
349             RELEASE_ASSERT(!moduleInformation.memory.isImport());
350
351             auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, exec->lexicalGlobalObject()->WebAssemblyMemoryStructure());
352             RETURN_IF_EXCEPTION(throwScope, nullptr);
353
354             RefPtr<Wasm::Memory> memory = Wasm::Memory::create(moduleInformation.memory.initial(), moduleInformation.memory.maximum(),
355                 [&vm] (Wasm::Memory::NotifyPressure) { vm.heap.collectAsync(CollectionScope::Full); },
356                 [&vm] (Wasm::Memory::SyncTryToReclaim) { vm.heap.collectSync(CollectionScope::Full); },
357                 [&vm, jsMemory] (Wasm::Memory::GrowSuccess, Wasm::PageCount oldPageCount, Wasm::PageCount newPageCount) { jsMemory->growSuccessCallback(vm, oldPageCount, newPageCount); });
358             if (!memory)
359                 return exception(createOutOfMemoryError(exec));
360
361             jsMemory->adopt(memory.releaseNonNull());
362             jsInstance->setMemory(vm, jsMemory);
363             RETURN_IF_EXCEPTION(throwScope, nullptr);
364         }
365     }
366
367     {
368         if (!!moduleInformation.tableInformation && moduleInformation.tableInformation.isImport()) {
369             // We should either have a Table import or we should have thrown an exception.
370             RELEASE_ASSERT(hasTableImport);
371         }
372
373         if (!!moduleInformation.tableInformation && !hasTableImport) {
374             RELEASE_ASSERT(!moduleInformation.tableInformation.isImport());
375             // We create a Table when it's a Table definition.
376             RefPtr<Wasm::Table> wasmTable = Wasm::Table::create(moduleInformation.tableInformation.initial(), moduleInformation.tableInformation.maximum());
377             if (!wasmTable)
378                 return exception(createJSWebAssemblyLinkError(exec, vm, "couldn't create Table"));
379             JSWebAssemblyTable* table = JSWebAssemblyTable::create(exec, vm, exec->lexicalGlobalObject()->WebAssemblyTableStructure(), wasmTable.releaseNonNull());
380             // We should always be able to allocate a JSWebAssemblyTable we've defined.
381             // If it's defined to be too large, we should have thrown a validation error.
382             throwScope.assertNoException();
383             ASSERT(table);
384             jsInstance->setTable(vm, table);
385             RETURN_IF_EXCEPTION(throwScope, nullptr);
386         }
387     }
388     
389     if (!jsInstance->memory()) {
390         // Make sure we have a dummy memory, so that wasm -> wasm thunks avoid checking for a nullptr Memory when trying to set pinned registers.
391         auto* jsMemory = JSWebAssemblyMemory::create(exec, vm, exec->lexicalGlobalObject()->WebAssemblyMemoryStructure());
392         jsMemory->adopt(Wasm::Memory::create().releaseNonNull());
393         jsInstance->setMemory(vm, jsMemory);
394         RETURN_IF_EXCEPTION(throwScope, nullptr);
395     }
396     
397     // Globals
398     {
399         ASSERT(numImportGlobals == moduleInformation.firstInternalGlobal);
400         for (size_t globalIndex = numImportGlobals; globalIndex < moduleInformation.globals.size(); ++globalIndex) {
401             const auto& global = moduleInformation.globals[globalIndex];
402             ASSERT(global.initializationType != Wasm::Global::IsImport);
403             if (global.initializationType == Wasm::Global::FromGlobalImport) {
404                 ASSERT(global.initialBitsOrImportNumber < numImportGlobals);
405                 jsInstance->instance().setGlobal(globalIndex, jsInstance->instance().loadI64Global(global.initialBitsOrImportNumber));
406             } else
407                 jsInstance->instance().setGlobal(globalIndex, global.initialBitsOrImportNumber);
408         }
409     }
410
411     return jsInstance;
412 }
413
414 } // namespace JSC
415
416 #endif // ENABLE(WEBASSEMBLY)