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