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