Skip to content

Commit e40eb8a

Browse files
committed
[JSC] Reduce size of Wasm GC objects by dropping m_rtt
https://bugs.webkit.org/show_bug.cgi?id=310881 rdar://173493466 Reviewed by Keith Miller. This patch reduces Wasm GC object size by removing m_rtt. The key is that now these objects are realm-less: so Structure is unique to m_rtt. We use StructureID for type checking instead of embedded m_rtt. To keep the original m_rtt's faster checking, 1. We re-introduce cached type display in WebAssemblyGCStructure. But this time, we cache StructureID (4 bytes) instead of RTT* (8 bytes). And using StructureID comparison for most of type checks. 2. If the type depth is deeper than cached display size (6), then retrieving RTT* from Structure and uses the previous code. * JSTests/stress/wasm-gc-structureid-cast-optimization.js: Added. (uleb128): (sleb128): (encodeSection): (encodeString): (buildModule.encodeBody): (buildModule): (main): * Source/JavaScriptCore/b3/B3AbstractHeapRepository.h: * Source/JavaScriptCore/b3/B3LowerMacros.cpp: * Source/JavaScriptCore/b3/B3ReduceStrength.cpp: * Source/JavaScriptCore/b3/B3Validate.cpp: * Source/JavaScriptCore/b3/B3Value.cpp: (JSC::B3::Value::key const): * Source/JavaScriptCore/b3/B3Value.h: * Source/JavaScriptCore/b3/B3ValueKey.cpp: (JSC::B3::ValueKey::materialize const): * Source/JavaScriptCore/b3/B3ValueKey.h: * Source/JavaScriptCore/b3/B3ValueKeyInlines.h: (JSC::B3::ValueKey::ValueKey): * Source/JavaScriptCore/b3/B3WasmRefTypeCheckValue.h: * Source/JavaScriptCore/runtime/Structure.cpp: (JSC::Structure::visitChildrenImpl): * Source/JavaScriptCore/wasm/WasmBBQJIT64.cpp: (JSC::Wasm::BBQJITImpl::BBQJIT::emitAllocateGCArrayUninitialized): (JSC::Wasm::BBQJITImpl::BBQJIT::emitAllocateGCStructUninitialized): (JSC::Wasm::BBQJITImpl::BBQJIT::emitRefTestOrCast): * Source/JavaScriptCore/wasm/WasmFormat.cpp: (JSC::Wasm::validateWasmValue): * Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp: (JSC::Wasm::OMGIRGenerator::emitRefTestOrCast): (JSC::Wasm::OMGIRGenerator::allocateWasmGCObject): (JSC::Wasm::OMGIRGenerator::allocateWasmGCArrayUninitialized): * Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp: (JSC::Wasm::TypeInformation::isReferenceValueAssignable): * Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h: * Source/JavaScriptCore/wasm/js/WebAssemblyGCObjectBase.cpp: (JSC::WebAssemblyGCObjectBase::WebAssemblyGCObjectBase): * Source/JavaScriptCore/wasm/js/WebAssemblyGCObjectBase.h: (JSC::WebAssemblyGCObjectBase::rtt const): (JSC::WebAssemblyGCObjectBase::offsetOfRTT): Deleted. * Source/JavaScriptCore/wasm/js/WebAssemblyGCStructure.cpp: (JSC::WebAssemblyGCStructure::finishCreation): (JSC::WebAssemblyGCStructure::visitAdditionalChildren): * Source/JavaScriptCore/wasm/js/WebAssemblyGCStructure.h: Canonical link: https://commits.webkit.org/310119@main
1 parent f6a3781 commit e40eb8a

21 files changed

