Skip to content

Commit 4f51c89

Browse files
committed
[JSC] Fix WebAssembly.compileStreaming and .instantiateStreaming to accept compileOptions
https://bugs.webkit.org/show_bug.cgi?id=308136 rdar://170989896 Reviewed by Marcus Plutowski. [Re-landing an earlier PR #59278. This iteration adds SaferCPP expectations for source files, most of them under DerivedSources, in which SaferCPP started detecting uncounted args and vars. These files are unrelated to the patch, but an added #include made SaferCPP detect issues that previously went unnoticed]: * Source/WebKit/SaferCPPExpectations/RetainPtrCtorAdoptCheckerExpectations: Added. * Source/WebKit/SaferCPPExpectations/UncountedCallArgsCheckerExpectations: * Source/WebKit/SaferCPPExpectations/UncountedLocalVarsCheckerExpectations: * Source/WebKitLegacy/SaferCPPExpectations/UncountedCallArgsCheckerExpectations: * Source/WebKitLegacy/SaferCPPExpectations/UncountedLocalVarsCheckerExpectations: This patch adds the support required by the JS string builtin proposal to the streaming compilation and instantiation APIs of WebAssembly (defined in the Web Embedding rather than the JavaScript Embedding document of the proposal). Highlights: Added support for the optional compileOptions parameter to the JS builtins and the underlying internal functions in: * Source/JavaScriptCore/builtins/WebAssembly.js * Source/JavaScriptCore/wasm/js/JSWebAssembly.cpp: Added the plumbing to carry the options from the JS builtins to the streaming compiler (function signature changes in multiple places). Added the handling of compileOptions to: * Source/JavaScriptCore/wasm/WasmStreamingCompiler.cpp: * Source/JavaScriptCore/wasm/WasmStreamingCompiler.h: Added tests: * LayoutTests/http/tests/wasm/wasm-js-string-builtins-streaming.html Canonical link: https://commits.webkit.org/308419@main
1 parent 92d46f7 commit 4f51c89

17 files changed

