1/*
2 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
3 * Copyright (C) 2018 Oleksandr Skachkov <gskachkov@gmail.com>.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "WebAssemblyPrototype.h"
29
30#if ENABLE(WEBASSEMBLY)
31
32#include "CatchScope.h"
33#include "Exception.h"
34#include "FunctionPrototype.h"
35#include "JSCBuiltins.h"
36#include "JSCInlines.h"
37#include "JSModuleNamespaceObject.h"
38#include "JSPromiseDeferred.h"
39#include "JSToWasm.h"
40#include "JSWebAssemblyHelpers.h"
41#include "JSWebAssemblyInstance.h"
42#include "JSWebAssemblyModule.h"
43#include "ObjectConstructor.h"
44#include "Options.h"
45#include "PromiseDeferredTimer.h"
46#include "StrongInlines.h"
47#include "ThrowScope.h"
48#include "WasmBBQPlan.h"
49#include "WasmToJS.h"
50#include "WasmWorklist.h"
51#include "WebAssemblyInstanceConstructor.h"
52#include "WebAssemblyModuleConstructor.h"
53
54using JSC::Wasm::Plan;
55using JSC::Wasm::BBQPlan;
56
57namespace JSC {
58static EncodedJSValue JSC_HOST_CALL webAssemblyCompileFunc(ExecState*);
59static EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateFunc(ExecState*);
60static EncodedJSValue JSC_HOST_CALL webAssemblyValidateFunc(ExecState*);
61}
62
63#include "WebAssemblyPrototype.lut.h"
64
65namespace JSC {
66
67const ClassInfo WebAssemblyPrototype::s_info = { "WebAssembly", &Base::s_info, &prototypeTableWebAssembly, nullptr, CREATE_METHOD_TABLE(WebAssemblyPrototype) };
68
69/* Source for WebAssemblyPrototype.lut.h
70 @begin prototypeTableWebAssembly
71 compile webAssemblyCompileFunc DontEnum|Function 1
72 instantiate webAssemblyInstantiateFunc DontEnum|Function 1
73 validate webAssemblyValidateFunc DontEnum|Function 1
74 @end
75 */
76
77static void reject(ExecState* exec, CatchScope& catchScope, JSPromiseDeferred* promise)
78{
79 Exception* exception = catchScope.exception();
80 ASSERT(exception);
81 catchScope.clearException();
82 promise->reject(exec, exception->value());
83 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, void());
84}
85
86static void webAssemblyModuleValidateAsyncInternal(ExecState* exec, JSPromiseDeferred* promise, Vector<uint8_t>&& source)
87{
88 VM& vm = exec->vm();
89 auto* globalObject = exec->lexicalGlobalObject();
90
91 Vector<Strong<JSCell>> dependencies;
92 dependencies.append(Strong<JSCell>(vm, globalObject));
93
94 vm.promiseDeferredTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
95
96 Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, globalObject, &vm] (Wasm::Module::ValidationResult&& result) mutable {
97 vm.promiseDeferredTimer->scheduleWorkSoon(promise, [promise, globalObject, result = WTFMove(result), &vm] () mutable {
98 auto scope = DECLARE_CATCH_SCOPE(vm);
99 ExecState* exec = globalObject->globalExec();
100 JSValue module = JSWebAssemblyModule::createStub(vm, exec, globalObject->webAssemblyModuleStructure(), WTFMove(result));
101 if (UNLIKELY(scope.exception())) {
102 reject(exec, scope, promise);
103 return;
104 }
105
106 promise->resolve(exec, module);
107 CLEAR_AND_RETURN_IF_EXCEPTION(scope, void());
108 });
109 }));
110}
111
112static EncodedJSValue JSC_HOST_CALL webAssemblyCompileFunc(ExecState* exec)
113{
114 VM& vm = exec->vm();
115 auto throwScope = DECLARE_THROW_SCOPE(vm);
116 auto* globalObject = exec->lexicalGlobalObject();
117
118 JSPromiseDeferred* promise = JSPromiseDeferred::tryCreate(exec, globalObject);
119 RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
120
121 {
122 auto catchScope = DECLARE_CATCH_SCOPE(vm);
123 Vector<uint8_t> source = createSourceBufferFromValue(vm, exec, exec->argument(0));
124
125 if (UNLIKELY(catchScope.exception()))
126 reject(exec, catchScope, promise);
127 else
128 webAssemblyModuleValidateAsyncInternal(exec, promise, WTFMove(source));
129
130 return JSValue::encode(promise->promise());
131 }
132}
133
134enum class Resolve { WithInstance, WithModuleRecord, WithModuleAndInstance };
135static void resolve(VM& vm, ExecState* exec, JSPromiseDeferred* promise, JSWebAssemblyInstance* instance, JSWebAssemblyModule* module, JSObject* importObject, Ref<Wasm::CodeBlock>&& codeBlock, Resolve resolveKind, Wasm::CreationMode creationMode)
136{
137 auto scope = DECLARE_CATCH_SCOPE(vm);
138 instance->finalizeCreation(vm, exec, WTFMove(codeBlock), importObject, creationMode);
139 RETURN_IF_EXCEPTION(scope, reject(exec, scope, promise));
140
141 if (resolveKind == Resolve::WithInstance)
142 promise->resolve(exec, instance);
143 else if (resolveKind == Resolve::WithModuleRecord) {
144 auto* moduleRecord = instance->moduleNamespaceObject()->moduleRecord();
145 if (Options::dumpModuleRecord())
146 moduleRecord->dump();
147 promise->resolve(exec, moduleRecord);
148 } else {
149 JSObject* result = constructEmptyObject(exec);
150 result->putDirect(vm, Identifier::fromString(&vm, "module"_s), module);
151 result->putDirect(vm, Identifier::fromString(&vm, "instance"_s), instance);
152 promise->resolve(exec, result);
153 }
154 CLEAR_AND_RETURN_IF_EXCEPTION(scope, void());
155}
156
157void WebAssemblyPrototype::webAssemblyModuleValidateAsync(ExecState* exec, JSPromiseDeferred* promise, Vector<uint8_t>&& source)
158{
159 VM& vm = exec->vm();
160 auto catchScope = DECLARE_CATCH_SCOPE(vm);
161 webAssemblyModuleValidateAsyncInternal(exec, promise, WTFMove(source));
162 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, void());
163}
164
165static void instantiate(VM& vm, ExecState* exec, JSPromiseDeferred* promise, JSWebAssemblyModule* module, JSObject* importObject, const Identifier& moduleKey, Resolve resolveKind, Wasm::CreationMode creationMode)
166{
167 auto scope = DECLARE_CATCH_SCOPE(vm);
168 // In order to avoid potentially recompiling a module. We first gather all the import/memory information prior to compiling code.
169 JSWebAssemblyInstance* instance = JSWebAssemblyInstance::create(vm, exec, moduleKey, module, importObject, exec->lexicalGlobalObject()->webAssemblyInstanceStructure(), Ref<Wasm::Module>(module->module()), creationMode);
170 RETURN_IF_EXCEPTION(scope, reject(exec, scope, promise));
171
172 Vector<Strong<JSCell>> dependencies;
173 // The instance keeps the module alive.
174 dependencies.append(Strong<JSCell>(vm, instance));
175 dependencies.append(Strong<JSCell>(vm, importObject));
176 vm.promiseDeferredTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
177 // Note: This completion task may or may not get called immediately.
178 module->module().compileAsync(&vm.wasmContext, instance->memoryMode(), createSharedTask<Wasm::CodeBlock::CallbackType>([promise, instance, module, importObject, resolveKind, creationMode, &vm] (Ref<Wasm::CodeBlock>&& refCodeBlock) mutable {
179 RefPtr<Wasm::CodeBlock> codeBlock = WTFMove(refCodeBlock);
180 vm.promiseDeferredTimer->scheduleWorkSoon(promise, [promise, instance, module, importObject, resolveKind, creationMode, &vm, codeBlock = WTFMove(codeBlock)] () mutable {
181 ExecState* exec = instance->globalObject(vm)->globalExec();
182 resolve(vm, exec, promise, instance, module, importObject, codeBlock.releaseNonNull(), resolveKind, creationMode);
183 });
184 }), &Wasm::createJSToWasmWrapper, &Wasm::wasmToJSException);
185}
186
187static void compileAndInstantiate(VM& vm, ExecState* exec, JSPromiseDeferred* promise, const Identifier& moduleKey, JSValue buffer, JSObject* importObject, Resolve resolveKind, Wasm::CreationMode creationMode)
188{
189 auto scope = DECLARE_CATCH_SCOPE(vm);
190
191 auto* globalObject = exec->lexicalGlobalObject();
192
193 JSCell* moduleKeyCell = identifierToJSValue(vm, moduleKey).asCell();
194 Vector<Strong<JSCell>> dependencies;
195 dependencies.append(Strong<JSCell>(vm, importObject));
196 dependencies.append(Strong<JSCell>(vm, moduleKeyCell));
197 vm.promiseDeferredTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
198
199 Vector<uint8_t> source = createSourceBufferFromValue(vm, exec, buffer);
200 RETURN_IF_EXCEPTION(scope, reject(exec, scope, promise));
201
202 Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, importObject, moduleKeyCell, globalObject, resolveKind, creationMode, &vm] (Wasm::Module::ValidationResult&& result) mutable {
203 vm.promiseDeferredTimer->scheduleWorkSoon(promise, [promise, importObject, moduleKeyCell, globalObject, result = WTFMove(result), resolveKind, creationMode, &vm] () mutable {
204 auto scope = DECLARE_CATCH_SCOPE(vm);
205 ExecState* exec = globalObject->globalExec();
206 JSWebAssemblyModule* module = JSWebAssemblyModule::createStub(vm, exec, globalObject->webAssemblyModuleStructure(), WTFMove(result));
207 if (UNLIKELY(scope.exception()))
208 return reject(exec, scope, promise);
209
210 const Identifier moduleKey = JSValue(moduleKeyCell).toPropertyKey(exec);
211 if (UNLIKELY(scope.exception()))
212 return reject(exec, scope, promise);
213
214 instantiate(vm, exec, promise, module, importObject, moduleKey, resolveKind, creationMode);
215 });
216 }));
217}
218
219JSValue WebAssemblyPrototype::instantiate(ExecState* exec, JSPromiseDeferred* promise, const Identifier& moduleKey, JSValue argument)
220{
221 VM& vm = exec->vm();
222 compileAndInstantiate(vm, exec, promise, moduleKey, argument, nullptr, Resolve::WithModuleRecord, Wasm::CreationMode::FromModuleLoader);
223 return promise->promise();
224}
225
226static void webAssemblyModuleInstantinateAsyncInternal(ExecState* exec, JSPromiseDeferred* promise, Vector<uint8_t>&& source, JSObject* importObject)
227{
228 auto* globalObject = exec->lexicalGlobalObject();
229 VM& vm = exec->vm();
230
231 Vector<Strong<JSCell>> dependencies;
232 dependencies.append(Strong<JSCell>(vm, importObject));
233 dependencies.append(Strong<JSCell>(vm, globalObject));
234 vm.promiseDeferredTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
235
236 Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, importObject, globalObject, &vm] (Wasm::Module::ValidationResult&& result) mutable {
237 vm.promiseDeferredTimer->scheduleWorkSoon(promise, [promise, importObject, globalObject, result = WTFMove(result), &vm] () mutable {
238 auto scope = DECLARE_CATCH_SCOPE(vm);
239 ExecState* exec = globalObject->globalExec();
240 JSWebAssemblyModule* module = JSWebAssemblyModule::createStub(vm, exec, globalObject->webAssemblyModuleStructure(), WTFMove(result));
241 if (UNLIKELY(scope.exception()))
242 return reject(exec, scope, promise);
243
244 instantiate(vm, exec, promise, module, importObject, JSWebAssemblyInstance::createPrivateModuleKey(), Resolve::WithModuleAndInstance, Wasm::CreationMode::FromJS);
245 CLEAR_AND_RETURN_IF_EXCEPTION(scope, reject(exec, scope, promise));
246 });
247 }));
248}
249
250void WebAssemblyPrototype::webAssemblyModuleInstantinateAsync(ExecState* exec, JSPromiseDeferred* promise, Vector<uint8_t>&& source, JSObject* importedObject)
251{
252 VM& vm = exec->vm();
253 auto catchScope = DECLARE_CATCH_SCOPE(vm);
254 webAssemblyModuleInstantinateAsyncInternal(exec, promise, WTFMove(source), importedObject);
255 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, void());
256}
257
258static EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateFunc(ExecState* exec)
259{
260 VM& vm = exec->vm();
261 auto throwScope = DECLARE_THROW_SCOPE(vm);
262 auto* globalObject = exec->lexicalGlobalObject();
263
264 JSPromiseDeferred* promise = JSPromiseDeferred::tryCreate(exec, globalObject);
265 RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
266
267 {
268 auto catchScope = DECLARE_CATCH_SCOPE(vm);
269
270 JSValue importArgument = exec->argument(1);
271 JSObject* importObject = importArgument.getObject();
272 if (UNLIKELY(!importArgument.isUndefined() && !importObject)) {
273 promise->reject(exec, createTypeError(exec,
274 "second argument to WebAssembly.instantiate must be undefined or an Object"_s, defaultSourceAppender, runtimeTypeForValue(vm, importArgument)));
275 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise->promise()));
276 } else {
277 JSValue firstArgument = exec->argument(0);
278 if (auto* module = jsDynamicCast<JSWebAssemblyModule*>(vm, firstArgument))
279 instantiate(vm, exec, promise, module, importObject, JSWebAssemblyInstance::createPrivateModuleKey(), Resolve::WithInstance, Wasm::CreationMode::FromJS);
280 else
281 compileAndInstantiate(vm, exec, promise, JSWebAssemblyInstance::createPrivateModuleKey(), firstArgument, importObject, Resolve::WithModuleAndInstance, Wasm::CreationMode::FromJS);
282 }
283
284 return JSValue::encode(promise->promise());
285 }
286}
287
288static EncodedJSValue JSC_HOST_CALL webAssemblyValidateFunc(ExecState* exec)
289{
290 VM& vm = exec->vm();
291 auto scope = DECLARE_THROW_SCOPE(vm);
292
293 const uint8_t* base;
294 size_t byteSize;
295 std::tie(base, byteSize) = getWasmBufferFromValue(exec, exec->argument(0));
296 RETURN_IF_EXCEPTION(scope, encodedJSValue());
297 BBQPlan plan(&vm.wasmContext, BBQPlan::Validation, Plan::dontFinalize());
298 // FIXME: We might want to throw an OOM exception here if we detect that something will OOM.
299 // https://bugs.webkit.org/show_bug.cgi?id=166015
300 return JSValue::encode(jsBoolean(plan.parseAndValidateModule(base, byteSize)));
301}
302
303EncodedJSValue JSC_HOST_CALL webAssemblyCompileStreamingInternal(ExecState* exec)
304{
305 VM& vm = exec->vm();
306 auto* globalObject = exec->lexicalGlobalObject();
307 auto catchScope = DECLARE_CATCH_SCOPE(vm);
308
309 JSPromiseDeferred* promise = JSPromiseDeferred::tryCreate(exec, globalObject);
310
311 Vector<Strong<JSCell>> dependencies;
312 dependencies.append(Strong<JSCell>(vm, globalObject));
313 vm.promiseDeferredTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
314
315 if (globalObject->globalObjectMethodTable()->compileStreaming)
316 globalObject->globalObjectMethodTable()->compileStreaming(globalObject, exec, promise, exec->argument(0));
317 else {
318 // CompileStreaming is not supported in jsc, only in browser environment
319 ASSERT_NOT_REACHED();
320 }
321
322 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise->promise()));
323
324 return JSValue::encode(promise->promise());
325}
326
327EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateStreamingInternal(ExecState* exec)
328{
329 VM& vm = exec->vm();
330 auto throwScope = DECLARE_THROW_SCOPE(vm);
331 auto* globalObject = exec->lexicalGlobalObject();
332
333 JSPromiseDeferred* promise = JSPromiseDeferred::tryCreate(exec, globalObject);
334 RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
335 {
336 auto catchScope = DECLARE_CATCH_SCOPE(vm);
337
338 JSValue importArgument = exec->argument(1);
339 JSObject* importObject = importArgument.getObject();
340 if (UNLIKELY(!importArgument.isUndefined() && !importObject)) {
341 promise->reject(exec, createTypeError(exec,
342 "second argument to WebAssembly.instantiateStreaming must be undefined or an Object"_s, defaultSourceAppender, runtimeTypeForValue(vm, importArgument)));
343 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise->promise()));
344 } else {
345 if (globalObject->globalObjectMethodTable()->instantiateStreaming) {
346 Vector<Strong<JSCell>> dependencies;
347 dependencies.append(Strong<JSCell>(vm, globalObject));
348 dependencies.append(Strong<JSCell>(vm, importObject));
349 vm.promiseDeferredTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
350
351 // FIXME: <http://webkit.org/b/184888> if there's an importObject and it contains a Memory, then we can compile the module with the right memory type (fast or not) by looking at the memory's type.
352 globalObject->globalObjectMethodTable()->instantiateStreaming(globalObject, exec, promise, exec->argument(0), importObject);
353 } else {
354 // InstantiateStreaming is not supported in jsc, only in browser environment.
355 ASSERT_NOT_REACHED();
356 }
357 }
358 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise->promise()));
359
360 return JSValue::encode(promise->promise());
361 }
362}
363
364WebAssemblyPrototype* WebAssemblyPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
365{
366 auto* object = new (NotNull, allocateCell<WebAssemblyPrototype>(vm.heap)) WebAssemblyPrototype(vm, structure);
367 object->finishCreation(vm, globalObject);
368 return object;
369}
370
371Structure* WebAssemblyPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
372{
373 return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
374}
375
376void WebAssemblyPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
377{
378 Base::finishCreation(vm);
379
380 if (Options::useWebAssemblyStreamingApi()) {
381 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("compileStreaming", webAssemblyPrototypeCompileStreamingCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
382 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("instantiateStreaming", webAssemblyPrototypeInstantiateStreamingCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
383 }
384}
385
386WebAssemblyPrototype::WebAssemblyPrototype(VM& vm, Structure* structure)
387 : Base(vm, structure)
388{
389}
390
391} // namespace JSC
392
393#endif // ENABLE(WEBASSEMBLY)
394