Lines changed: 554 additions & 62 deletions
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
// Tests for WasmRefTypeCheckValue with StructureID-based type checking.
2+
// Exercises B3 ReduceStrength's replaceWithNullTrapping and ValueKey encoding
3+
// for 2-child WasmRefTypeCheckValue nodes.
4+
5+
function uleb128(value) {
6+
const result = [];
7+
do {
8+
let byte = value & 0x7f;
9+
value >>>= 7;
10+
if (value !== 0) byte |= 0x80;
11+
result.push(byte);
12+
} while (value !== 0);
13+
return result;
14+
}
15+
16+
function sleb128(value) {
17+
const result = [];
18+
let more = true;
19+
while (more) {
20+
let byte = value & 0x7f;
21+
value >>= 7;
22+
if ((value === 0 && (byte & 0x40) === 0) ||
23+
(value === -1 && (byte & 0x40) !== 0)) {
24+
more = false;
25+
} else {
26+
byte |= 0x80;
27+
}
28+
result.push(byte);
29+
}
30+
return result;
31+
}
32+
33+
function encodeSection(id, contents) {
34+
return [id, ...uleb128(contents.length), ...contents];
35+
}
36+
37+
function encodeString(str) {
38+
const bytes = [];
39+
for (let i = 0; i < str.length; i++) bytes.push(str.charCodeAt(i));
40+
return [...uleb128(bytes.length), ...bytes];
41+
}
42+
43+
const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d];
44+
const WASM_VERSION = [0x01, 0x00, 0x00, 0x00];
45+
46+
const SEC_TYPE = 1;
47+
const SEC_FUNCTION = 3;
48+
const SEC_EXPORT = 7;
49+
const SEC_CODE = 10;
50+
51+
const TYPE_I32 = 0x7f;
52+
const TYPE_I64 = 0x7e;
53+
const TYPE_FUNCTYPE = 0x60;
54+
const TYPE_STRUCT = 0x5f;
55+
const TYPE_SUB = 0x50;
56+
const TYPE_SUB_FINAL = 0x4f;
57+
const TYPE_REC = 0x4e;
58+
const REF_NULL = 0x6c;
59+
const REF = 0x6b;
60+
const FIELD_MUTABLE = 0x01;
61+
62+
const GC_PREFIX = 0xfb;
63+
const GC_STRUCT_NEW = 0x00;
64+
const GC_STRUCT_GET = 0x02;
65+
const GC_REF_TEST = 0x14;
66+
const GC_REF_TEST_NULL = 0x15;
67+
const GC_REF_CAST = 0x16;
68+
const GC_REF_CAST_NULL = 0x17;
69+
70+
const OP_END = 0x0b;
71+
const OP_LOCAL_GET = 0x20;
72+
const OP_LOCAL_SET = 0x21;
73+
const OP_I32_CONST = 0x41;
74+
const OP_I64_CONST = 0x42;
75+
const OP_I32_ADD = 0x6a;
76+
const OP_IF = 0x04;
77+
const OP_ELSE = 0x05;
78+
const OP_BLOCK = 0x02;
79+
const OP_LOOP = 0x03;
80+
const OP_BR = 0x0c;
81+
const OP_BR_IF = 0x0d;
82+
const OP_I32_LT_S = 0x48;
83+
const OP_I32_EQZ = 0x45;
84+
const OP_REF_NULL = 0xd0;
85+
const OP_DROP = 0x1a;
86+
const OP_RETURN = 0x0f;
87+
const OP_REF_IS_NULL = 0xd1;
88+
const EXPORT_FUNC = 0x00;
89+
90+
function buildModule() {
91+
// Type hierarchy:
92+
// type 0: $Base = sub (open) struct { i32 }
93+
// type 1: $Mid = sub $Base (open) struct { i32, i64 }
94+
// type 2: $Leaf = sub final $Mid struct { i32, i64, i32 }
95+
// type 3: $Final = sub final struct { i32 } (unrelated final type)
96+
//
97+
// Function types:
98+
// type 4: (i32) -> (i32)
99+
// type 5: () -> (i32)
100+
// type 6: (i32) -> (i64)
101+
102+
const typeSectionBody = [
103+
0x05, // 5 entries: 1 rec group + 1 standalone struct + 3 func types
104+
105+
// Entry 1: rec group with 3 types
106+
TYPE_REC,
107+
0x03, // 3 types in rec group
108+
109+
// Type 0: $Base = sub (open) struct { i32 mutable }
110+
TYPE_SUB,
111+
0x00, // 0 supertypes
112+
TYPE_STRUCT,
113+
0x01, // 1 field
114+
TYPE_I32, FIELD_MUTABLE,
115+
116+
// Type 1: $Mid = sub $Base (open) struct { i32, i64 }
117+
TYPE_SUB,
118+
0x01, // 1 supertype
119+
...uleb128(0),
120+
TYPE_STRUCT,
121+
0x02, // 2 fields
122+
TYPE_I32, FIELD_MUTABLE,
123+
TYPE_I64, FIELD_MUTABLE,
124+
125+
// Type 2: $Leaf = sub final $Mid struct { i32, i64, i32 }
126+
TYPE_SUB_FINAL,
127+
0x01, // 1 supertype
128+
...uleb128(1),
129+
TYPE_STRUCT,
130+
0x03, // 3 fields
131+
TYPE_I32, FIELD_MUTABLE,
132+
TYPE_I64, FIELD_MUTABLE,
133+
TYPE_I32, FIELD_MUTABLE,
134+
135+
// Entry 2: $Final = sub final struct { i32 } (standalone, unrelated final type)
136+
TYPE_SUB_FINAL,
137+
0x00, // 0 supertypes
138+
TYPE_STRUCT,
139+
0x01,
140+
TYPE_I32, FIELD_MUTABLE,
141+
142+
// Entry 3: (i32) -> (i32)
143+
TYPE_FUNCTYPE, 0x01, TYPE_I32, 0x01, TYPE_I32,
144+
145+
// Entry 4: () -> (i32)
146+
TYPE_FUNCTYPE, 0x00, 0x01, TYPE_I32,
147+
148+
// Entry 5: (i32) -> (i64)
149+
TYPE_FUNCTYPE, 0x01, TYPE_I32, 0x01, TYPE_I64,
150+
];
151+
152+
const funcSectionBody = [
153+
0x07, // 7 functions
154+
0x04, // func 0: cast_to_final type (i32)->i32
155+
0x04, // func 1: cast_to_mid type (i32)->i32
156+
0x04, // func 2: test_final type (i32)->i32
157+
0x04, // func 3: test_mid type (i32)->i32
158+
0x04, // func 4: cast_to_parent type (i32)->i32
159+
0x06, // func 5: null_cast_get type (i32)->i64
160+
0x05, // func 6: loop_cast_final type ()->i32
161+
];
162+
163+
const exportSectionBody = [
164+
0x07,
165+
...encodeString("cast_to_final"), EXPORT_FUNC, 0x00,
166+
...encodeString("cast_to_mid"), EXPORT_FUNC, 0x01,
167+
...encodeString("test_final"), EXPORT_FUNC, 0x02,
168+
...encodeString("test_mid"), EXPORT_FUNC, 0x03,
169+
...encodeString("cast_to_parent"), EXPORT_FUNC, 0x04,
170+
...encodeString("null_cast_get"), EXPORT_FUNC, 0x05,
171+
...encodeString("loop_cast_final"), EXPORT_FUNC, 0x06,
172+
];
173+
174+
// func 0: cast_to_final(val: i32) -> i32
175+
// Create $Leaf, cast to $Leaf (final type => StructureID compare), get field 0
176+
const func0Body = [
177+
0x00,
178+
OP_LOCAL_GET, 0x00,
179+
OP_I64_CONST, ...sleb128(100),
180+
OP_LOCAL_GET, 0x00,
181+
GC_PREFIX, GC_STRUCT_NEW, ...uleb128(2), // struct.new $Leaf
182+
GC_PREFIX, GC_REF_CAST, ...sleb128(2), // ref.cast (ref $Leaf)
183+
GC_PREFIX, GC_STRUCT_GET, ...uleb128(2), ...uleb128(0), // struct.get $Leaf field 0
184+
OP_END,
185+
];
186+
187+
// func 1: cast_to_mid(val: i32) -> i32
188+
// Create $Leaf, cast to $Mid (non-final, shallow hierarchy), get field 0
189+
const func1Body = [
190+
0x00,
191+
OP_LOCAL_GET, 0x00,
192+
OP_I64_CONST, ...sleb128(200),
193+
OP_LOCAL_GET, 0x00,
194+
GC_PREFIX, GC_STRUCT_NEW, ...uleb128(2), // struct.new $Leaf
195+
GC_PREFIX, GC_REF_CAST, ...sleb128(1), // ref.cast (ref $Mid)
196+
GC_PREFIX, GC_STRUCT_GET, ...uleb128(1), ...uleb128(0), // struct.get $Mid field 0
197+
OP_END,
198+
];
199+
200+
// func 2: test_final(val: i32) -> i32
201+
// Create $Leaf, ref.test for $Leaf (final type)
202+
const func2Body = [
203+
0x00,
204+
OP_LOCAL_GET, 0x00,
205+
OP_I64_CONST, ...sleb128(300),
206+
OP_LOCAL_GET, 0x00,
207+
GC_PREFIX, GC_STRUCT_NEW, ...uleb128(2), // struct.new $Leaf
208+
GC_PREFIX, GC_REF_TEST, ...sleb128(2), // ref.test (ref $Leaf)
209+
OP_END,
210+
];
211+
212+
// func 3: test_mid(val: i32) -> i32
213+
// Create $Leaf, ref.test for $Mid (non-final)
214+
const func3Body = [
215+
0x00,
216+
OP_LOCAL_GET, 0x00,
217+
OP_I64_CONST, ...sleb128(400),
218+
OP_LOCAL_GET, 0x00,
219+
GC_PREFIX, GC_STRUCT_NEW, ...uleb128(2), // struct.new $Leaf
220+
GC_PREFIX, GC_REF_TEST, ...sleb128(1), // ref.test (ref $Mid)
221+
OP_END,
222+
];
223+
224+
// func 4: cast_to_parent(val: i32) -> i32
225+
// Create $Leaf, cast to $Base (subtype hierarchy), get field 0
226+
const func4Body = [
227+
0x00,
228+
OP_LOCAL_GET, 0x00,
229+
OP_I64_CONST, ...sleb128(500),
230+
OP_LOCAL_GET, 0x00,
231+
GC_PREFIX, GC_STRUCT_NEW, ...uleb128(2), // struct.new $Leaf
232+
GC_PREFIX, GC_REF_CAST, ...sleb128(0), // ref.cast (ref $Base)
233+
GC_PREFIX, GC_STRUCT_GET, ...uleb128(0), ...uleb128(0), // struct.get $Base field 0
234+
OP_END,
235+
];
236+
237+
// func 5: null_cast_get(val: i32) -> i64
238+
// ref.cast null $Leaf (allowNull) + struct.get => triggers replaceWithNullTrapping in ReduceStrength
239+
// Always creates a valid $Leaf, but uses ref.cast null variant
240+
const func5Body = [
241+
0x00, // 0 local declarations
242+
OP_LOCAL_GET, 0x00, // local.get 0 (val)
243+
OP_I64_CONST, ...sleb128(777),
244+
OP_I32_CONST, ...sleb128(888),
245+
GC_PREFIX, GC_STRUCT_NEW, ...uleb128(2), // struct.new $Leaf
246+
GC_PREFIX, GC_REF_CAST_NULL, ...sleb128(2), // ref.cast (ref null $Leaf) - allowNull
247+
GC_PREFIX, GC_STRUCT_GET, ...uleb128(2), ...uleb128(1), // struct.get $Leaf field 1
248+
OP_END,
249+
];
250+
251+
// func 6: loop_cast_final() -> i32
252+
// Loop doing ref.cast to final type to trigger OMG compilation
253+
// 2 locals: i32 counter, i32 sum
254+
const func6Body = [
255+
0x02, // 2 local declarations
256+
0x01, TYPE_I32, // 1 local of type i32 (counter)
257+
0x01, TYPE_I32, // 1 local of type i32 (sum)
258+
// counter = 0, sum = 0 (already zero-initialized)
259+
OP_BLOCK, 0x40, // block (void)
260+
OP_LOOP, 0x40, // loop (void)
261+
// Create $Leaf, cast to $Leaf (final), get field 0, add to sum
262+
OP_LOCAL_GET, 0x00, // counter
263+
OP_I64_CONST, ...sleb128(0),
264+
OP_I32_CONST, ...sleb128(0),
265+
GC_PREFIX, GC_STRUCT_NEW, ...uleb128(2), // struct.new $Leaf
266+
GC_PREFIX, GC_REF_CAST, ...sleb128(2), // ref.cast (ref $Leaf)
267+
GC_PREFIX, GC_STRUCT_GET, ...uleb128(2), ...uleb128(0),
268+
OP_LOCAL_GET, 0x01, // sum
269+
OP_I32_ADD,
270+
OP_LOCAL_SET, 0x01, // sum += result
271+
// counter++
272+
OP_LOCAL_GET, 0x00,
273+
OP_I32_CONST, ...sleb128(1),
274+
OP_I32_ADD,
275+
OP_LOCAL_SET, 0x00,
276+
// if counter < 100000, continue loop
277+
OP_LOCAL_GET, 0x00,
278+
OP_I32_CONST, ...sleb128(100000),
279+
OP_I32_LT_S,
280+
OP_BR_IF, 0x00, // br_if loop
281+
OP_END, // end loop
282+
OP_END, // end block
283+
OP_LOCAL_GET, 0x01, // return sum
284+
OP_END,
285+
];
286+
287+
function encodeBody(body) {
288+
return [...uleb128(body.length), ...body];
289+
}
290+
291+
const codeSectionBody = [
292+
0x07,
293+
...encodeBody(func0Body),
294+
...encodeBody(func1Body),
295+
...encodeBody(func2Body),
296+
...encodeBody(func3Body),
297+
...encodeBody(func4Body),
298+
...encodeBody(func5Body),
299+
...encodeBody(func6Body),
300+
];
301+
302+
const module = [
303+
...WASM_MAGIC,
304+
...WASM_VERSION,
305+
...encodeSection(SEC_TYPE, typeSectionBody),
306+
...encodeSection(SEC_FUNCTION, funcSectionBody),
307+
...encodeSection(SEC_EXPORT, exportSectionBody),
308+
...encodeSection(SEC_CODE, codeSectionBody),
309+
];
310+
311+
return new Uint8Array(module);
312+
}
313+
314+
function main() {
315+
let bytes;
316+
try {
317+
bytes = buildModule();
318+
} catch (e) {
319+
throw new Error("Failed to build module: " + e);
320+
}
321+
322+
let module, instance;
323+
try {
324+
module = new WebAssembly.Module(bytes);
325+
instance = new WebAssembly.Instance(module);
326+
} catch (e) {
327+
throw new Error("Failed to instantiate module: " + e);
328+
}
329+
330+
const exports = instance.exports;
331+
332+
// Case A: ref.cast to final type ($Leaf) — StructureID compare path
333+
for (let i = 0; i < 200000; i++) {
334+
let result = exports.cast_to_final(i);
335+
if (result !== i)
336+
throw new Error("cast_to_final(" + i + ") returned " + result + ", expected " + i);
337+
}
338+
339+
// Case B: ref.cast to non-final type ($Mid) — inlined display path
340+
for (let i = 0; i < 200000; i++) {
341+
let result = exports.cast_to_mid(i);
342+
if (result !== i)
343+
throw new Error("cast_to_mid(" + i + ") returned " + result + ", expected " + i);
344+
}
345+
346+
// Case C: ref.test for final type ($Leaf)
347+
for (let i = 0; i < 200000; i++) {
348+
let result = exports.test_final(i);
349+
if (result !== 1)
350+
throw new Error("test_final(" + i + ") returned " + result + ", expected 1");
351+
}
352+
353+
// Case D: ref.test for non-final type ($Mid)
354+
for (let i = 0; i < 200000; i++) {
355+
let result = exports.test_mid(i);
356+
if (result !== 1)
357+
throw new Error("test_mid(" + i + ") returned " + result + ", expected 1");
358+
}
359+
360+
// Case E: cast to parent type ($Base) from $Leaf
361+
for (let i = 0; i < 200000; i++) {
362+
let result = exports.cast_to_parent(i);
363+
if (result !== i)
364+
throw new Error("cast_to_parent(" + i + ") returned " + result + ", expected " + i);
365+
}
366+
367+
// Case F: null handling — ref.cast null + struct.get triggers replaceWithNullTrapping
368+
// The ReduceStrength optimization converts ref.cast(allowNull) + struct.get to ref.cast(!allowNull) + struct.get
369+
for (let i = 0; i < 200000; i++) {
370+
let result = exports.null_cast_get(i);
371+
if (result !== 777n)
372+
throw new Error("null_cast_get(" + i + ") returned " + result + ", expected 777n");
373+
}
374+
375+
// Case G: loop with repeated casts to trigger OMG
376+
for (let i = 0; i < 10; i++) {
377+
exports.loop_cast_final();
378+
}
379+
}
380+
381+
main();