+353
-28
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Test that WebAssembly.compileStreaming and .instantiateStreaming can accept compile options for JS String Builtins
2+
3+
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
4+
5+
6+
PASS compileStreaming with builtins option succeeded
7+
PASS instantiateStreaming with builtins option succeeded
8+
PASS compileStreaming without builtins, import via importObject succeeded
9+
PASS instantiateStreaming without builtins, import via importObject succeeded
10+
PASS compileStreaming with invalid options threw TypeError
11+
PASS instantiateStreaming with invalid options threw TypeError
12+
PASS compileStreaming with wrong builtin signature threw CompileError
13+
PASS instantiateStreaming with wrong builtin signature threw CompileError
14+
PASS successfullyParsed is true
15+
16+
TEST COMPLETE
17+
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<!-- webkit-test-runner [ jscOptions=--useWasmJSStringBuiltins=true ] -->
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<title>WebAssembly JS String Builtins Streaming Test</title>
6+
<script src="/js-test-resources/js-test.js"></script>
7+
</head>
8+
<body>
9+
<script>
10+
description("Test that WebAssembly.compileStreaming and .instantiateStreaming can accept compile options for JS String Builtins");
11+
window.jsTestIsAsync = true;
12+
13+
if (window.testRunner)
14+
testRunner.waitUntilDone();
15+
16+
async function runTest() {
17+
/*
18+
(module
19+
(; 0000000b ;) (type (; 0 ;)
20+
(; 0000000b ;) (func
21+
(; 0000000d ;) (param i32)
22+
(; 0000000f ;) (result (ref extern)
23+
)
24+
)
25+
(; 00000011 ;) (import "wasm:js-string" "fromCharCode" (func (; 0 ;) (type 0)
26+
(param (; 0 ;) i32)
27+
(result (ref extern)
28+
))
29+
)
30+
)
31+
*/
32+
let bytes = new Int8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 1, 127, 1, 100, 111, 2, 31, 1, 14, 119, 97,
33+
115, 109, 58, 106, 115, 45, 115, 116, 114, 105, 110, 103, 12, 102, 114, 111, 109, 67, 104, 97, 114, 67, 111,
34+
100, 101, 0, 0, 3, 1, 0, 5, 4, 1, 1, 0, 0, 7, 10, 1, 6, 109, 101, 109, 111, 114, 121, 2, 0, 10,
35+
-127, -128, -128, 0, 0]);
36+
37+
// Test WebAssembly.compileStreaming with builtins option
38+
try {
39+
let response = new Response(bytes, {
40+
headers: {
41+
"Content-Type": "application/wasm"
42+
}
43+
});
44+
let module = await WebAssembly.compileStreaming(response, { builtins: ["js-string"] });
45+
await WebAssembly.instantiate(module, {}, { builtins: ["js-string"] });
46+
testPassed("compileStreaming with builtins option succeeded");
47+
} catch (e) {
48+
testFailed("compileStreaming with builtins option threw " + e.constructor.name + ": " + e.message);
49+
}
50+
51+
// Test WebAssembly.instantiateStreaming with builtins option
52+
try {
53+
let response = new Response(bytes, {
54+
headers: {
55+
"Content-Type": "application/wasm"
56+
}
57+
});
58+
await WebAssembly.instantiateStreaming(response, {}, { builtins: ["js-string"] });
59+
testPassed("instantiateStreaming with builtins option succeeded");
60+
} catch (e) {
61+
testFailed("instantiateStreaming with builtins option threw " + e.constructor.name + ": " + e.message);
62+
}
63+
64+
// Import object to satisfy the wasm:js-string import manually
65+
let importObject = {
66+
"wasm:js-string": {
67+
fromCharCode: (code) => String.fromCharCode(code)
68+
}
69+
};
70+
71+
// Test WebAssembly.compileStreaming without builtins, import satisfied via importObject
72+
try {
73+
let response = new Response(bytes, {
74+
headers: {
75+
"Content-Type": "application/wasm"
76+
}
77+
});
78+
let module = await WebAssembly.compileStreaming(response);
79+
await WebAssembly.instantiate(module, importObject);
80+
testPassed("compileStreaming without builtins, import via importObject succeeded");
81+
} catch (e) {
82+
testFailed("compileStreaming without builtins, import via importObject threw " + e.constructor.name + ": " + e.message);
83+
}
84+
85+
// Test WebAssembly.instantiateStreaming without builtins, import satisfied via importObject
86+
try {
87+
let response = new Response(bytes, {
88+
headers: {
89+
"Content-Type": "application/wasm"
90+
}
91+
});
92+
await WebAssembly.instantiateStreaming(response, importObject);
93+
testPassed("instantiateStreaming without builtins, import via importObject succeeded");
94+
} catch (e) {
95+
testFailed("instantiateStreaming without builtins, import via importObject threw " + e.constructor.name + ": " + e.message);
96+
}
97+
98+
// Test WebAssembly.compileStreaming with invalid options (number instead of object)
99+
try {
100+
let response = new Response(bytes, {
101+
headers: {
102+
"Content-Type": "application/wasm"
103+
}
104+
});
105+
await WebAssembly.compileStreaming(response, 42);
106+
testFailed("compileStreaming with invalid options should throw, but did not");
107+
} catch (e) {
108+
if (e instanceof TypeError)
109+
testPassed("compileStreaming with invalid options threw TypeError");
110+
else
111+
testFailed("compileStreaming with invalid options threw " + e.constructor.name + " instead of TypeError");
112+
}
113+
114+
// Test WebAssembly.instantiateStreaming with invalid options (number instead of object)
115+
try {
116+
let response = new Response(bytes, {
117+
headers: {
118+
"Content-Type": "application/wasm"
119+
}
120+
});
121+
await WebAssembly.instantiateStreaming(response, {}, 42);
122+
testFailed("instantiateStreaming with invalid options should throw, but did not");
123+
} catch (e) {
124+
if (e instanceof TypeError)
125+
testPassed("instantiateStreaming with invalid options threw TypeError");
126+
else
127+
testFailed("instantiateStreaming with invalid options threw " + e.constructor.name + " instead of TypeError");
128+
}
129+
130+
// Module that imports "fromCharCode" but with the wrong signature (two i32 params instead of one).
131+
// This should cause a CompileError when compiled with { builtins: ["js-string"] } because
132+
// the signature doesn't match the expected builtin signature.
133+
/*
134+
(module
135+
(type (func (param i32 i32) (result externref)))
136+
(import "wasm:js-string" "fromCharCode" (func (type 0)))
137+
)
138+
*/
139+
let wrongSignatureBytes = new Int8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60,
140+
0x02, 0x7f, 0x7f, 0x01, 0x6f, 0x02, 0x1f, 0x01, 0x0e, 0x77, 0x61, 0x73, 0x6d, 0x3a, 0x6a, 0x73, 0x2d, 0x73,
141+
0x74, 0x72, 0x69, 0x6e, 0x67, 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x72, 0x43, 0x6f, 0x64, 0x65,
142+
0x00, 0x00]);
143+
144+
// Test WebAssembly.compileStreaming with wrong builtin signature throws CompileError
145+
try {
146+
let response = new Response(wrongSignatureBytes, {
147+
headers: {
148+
"Content-Type": "application/wasm"
149+
}
150+
});
151+
await WebAssembly.compileStreaming(response, { builtins: ["js-string"] });
152+
testFailed("compileStreaming with wrong builtin signature should throw, but did not");
153+
} catch (e) {
154+
if (e instanceof WebAssembly.CompileError)
155+
testPassed("compileStreaming with wrong builtin signature threw CompileError");
156+
else
157+
testFailed("compileStreaming with wrong builtin signature threw " + e.constructor.name + " instead of CompileError");
158+
}
159+
160+
// Test WebAssembly.instantiateStreaming with wrong builtin signature throws CompileError
161+
try {
162+
let response = new Response(wrongSignatureBytes, {
163+
headers: {
164+
"Content-Type": "application/wasm"
165+
}
166+
});
167+
await WebAssembly.instantiateStreaming(response, {}, { builtins: ["js-string"] });
168+
testFailed("instantiateStreaming with wrong builtin signature should throw, but did not");
169+
} catch (e) {
170+
if (e instanceof WebAssembly.CompileError)
171+
testPassed("instantiateStreaming with wrong builtin signature threw CompileError");
172+
else
173+
testFailed("instantiateStreaming with wrong builtin signature threw " + e.constructor.name + " instead of CompileError");
174+
}
175+
176+
finishJSTest();
177+
}
178+
179+
runTest();
180+
181+
</script>
182+
</body>
183+
</html>

