Unreviewed, rolling out r235107.
[WebKit-https.git] / Source / JavaScriptCore / wasm / js / WebAssemblyPrototype.cpp
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
54 using JSC::Wasm::Plan;
55 using JSC::Wasm::BBQPlan;
56
57 namespace JSC {
58 static EncodedJSValue JSC_HOST_CALL webAssemblyCompileFunc(ExecState*);
59 static EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateFunc(ExecState*);
60 static EncodedJSValue JSC_HOST_CALL webAssemblyValidateFunc(ExecState*);
61 }
62
63 #include "WebAssemblyPrototype.lut.h"
64
65 namespace JSC {
66
67 const 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
77 static 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
86 static 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(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
112 static 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::create(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
134 enum class Resolve { WithInstance, WithModuleRecord, WithModuleAndInstance };
135 static 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
157 void 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
165 static 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(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
187 static 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(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
219 JSValue 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
226 static 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(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
250 void 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
258 static 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::create(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
288 static 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, base, byteSize, 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()));
301 }
302
303 EncodedJSValue 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::create(exec, globalObject);
310
311     Vector<Strong<JSCell>> dependencies;
312     dependencies.append(Strong<JSCell>(vm, globalObject));
313     vm.promiseDeferredTimer->addPendingPromise(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
327 EncodedJSValue 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::create(exec, globalObject);
334
335     RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
336     {
337         auto catchScope = DECLARE_CATCH_SCOPE(vm);
338
339         JSValue importArgument = exec->argument(1);
340         JSObject* importObject = importArgument.getObject();
341         if (UNLIKELY(!importArgument.isUndefined() && !importObject)) {
342             promise->reject(exec, createTypeError(exec,
343                 "second argument to WebAssembly.instantiateStreaming must be undefined or an Object"_s, defaultSourceAppender, runtimeTypeForValue(vm, importArgument)));
344             CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise->promise()));
345         } else {
346             if (globalObject->globalObjectMethodTable()->instantiateStreaming) {
347                 Vector<Strong<JSCell>> dependencies;
348                 dependencies.append(Strong<JSCell>(vm, globalObject));
349                 dependencies.append(Strong<JSCell>(vm, importObject));
350                 vm.promiseDeferredTimer->addPendingPromise(promise, WTFMove(dependencies));
351
352                 // 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.
353                 globalObject->globalObjectMethodTable()->instantiateStreaming(globalObject, exec, promise, exec->argument(0), importObject);
354             } else {
355                 // InstantiateStreaming is not supported in jsc, only in browser environment.
356                 ASSERT_NOT_REACHED();
357             }
358         }
359         CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise->promise()));
360
361         return JSValue::encode(promise->promise());
362     }
363 }
364
365 WebAssemblyPrototype* WebAssemblyPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
366 {
367     auto* object = new (NotNull, allocateCell<WebAssemblyPrototype>(vm.heap)) WebAssemblyPrototype(vm, structure);
368     object->finishCreation(vm, globalObject);
369     return object;
370 }
371
372 Structure* WebAssemblyPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
373 {
374     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
375 }
376
377 void WebAssemblyPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
378 {
379     Base::finishCreation(vm);
380
381     if (Options::useWebAssemblyStreamingApi()) {
382         JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("compileStreaming", webAssemblyPrototypeCompileStreamingCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
383         JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("instantiateStreaming", webAssemblyPrototypeInstantiateStreamingCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
384     }
385 }
386
387 WebAssemblyPrototype::WebAssemblyPrototype(VM& vm, Structure* structure)
388     : Base(vm, structure)
389 {
390 }
391
392 } // namespace JSC
393
394 #endif // ENABLE(WEBASSEMBLY)