‎Source/JavaScriptCore/b3/B3AbstractHeapRepository.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ namespace JSC::B3 {
204204
macro(WebAssemblyFunctionBase_entrypointLoadLocation, WebAssemblyFunctionBase::offsetOfEntrypointLoadLocation(), Mutability::Immutable) \
205205
macro(WebAssemblyFunctionBase_rtt, WebAssemblyFunctionBase::offsetOfRTT(), Mutability::Immutable) \
206206
macro(WebAssemblyFunctionBase_targetInstance, WebAssemblyFunctionBase::offsetOfTargetInstance(), Mutability::Immutable) \
207-
macro(WebAssemblyGCObjectBase_rtt, WebAssemblyGCObjectBase::offsetOfRTT(), Mutability::Immutable) \
208207
macro(WebAssemblyGCStructure_rtt, WebAssemblyGCStructure::offsetOfRTT(), Mutability::Immutable) \
209208
macro(WebAssemblyModuleRecord_exportsObject, WebAssemblyModuleRecord::offsetOfExportsObject(), Mutability::Mutable) \
210209
macro(Symbol_symbolImpl, Symbol::offsetOfSymbolImpl(), Mutability::Immutable) \
@@ -230,6 +229,7 @@ namespace JSC::B3 {
230229
macro(HasOwnPropertyCache, 0, sizeof(HasOwnPropertyCache::Entry)) \
231230
macro(SmallIntCache, 0, sizeof(NumericStrings::StringWithJSString)) \
232231
macro(WasmRTT_data, Wasm::RTT::offsetOfData(), sizeof(RefPtr<const Wasm::RTT>)) \
232+
macro(WebAssemblyGCStructure_inlinedDisplay, WebAssemblyGCStructure::offsetOfInlinedDisplay(), sizeof(WriteBarrierStructureID)) \
233233

234234
#define FOR_EACH_NUMBERED_ABSTRACT_HEAP(macro) \
235235
macro(properties) \

0 commit comments

Comments
 (0)