Source/JavaScriptCore/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,7 @@ set(JavaScriptCore_PRIVATE_FRAMEWORK_HEADERS
14891489
wasm/js/WebAssemblySuspending.h
14901490
wasm/js/JSWebAssemblyTable.h
14911491
wasm/js/WebAssemblyBuiltin.h
1492+
wasm/js/WebAssemblyCompileOptions.h
14921493
wasm/js/WebAssemblyFunction.h
14931494
wasm/js/WebAssemblyFunctionBase.h
14941495
wasm/js/WebAssemblyGCStructure.h

Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@
809809
237C93DE2E95A89A00F62455 /* WebAssemblySuspendingPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = 237C93DB2E95A85F00F62455 /* WebAssemblySuspendingPrototype.h */; };
810810
23863EEB2E5EC93E00E57879 /* WebAssemblyBuiltinTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = 23863EEA2E5EC92900E57879 /* WebAssemblyBuiltinTrampoline.h */; };
811811
2392A1302DD51DB100DD1CB9 /* WebAssemblyBuiltin.h in Headers */ = {isa = PBXBuildFile; fileRef = 2392A12A2DD51DB100DD1CB9 /* WebAssemblyBuiltin.h */; settings = {ATTRIBUTES = (Private, ); }; };
812-
2392A1312DD51DB100DD1CB9 /* WebAssemblyCompileOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2392A12C2DD51DB100DD1CB9 /* WebAssemblyCompileOptions.h */; };
812+
2392A1312DD51DB100DD1CB9 /* WebAssemblyCompileOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2392A12C2DD51DB100DD1CB9 /* WebAssemblyCompileOptions.h */; settings = {ATTRIBUTES = (Private, ); }; };
813813
23968F0E2F295B1B001AED63 /* WebAssemblySuspending.h in Headers */ = {isa = PBXBuildFile; fileRef = 23968F0C2F295B1B001AED63 /* WebAssemblySuspending.h */; };
814814
23968F0F2F295B1B001AED63 /* WebAssemblyPromising.h in Headers */ = {isa = PBXBuildFile; fileRef = 23968F0A2F295B1B001AED63 /* WebAssemblyPromising.h */; };
815815
23A356912E9790F40039C82A /* PinballCompletion.h in Headers */ = {isa = PBXBuildFile; fileRef = 23A3568E2E9790F40039C82A /* PinballCompletion.h */; settings = {ATTRIBUTES = (Private, ); }; };

Source/JavaScriptCore/builtins/WebAssembly.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,19 @@
2626
function compileStreaming(source) {
2727
"use strict";
2828

29-
return @promiseResolve(@Promise, source).@then(@webAssemblyCompileStreamingInternal);
29+
var compileOptions = @argument(1);
30+
return @promiseResolve(@Promise, source).@then((source) => {
31+
return @webAssemblyCompileStreamingInternal(source, compileOptions);
32+
});
3033
}
3134

