1f4164861d1672c854a2de23bb9f6be84f710f6d
[WebKit-https.git] / Source / JavaScriptCore / wasm / js / WebAssemblyModuleRecord.cpp
1 /*
2  * Copyright (C) 2016 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 "WebAssemblyModuleRecord.h"
28
29 #if ENABLE(WEBASSEMBLY)
30
31 #include "Error.h"
32 #include "JSCInlines.h"
33 #include "JSLexicalEnvironment.h"
34 #include "JSModuleEnvironment.h"
35 #include "JSWebAssemblyHelpers.h"
36 #include "JSWebAssemblyInstance.h"
37 #include "JSWebAssemblyLinkError.h"
38 #include "JSWebAssemblyModule.h"
39 #include "ProtoCallFrame.h"
40 #include "WasmFormat.h"
41 #include "WasmSignature.h"
42 #include "WebAssemblyFunction.h"
43 #include <limits>
44
45 namespace JSC {
46
47 const ClassInfo WebAssemblyModuleRecord::s_info = { "WebAssemblyModuleRecord", &Base::s_info, nullptr, CREATE_METHOD_TABLE(WebAssemblyModuleRecord) };
48
49 Structure* WebAssemblyModuleRecord::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
50 {
51     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
52 }
53
54 WebAssemblyModuleRecord* WebAssemblyModuleRecord::create(ExecState* exec, VM& vm, Structure* structure, const Identifier& moduleKey, const Wasm::ModuleInformation& moduleInformation)
55 {
56     WebAssemblyModuleRecord* instance = new (NotNull, allocateCell<WebAssemblyModuleRecord>(vm.heap)) WebAssemblyModuleRecord(vm, structure, moduleKey);
57     instance->finishCreation(exec, vm, moduleInformation);
58     return instance;
59 }
60
61 WebAssemblyModuleRecord::WebAssemblyModuleRecord(VM& vm, Structure* structure, const Identifier& moduleKey)
62     : Base(vm, structure, moduleKey)
63 {
64 }
65
66 void WebAssemblyModuleRecord::destroy(JSCell* cell)
67 {
68     WebAssemblyModuleRecord* thisObject = static_cast<WebAssemblyModuleRecord*>(cell);
69     thisObject->WebAssemblyModuleRecord::~WebAssemblyModuleRecord();
70 }
71
72 void WebAssemblyModuleRecord::finishCreation(ExecState* exec, VM& vm, const Wasm::ModuleInformation& moduleInformation)
73 {
74     Base::finishCreation(exec, vm);
75     ASSERT(inherits(vm, info()));
76     for (const auto& exp : moduleInformation.exports) {
77         Identifier field = Identifier::fromString(&vm, exp.field);
78         addExportEntry(ExportEntry::createLocal(field, field));
79     }
80 }
81
82 void WebAssemblyModuleRecord::visitChildren(JSCell* cell, SlotVisitor& visitor)
83 {
84     WebAssemblyModuleRecord* thisObject = jsCast<WebAssemblyModuleRecord*>(cell);
85     Base::visitChildren(thisObject, visitor);
86     visitor.append(thisObject->m_instance);
87     visitor.append(thisObject->m_startFunction);
88 }
89
90 void WebAssemblyModuleRecord::link(ExecState* exec, JSWebAssemblyModule* module, JSWebAssemblyInstance* instance)
91 {
92     VM& vm = exec->vm();
93     auto scope = DECLARE_THROW_SCOPE(vm);
94     UNUSED_PARAM(scope);
95     auto* globalObject = exec->lexicalGlobalObject();
96
97     JSWebAssemblyCodeBlock* codeBlock = instance->codeBlock();
98     const Wasm::ModuleInformation& moduleInformation = module->moduleInformation();
99
100     SymbolTable* exportSymbolTable = module->exportSymbolTable();
101     unsigned functionImportCount = codeBlock->functionImportCount();
102
103     // FIXME wire up the imports. https://bugs.webkit.org/show_bug.cgi?id=165118
104
105     // Let exports be a list of (string, JS value) pairs that is mapped from each external value e in instance.exports as follows:
106     JSModuleEnvironment* moduleEnvironment = JSModuleEnvironment::create(vm, globalObject, nullptr, exportSymbolTable, JSValue(), this);
107     for (const auto& exp : moduleInformation.exports) {
108         JSValue exportedValue;
109         switch (exp.kind) {
110         case Wasm::ExternalKind::Function: {
111             // 1. If e is a closure c:
112             //   i. If there is an Exported Function Exotic Object func in funcs whose func.[[Closure]] equals c, then return func.
113             //   ii. (Note: At most one wrapper is created for any closure, so func is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import that is already an Exported Function Exotic Object, then the original function object will be found. For imports that are regular JS functions, a new wrapper will be created.)
114             if (exp.kindIndex < functionImportCount) {
115                 unsigned functionIndex = exp.kindIndex;
116                 JSObject* functionImport = instance->importFunction(functionIndex);
117                 if (isWebAssemblyHostFunction(vm, functionImport))
118                     exportedValue = functionImport;
119                 else {
120                     Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(functionIndex);
121                     exportedValue = WebAssemblyWrapperFunction::create(vm, globalObject, functionImport, functionIndex, codeBlock, signatureIndex);
122                 }
123             } else {
124                 //   iii. Otherwise:
125                 //     a. Let func be an Exported Function Exotic Object created from c.
126                 //     b. Append func to funcs.
127                 //     c. Return func.
128                 Wasm::Callee& jsEntrypointCallee = codeBlock->jsEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
129                 Wasm::Callee& wasmEntrypointCallee = codeBlock->wasmEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
130                 Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(exp.kindIndex);
131                 const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
132                 WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature.argumentCount(), exp.field, instance, jsEntrypointCallee, wasmEntrypointCallee, signatureIndex);
133                 exportedValue = function;
134             }
135             break;
136         }
137         case Wasm::ExternalKind::Table: {
138             // This should be guaranteed by module verification.
139             RELEASE_ASSERT(instance->table()); 
140             ASSERT(exp.kindIndex == 0);
141
142             exportedValue = instance->table();
143             break;
144         }
145         case Wasm::ExternalKind::Memory: {
146             ASSERT(exp.kindIndex == 0);
147
148             exportedValue = instance->memory();
149             break;
150         }
151         case Wasm::ExternalKind::Global: {
152             // Assert: the global is immutable by MVP validation constraint.
153             const Wasm::Global& global = moduleInformation.globals[exp.kindIndex];
154             ASSERT(global.mutability == Wasm::Global::Immutable);
155             // Return ToJSValue(v).
156             switch (global.type) {
157             case Wasm::I32:
158                 exportedValue = JSValue(instance->loadI32Global(exp.kindIndex));
159                 break;
160
161             case Wasm::I64:
162                 throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("exported global cannot be an i64")));
163                 return;
164
165             case Wasm::F32:
166                 exportedValue = JSValue(instance->loadF32Global(exp.kindIndex));
167                 break;
168
169             case Wasm::F64:
170                 exportedValue = JSValue(instance->loadF64Global(exp.kindIndex));
171                 break;
172
173             default:
174                 RELEASE_ASSERT_NOT_REACHED();
175             }
176             break;
177         }
178         }
179
180         bool shouldThrowReadOnlyError = false;
181         bool ignoreReadOnlyErrors = true;
182         bool putResult = false;
183         symbolTablePutTouchWatchpointSet(moduleEnvironment, exec, Identifier::fromString(&vm, exp.field), exportedValue, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
184         RELEASE_ASSERT(putResult);
185     }
186
187     bool hasStart = !!moduleInformation.startFunctionIndexSpace;
188     if (hasStart) {
189         auto startFunctionIndexSpace = moduleInformation.startFunctionIndexSpace.value_or(0);
190         Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(startFunctionIndexSpace);
191         const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
192         // The start function must not take any arguments or return anything. This is enforced by the parser.
193         ASSERT(!signature.argumentCount());
194         ASSERT(signature.returnType() == Wasm::Void);
195         if (startFunctionIndexSpace < codeBlock->functionImportCount()) {
196             JSObject* startFunction = instance->importFunction(startFunctionIndexSpace);
197             m_startFunction.set(vm, this, startFunction);
198         } else {
199             Wasm::Callee& jsEntrypointCallee = codeBlock->jsEntrypointCalleeFromFunctionIndexSpace(startFunctionIndexSpace);
200             Wasm::Callee& wasmEntrypointCallee = codeBlock->wasmEntrypointCalleeFromFunctionIndexSpace(startFunctionIndexSpace);
201             WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature.argumentCount(), "start", instance, jsEntrypointCallee, wasmEntrypointCallee, signatureIndex);
202             m_startFunction.set(vm, this, function);
203         }
204     }
205
206     RELEASE_ASSERT(!m_instance);
207     m_instance.set(vm, this, instance);
208     m_moduleEnvironment.set(vm, this, moduleEnvironment);
209 }
210
211 template <typename Scope, typename M, typename N, typename ...Args>
212 NEVER_INLINE static JSValue dataSegmentFail(ExecState* exec, VM& vm, Scope& scope, M memorySize, N segmentSize, N offset, Args... args)
213 {
214     return throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, makeString(ASCIILiteral("Invalid data segment initialization: segment of "), String::number(segmentSize), ASCIILiteral(" bytes memory of "), String::number(memorySize), ASCIILiteral(" bytes, at offset "), String::number(offset), args...)));
215 }
216
217 JSValue WebAssemblyModuleRecord::evaluate(ExecState* exec)
218 {
219     VM& vm = exec->vm();
220     auto scope = DECLARE_THROW_SCOPE(vm);
221
222     {
223         JSWebAssemblyModule* module = m_instance->module();
224         JSWebAssemblyCodeBlock* codeBlock = m_instance->codeBlock();
225         const Wasm::ModuleInformation& moduleInformation = module->moduleInformation();
226         JSWebAssemblyTable* table = m_instance->table();
227         for (const Wasm::Element& element : moduleInformation.elements) {
228             // It should be a validation error to have any elements without a table.
229             // Also, it could be that a table wasn't imported, or that the table
230             // imported wasn't compatible. However, those should error out before
231             // getting here.
232             ASSERT(!!table);
233             if (!element.functionIndices.size())
234                 continue;
235
236             uint32_t tableIndex;
237
238             if (element.offset.isGlobalImport())
239                 tableIndex = static_cast<uint32_t>(m_instance->loadI32Global(element.offset.globalImportIndex()));
240             else
241                 tableIndex = element.offset.constValue();
242
243             uint64_t lastWrittenIndex = static_cast<uint64_t>(tableIndex) + static_cast<uint64_t>(element.functionIndices.size()) - 1;
244             if (lastWrittenIndex >= table->size())
245                 return throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Element is trying to set an out of bounds table index")));
246
247             for (uint32_t i = 0; i < element.functionIndices.size(); ++i) {
248                 // FIXME: This essentially means we're exporting an import.
249                 // We need a story here. We need to create a WebAssemblyFunction
250                 // for the import.
251                 // https://bugs.webkit.org/show_bug.cgi?id=165510
252                 uint32_t functionIndex = element.functionIndices[i];
253                 Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(functionIndex);
254                 if (functionIndex < codeBlock->functionImportCount()) {
255                     JSObject* functionImport = jsCast<JSObject*>(m_instance->importFunction(functionIndex));
256                     if (isWebAssemblyHostFunction(vm, functionImport)) {
257                         WebAssemblyFunction* wasmFunction = jsDynamicCast<WebAssemblyFunction*>(vm, functionImport);
258                         // If we ever import a WebAssemblyWrapperFunction, we set the import as the unwrapped value.
259                         // Because a WebAssemblyWrapperFunction can never wrap another WebAssemblyWrapperFunction,
260                         // the only type this could be is WebAssemblyFunction.
261                         RELEASE_ASSERT(wasmFunction);
262                         table->setFunction(vm, tableIndex, wasmFunction);
263                         ++tableIndex;
264                         continue;
265                     }
266
267                     table->setFunction(vm, tableIndex,
268                         WebAssemblyWrapperFunction::create(vm, m_instance->globalObject(), functionImport, functionIndex, codeBlock, signatureIndex));
269                     ++tableIndex;
270                     continue;
271                 }
272
273                 Wasm::Callee& jsEntrypointCallee = codeBlock->jsEntrypointCalleeFromFunctionIndexSpace(functionIndex);
274                 Wasm::Callee& wasmEntrypointCallee = codeBlock->wasmEntrypointCalleeFromFunctionIndexSpace(functionIndex);
275                 const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
276                 // FIXME: Say we export local function "foo" at function index 0.
277                 // What if we also set it to the table an Element w/ index 0.
278                 // Does (new Instance(...)).exports.foo === table.get(0)?
279                 // https://bugs.webkit.org/show_bug.cgi?id=165825
280                 WebAssemblyFunction* function = WebAssemblyFunction::create(
281                     vm, m_instance->globalObject(), signature.argumentCount(), String(), m_instance.get(), jsEntrypointCallee, wasmEntrypointCallee, signatureIndex);
282
283                 table->setFunction(vm, tableIndex, function);
284                 ++tableIndex;
285             }
286         }
287     }
288
289     {
290         const Vector<Wasm::Segment::Ptr>& data = m_instance->module()->moduleInformation().data;
291         JSWebAssemblyMemory* jsMemory = m_instance->memory();
292         if (!data.isEmpty()) {
293             uint8_t* memory = reinterpret_cast<uint8_t*>(jsMemory->memory().memory());
294             uint64_t sizeInBytes = jsMemory->memory().size();
295             for (auto& segment : data) {
296                 if (segment->sizeInBytes) {
297                     uint32_t offset;
298                     if (segment->offset.isGlobalImport())
299                         offset = static_cast<uint32_t>(m_instance->loadI32Global(segment->offset.globalImportIndex()));
300                     else
301                         offset = segment->offset.constValue();
302
303                     if (UNLIKELY(sizeInBytes < segment->sizeInBytes))
304                         return dataSegmentFail(exec, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ASCIILiteral(", segment is too big"));
305                     if (UNLIKELY(offset > sizeInBytes - segment->sizeInBytes))
306                         return dataSegmentFail(exec, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ASCIILiteral(", segment writes outside of memory"));
307                     RELEASE_ASSERT(memory);
308                     memcpy(memory + offset, &segment->byte(0), segment->sizeInBytes);
309                 }
310             }
311         }
312     }
313
314     if (JSObject* startFunction = m_startFunction.get()) {
315         CallData callData;
316         CallType callType = JSC::getCallData(startFunction, callData);
317         call(exec, startFunction, callType, callData, jsUndefined(), exec->emptyList());
318         RETURN_IF_EXCEPTION(scope, { });
319     }
320
321     return jsUndefined();
322 }
323
324 } // namespace JSC
325
326 #endif // ENABLE(WEBASSEMBLY)