f17f29df9850c3c8f464a2fe1606f74dace0023a
[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::F32:
152                 exportedValue = JSValue(instance->loadF32Global(exp.kindIndex));
153                 break;
154
155             case Wasm::F64:
156                 exportedValue = JSValue(instance->loadF64Global(exp.kindIndex));
157                 break;
158
159             default:
160                 RELEASE_ASSERT_NOT_REACHED();
161             }
162             break;
163         }
164         }
165
166         bool shouldThrowReadOnlyError = false;
167         bool ignoreReadOnlyErrors = true;
168         bool putResult = false;
169         symbolTablePutTouchWatchpointSet(moduleEnvironment, state, exp.field, exportedValue, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
170         RELEASE_ASSERT(putResult);
171     }
172
173     bool hasStart = !!moduleInformation.startFunctionIndexSpace;
174     if (hasStart) {
175         auto startFunctionIndexSpace = moduleInformation.startFunctionIndexSpace.value_or(0);
176         Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(startFunctionIndexSpace);
177         const Wasm::Signature* signature = Wasm::SignatureInformation::get(&vm, signatureIndex);
178         // The start function must not take any arguments or return anything. This is enforced by the parser.
179         ASSERT(!signature->argumentCount());
180         ASSERT(signature->returnType() == Wasm::Void);
181         if (startFunctionIndexSpace < module->functionImportCount()) {
182             JSCell* startFunction = instance->importFunction(startFunctionIndexSpace)->get();
183             m_startFunction.set(vm, this, startFunction);
184         } else {
185             JSWebAssemblyCallee* jsEntrypointCallee = module->jsEntrypointCalleeFromFunctionIndexSpace(startFunctionIndexSpace);
186             JSWebAssemblyCallee* wasmEntrypointCallee = module->wasmEntrypointCalleeFromFunctionIndexSpace(startFunctionIndexSpace);
187             WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature->argumentCount(), "start", instance, jsEntrypointCallee, wasmEntrypointCallee, signatureIndex);
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 M, typename N, typename ...Args>
198 NEVER_INLINE static JSValue dataSegmentFail(ExecState* state, VM& vm, Scope& scope, M memorySize, N segmentSize, N offset, Args... args)
199 {
200     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...)));
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 throwException(state, scope, createJSWebAssemblyLinkError(state, vm, 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->functionImportCount()) {
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::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(functionIndex);
240                 const Wasm::Signature* signature = Wasm::SignatureInformation::get(&vm, signatureIndex);
241                 // FIXME: Say we export local function "foo" at funciton index 0.
242                 // What if we also set it to the table an Element w/ index 0.
243                 // Does (new Instance(...)).exports.foo === table.get(0)?
244                 // https://bugs.webkit.org/show_bug.cgi?id=165825
245                 WebAssemblyFunction* function = WebAssemblyFunction::create(
246                     vm, m_instance->globalObject(), signature->argumentCount(), String(), m_instance.get(), jsEntrypointCallee, wasmEntrypointCallee, signatureIndex);
247
248                 table->setFunction(vm, tableIndex, function);
249                 ++tableIndex;
250             }
251         }
252     }
253
254     {
255         const Vector<Wasm::Segment::Ptr>& data = m_instance->module()->moduleInformation().data;
256         JSWebAssemblyMemory* jsMemory = m_instance->memory();
257         if (!data.isEmpty()) {
258             uint8_t* memory = reinterpret_cast<uint8_t*>(jsMemory->memory()->memory());
259             uint64_t sizeInBytes = jsMemory->memory()->size();
260             for (auto& segment : data) {
261                 if (segment->sizeInBytes) {
262                     uint32_t offset;
263                     if (segment->offset.isGlobalImport())
264                         offset = static_cast<uint32_t>(m_instance->loadI32Global(segment->offset.globalImportIndex()));
265                     else
266                         offset = segment->offset.constValue();
267
268                     if (UNLIKELY(sizeInBytes < segment->sizeInBytes))
269                         return dataSegmentFail(state, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ASCIILiteral(", segment is too big"));
270                     if (UNLIKELY(offset > sizeInBytes - segment->sizeInBytes))
271                         return dataSegmentFail(state, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ASCIILiteral(", segment writes outside of memory"));
272                     RELEASE_ASSERT(memory);
273                     memcpy(memory + offset, &segment->byte(0), segment->sizeInBytes);
274                 }
275             }
276         }
277     }
278
279     if (JSCell* startFunction = m_startFunction.get()) {
280         CallData callData;
281         CallType callType = JSC::getCallData(startFunction, callData);
282         call(state, startFunction, callType, callData, jsUndefined(), state->emptyList());
283         RETURN_IF_EXCEPTION(scope, { });
284     }
285
286     return jsUndefined();
287 }
288
289 } // namespace JSC
290
291 #endif // ENABLE(WEBASSEMBLY)