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(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 | |
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::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 | |
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(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 | |
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(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 | |
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(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 | |
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::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 | |
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, 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 | |
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::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 | |
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::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 | |
364 | WebAssemblyPrototype* 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 | |
371 | Structure* WebAssemblyPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) |
372 | { |
373 | return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); |
374 | } |
375 | |
376 | void 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 | |
386 | WebAssemblyPrototype::WebAssemblyPrototype(VM& vm, Structure* structure) |
387 | : Base(vm, structure) |
388 | { |
389 | } |
390 | |
391 | } // namespace JSC |
392 | |
393 | #endif // ENABLE(WEBASSEMBLY) |
394 | |