3235
function instantiateStreaming(source) {
3336
"use strict";
3437

3538
var importObject = @argument(1);
39+
var compileOptions = @argument(2);
3640
return @promiseResolve(@Promise, source).@then((source) => {
37-
return @webAssemblyInstantiateStreamingInternal(source, importObject);
41+
return @webAssemblyInstantiateStreamingInternal(source, importObject, compileOptions);
3842
});
3943
}
4044

Source/JavaScriptCore/runtime/GlobalObjectMethodTable.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
#include <JavaScriptCore/Exception.h>
2525
#include <wtf/Forward.h>
2626

27+
#if ENABLE(WEBASSEMBLY)
28+
#include <JavaScriptCore/WebAssemblyCompileOptions.h>
29+
#endif
30+
2731
namespace JSC {
2832

2933
class Identifier;
@@ -71,8 +75,13 @@ struct GlobalObjectMethodTable {
7175
ScriptExecutionStatus (*scriptExecutionStatus)(JSGlobalObject*, JSObject* scriptExecutionOwner);
7276
void (*reportViolationForUnsafeEval)(JSGlobalObject*, const String&);
7377
String (*defaultLanguage)();
74-
JSPromise* (*compileStreaming)(JSGlobalObject*, JSValue);
75-
JSPromise* (*instantiateStreaming)(JSGlobalObject*, JSValue, JSObject*);
78+
#if ENABLE(WEBASSEMBLY)
79+
JSPromise* (*compileStreaming)(JSGlobalObject*, JSValue, std::optional<WebAssemblyCompileOptions>&&);
80+
JSPromise* (*instantiateStreaming)(JSGlobalObject*, JSValue, JSObject* importObject, std::optional<WebAssemblyCompileOptions>&&);
81+
#else
82+
void* compileStreamingPlaceholder; // placeholders to make positional initializers consistent
83+
void* instantiateStreamingPlaceholder;
84+
#endif
7685
JSGlobalObject* (*deriveShadowRealmGlobalObject)(JSGlobalObject*);
7786
String (*codeForEval)(JSGlobalObject*, JSValue);
7887
bool (*canCompileStrings)(JSGlobalObject*, CompilationType, String, const ArgList&);

Source/JavaScriptCore/tools/JSDollarVM.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,10 +2019,10 @@ class WasmStreamingCompiler : public JSDestructibleObject {
20192019
return &vm.destructibleObjectSpace();
20202020
}
20212021

2022-
WasmStreamingCompiler(VM& vm, Structure* structure, Wasm::CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject, const SourceCode& source)
2022+
WasmStreamingCompiler(VM& vm, Structure* structure, Wasm::CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject, std::optional<WebAssemblyCompileOptions>&& compileOptions, const SourceCode& source)
20232023
: Base(vm, structure)
20242024
, m_promise(promise, WriteBarrierEarlyInit)
2025-
, m_streamingCompiler(Wasm::StreamingCompiler::create(vm, compilerMode, globalObject, promise, importObject, source))
2025+
, m_streamingCompiler(Wasm::StreamingCompiler::create(vm, compilerMode, globalObject, promise, importObject, WTF::move(compileOptions), source))
20262026
{
20272027
DollarVMAssertScope assertScope;
20282028
}
@@ -2032,7 +2032,7 @@ class WasmStreamingCompiler : public JSDestructibleObject {
20322032
DollarVMAssertScope assertScope;
20332033
JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure());
20342034
Structure* structure = createStructure(vm, globalObject, jsNull());
2035-
WasmStreamingCompiler* result = new (NotNull, allocateCell<WasmStreamingCompiler>(vm)) WasmStreamingCompiler(vm, structure, compilerMode, globalObject, promise, importObject, source);
2035+
WasmStreamingCompiler* result = new (NotNull, allocateCell<WasmStreamingCompiler>(vm)) WasmStreamingCompiler(vm, structure, compilerMode, globalObject, promise, importObject, std::nullopt, source);
20362036
result->finishCreation(vm);
20372037
return result;
20382038
}

Source/JavaScriptCore/wasm/WasmStreamingCompiler.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@
3737
#include "WasmIPIntPlan.h"
3838
#include "WasmStreamingPlan.h"
3939
#include "WasmWorklist.h"
40+
#include "WebAssemblyCompileOptions.h"
4041

4142
#if ENABLE(WEBASSEMBLY)
4243

4344
namespace JSC { namespace Wasm {
4445

45-
StreamingCompiler::StreamingCompiler(VM& vm, CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject, const SourceCode& source)
46+
StreamingCompiler::StreamingCompiler(VM& vm, CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject, std::optional<WebAssemblyCompileOptions>&& compileOptions, const SourceCode& source)
4647
: m_vm(vm)
4748
, m_compilerMode(compilerMode)
49+
, m_compileOptions(WTF::move(compileOptions))
4850
, m_info(Wasm::ModuleInformation::create())
4951
, m_parser(m_info.get(), *this)
5052
, m_source(source)
@@ -67,9 +69,9 @@ StreamingCompiler::~StreamingCompiler()
6769
}
6870
}
6971

70-
Ref<StreamingCompiler> StreamingCompiler::create(VM& vm, CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject, const SourceCode& source)
72+
Ref<StreamingCompiler> StreamingCompiler::create(VM& vm, CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject, std::optional<WebAssemblyCompileOptions>&& compileOptions, const SourceCode& source)
7173
{
72-
return adoptRef(*new StreamingCompiler(vm, compilerMode, globalObject, promise, importObject, source));
74+
return adoptRef(*new StreamingCompiler(vm, compilerMode, globalObject, promise, importObject, WTF::move(compileOptions), source));
7375
}
7476

7577
bool StreamingCompiler::didReceiveFunctionData(FunctionCodeIndex functionIndex, const Wasm::FunctionData&)
@@ -143,7 +145,7 @@ void StreamingCompiler::didComplete()
143145
auto ticket = std::exchange(m_ticket, nullptr);
144146
switch (m_compilerMode) {
145147
case CompilerMode::Validation: {
146-
m_vm.deferredWorkTimer->scheduleWorkSoon(ticket, [result = WTF::move(result)](DeferredWorkTimer::Ticket ticket) mutable {
148+
m_vm.deferredWorkTimer->scheduleWorkSoon(ticket, [result = WTF::move(result), compileOptions = WTF::move(m_compileOptions)](DeferredWorkTimer::Ticket ticket) mutable {
147149
JSPromise* promise = jsCast<JSPromise*>(ticket->target());
148150
JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(ticket->dependencies()[0]);
149151
VM& vm = globalObject->vm();
@@ -155,6 +157,16 @@ void StreamingCompiler::didComplete()
155157
return;
156158
}
157159

160+
if (compileOptions) {
161+
auto errorMessage = compileOptions->validateBuiltinsAndImportedStrings(result.value());
162+
if (errorMessage.has_value()) {
163+
throwException(globalObject, scope, createJSWebAssemblyCompileError(globalObject, vm, errorMessage.value()));
164+
promise->rejectWithCaughtException(globalObject, scope);
165+
return;
166+
}
167+
result.value()->applyCompileOptions(compileOptions.value());
168+
}
169+
158170
JSWebAssemblyModule* module = JSWebAssemblyModule::create(vm, globalObject->webAssemblyModuleStructure(), WTF::move(result.value()));
159171

160172
scope.release();
@@ -165,7 +177,7 @@ void StreamingCompiler::didComplete()
165177

166178
case CompilerMode::FullCompile: {
167179
RefPtr<SourceProvider> provider = m_source.provider();
168-
m_vm.deferredWorkTimer->scheduleWorkSoon(ticket, [result = WTF::move(result), provider = WTF::move(provider)](DeferredWorkTimer::Ticket ticket) mutable {
180+
m_vm.deferredWorkTimer->scheduleWorkSoon(ticket, [result = WTF::move(result), provider = WTF::move(provider), compileOptions = WTF::move(m_compileOptions)](DeferredWorkTimer::Ticket ticket) mutable {
169181
JSPromise* promise = jsCast<JSPromise*>(ticket->target());
170182
JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(ticket->dependencies()[0]);
171183
JSObject* importObject = jsCast<JSObject*>(ticket->dependencies()[1]);
@@ -178,6 +190,16 @@ void StreamingCompiler::didComplete()
178190
return;
179191
}
180192

193+
if (compileOptions) {
194+
auto errorMessage = compileOptions->validateBuiltinsAndImportedStrings(result.value());
195+
if (errorMessage.has_value()) {
196+
throwException(globalObject, scope, createJSWebAssemblyCompileError(globalObject, vm, errorMessage.value()));
197+
promise->rejectWithCaughtException(globalObject, scope);
198+
return;
199+
}
200+
result.value()->applyCompileOptions(compileOptions.value());
201+
}
202+
181203
JSWebAssemblyModule* module = JSWebAssemblyModule::create(vm, globalObject->webAssemblyModuleStructure(), WTF::move(result.value()));
182204
JSWebAssembly::instantiateForStreaming(vm, globalObject, promise, module, importObject, WTF::move(provider));
183205
if (scope.exception()) [[unlikely]] {

0 commit comments

Comments
 (0)