Skip to content

Commit f24380d

Browse files
committed
[Wasm-GC] Add struct.new_canon_default
https://bugs.webkit.org/show_bug.cgi?id=249197 Reviewed by Justin Michaud. Adds support for the struct.new_canon_default instruction, which initializes struct fields to the default (if no non-defaultable fields exist). This instruction is currently supported with two opcodes 0xfb02 and 0xfb08. Once opcode refactoring in the GC proposal is done, we can consolidate these. * JSTests/wasm/gc/structs.js: (testStructNewDefault): (testStructGet): Deleted. * JSTests/wasm/wasm.json: * Source/JavaScriptCore/bytecode/BytecodeList.rb: * Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h: (JSC::Wasm::ExpressionType>::addStructNewDefault): * Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp: (JSC::Wasm::B3IRGenerator::emitStructSet): (JSC::Wasm::B3IRGenerator::addStructNew): (JSC::Wasm::B3IRGenerator::addStructNewDefault): (JSC::Wasm::B3IRGenerator::addStructSet): * Source/JavaScriptCore/wasm/WasmFunctionParser.h: (JSC::Wasm::FunctionParser<Context>::parseExpression): (JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression): * Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp: (JSC::Wasm::LLIntGenerator::addArrayNew): (JSC::Wasm::LLIntGenerator::addArrayNewDefault): (JSC::Wasm::LLIntGenerator::addStructNew): (JSC::Wasm::LLIntGenerator::addStructNewDefault): * Source/JavaScriptCore/wasm/WasmLLIntGenerator.h: * Source/JavaScriptCore/wasm/WasmOperations.cpp: (JSC::Wasm::JSC_DEFINE_JIT_OPERATION): * Source/JavaScriptCore/wasm/WasmOperations.h: * Source/JavaScriptCore/wasm/WasmSlowPaths.cpp: (JSC::LLInt::WASM_SLOW_PATH_DECL): * Source/JavaScriptCore/wasm/wasm.json: Canonical link: https://commits.webkit.org/258689@main
1 parent 78f050a commit f24380d

File tree

12 files changed

+312
-46
lines changed

12 files changed

+312
-46
lines changed

JSTests/wasm/gc/structs.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,74 @@ function testStructNew() {
272272
`);
273273
}
274274

275+
function testStructNewDefault() {
276+
instantiate(`
277+
(module
278+
(type $Empty (struct))
279+
(func (export "main")
280+
(drop
281+
(struct.new_canon_default $Empty)
282+
)
283+
)
284+
)
285+
`).exports.main();
286+
287+
instantiate(`
288+
(module
289+
(type $Point (struct (field $x i32) (field $y i32)))
290+
(func (export "main")
291+
(drop
292+
(struct.new_canon_default $Point)
293+
)
294+
)
295+
)
296+
`).exports.main();
297+
298+
assert.throws(
299+
() => compile(`
300+
(module
301+
(type $Point (struct (field $x (ref func))))
302+
(func (export "main")
303+
(drop
304+
(struct.new_canon_default $Point)
305+
)
306+
)
307+
)
308+
`),
309+
WebAssembly.CompileError,
310+
"WebAssembly.Module doesn't parse at byte 4: struct.new_default 0 requires all fields to be defaultable, but field 0 has type Ref, in function at index 0"
311+
)
312+
313+
assert.throws(
314+
() => compile(`
315+
(module
316+
(type $Point (struct (field $x i32) (field $y i32)))
317+
(func (export "main")
318+
(drop
319+
(struct.new_canon_default 3)
320+
)
321+
)
322+
)
323+
`),
324+
WebAssembly.CompileError,
325+
"WebAssembly.Module doesn't validate: struct.new_default index 3 is out of bound, in function at index 0 (evaluating 'new WebAssembly.Module(binary)')"
326+
)
327+
328+
assert.throws(
329+
() => compile(`
330+
(module
331+
(type $Point (struct (field $x i32) (field $y i32)))
332+
(func (export "main")
333+
unreachable
334+
struct.new_canon_default 2
335+
)
336+
)
337+
`),
338+
WebAssembly.CompileError,
339+
"WebAssembly.Module doesn't validate: struct.new_default index 2 is out of bound, in function at index 0 (evaluating 'new WebAssembly.Module(binary)')"
340+
)
341+
}
342+
275343
function testStructGet() {
276344
{
277345
/*
@@ -304,6 +372,20 @@ function testStructGet() {
304372
assert.eq(main(), 37);
305373
}
306374

375+
{
376+
let main = instantiate(`
377+
(module
378+
(type $Point (struct (field $x i32)))
379+
(func (export "main") (result i32)
380+
(struct.get $Point $x
381+
(struct.new_canon_default $Point)
382+
)
383+
)
384+
)
385+
`).exports.main;
386+
assert.eq(main(), 0);
387+
}
388+
307389
{
308390
/*
309391
* Point(f32)
@@ -321,6 +403,20 @@ function testStructGet() {
321403
assert.eq(instance.exports.main(), 37);
322404
}
323405

406+
{
407+
let main = instantiate(`
408+
(module
409+
(type $Point (struct (field $x f32)))
410+
(func (export "main") (result f32)
411+
(struct.get $Point $x
412+
(struct.new_canon_default $Point)
413+
)
414+
)
415+
)
416+
`).exports.main;
417+
assert.eq(main(), 0);
418+
}
419+
324420
{
325421
/*
326422
* Point(i64)
@@ -341,6 +437,23 @@ function testStructGet() {
341437
assert.eq(instance.exports.main(), 1);
342438
}
343439

440+
{
441+
let main = instantiate(`
442+
(module
443+
(type $Point (struct (field $x i64)))
444+
(func (export "main") (result i32)
445+
(i64.eq
446+
(i64.const 0)
447+
(struct.get $Point $x
448+
(struct.new_canon_default $Point)
449+
)
450+
)
451+
)
452+
)
453+
`).exports.main;
454+
assert.eq(main(), 1);
455+
}
456+
344457
{
345458
/*
346459
* Point(f64)
@@ -358,6 +471,20 @@ function testStructGet() {
358471
assert.eq(instance.exports.main(), 37);
359472
}
360473

474+
{
475+
let main = instantiate(`
476+
(module
477+
(type $Point (struct (field $x f64)))
478+
(func (export "main") (result f64)
479+
(struct.get $Point $x
480+
(struct.new_canon_default $Point)
481+
)
482+
)
483+
)
484+
`).exports.main;
485+
assert.eq(main(), 0);
486+
}
487+
361488
{
362489
/*
363490
* Point(externref)
@@ -376,6 +503,20 @@ function testStructGet() {
376503
assert.eq(instance.exports.main(obj), obj);
377504
}
378505

506+
{
507+
let main = instantiate(`
508+
(module
509+
(type $Point (struct (field $x externref)))
510+
(func (export "main") (result externref)
511+
(struct.get $Point $x
512+
(struct.new_canon_default $Point)
513+
)
514+
)
515+
)
516+
`).exports.main;
517+
assert.eq(main(), null);
518+
}
519+
379520
{
380521
/*
381522
* (module
@@ -401,6 +542,20 @@ function testStructGet() {
401542
assert.eq(instance2.exports.main(foo), foo);
402543
}
403544

545+
{
546+
let main = instantiate(`
547+
(module
548+
(type $Point (struct (field $x funcref)))
549+
(func (export "main") (result funcref)
550+
(struct.get $Point $x
551+
(struct.new_canon_default $Point)
552+
)
553+
)
554+
)
555+
`).exports.main;
556+
assert.eq(main(), null);
557+
}
558+
404559
{
405560
/*
406561
* Point(i32, i32)
@@ -837,5 +992,6 @@ function testStructSet() {
837992
testStructDeclaration();
838993
testStructJS();
839994
testStructNew();
995+
testStructNewDefault();
840996
testStructGet();
841997
testStructSet();

JSTests/wasm/wasm.json

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"call": { "category": "call", "value": 16, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "function_index", "type": "varuint32"}], "description": "call a function by its index" },
108108
"call_indirect": { "category": "call", "value": 17, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "table_index","type": "varuint32"}],"description": "call a function indirect with an expected signature" },
109109
"tail_call": { "category": "call", "value": 18, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "function_index", "type": "varuint32"}], "description": "tail call a function by its index" },
110-
"tail_call_indirect": { "category": "call", "value": 19, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "table_index","type": "varuint32"}],"description": "tail call a function indirect with an expected signature" },
110+
"tail_call_indirect": { "category": "call", "value": 19, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "table_index","type": "varuint32"}],"description": "indirect tail call a function with an expected signature" },
111111
"call_ref": { "category": "call", "value": 20, "return": ["call"], "parameter": ["call"], "immediate": [], "description": "call a function reference" },
112112
"i32.load8_s": { "category": "memory", "value": 44, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" },
113113
"i32.load8_u": { "category": "memory", "value": 45, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" },
@@ -263,20 +263,22 @@
263263
"i64.extend16_s": { "category": "conversion", "value": 195, "return": ["i64"], "parameter": ["i64"], "immediate": [], "b3op": "SExt32(SExt16(Trunc(@0)))" },
264264
"i64.extend32_s": { "category": "conversion", "value": 196, "return": ["i64"], "parameter": ["i64"], "immediate": [], "b3op": "SExt32(Trunc(@0))" },
265265

266-
"struct.new_canon": { "category": "gc", "value": 251, "return": ["ref_type"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}], "description": "allocates a new structure", "extendedOp": 1 },
267-
"struct.get": { "category": "gc", "value": 251, "return": ["any"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "field_index","type": "varuint32"}],"description": "reads the field from a structure", "extendedOp": 3 },
268-
"struct.set": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["ref_type"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "field_index","type": "varuint32"}],"description": "sets the field from a structure", "extendedOp": 6 },
269-
"struct.new": { "category": "gc", "value": 251, "return": ["ref_type"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}], "description": "allocates a new structure", "extendedOp": 7 },
270-
"array.new": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["any", "i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 17 },
271-
"array.new_default": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 18 },
272-
"array.get": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 19 },
273-
"array.get_s": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 20 },
274-
"array.get_u": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 21 },
275-
"array.set": { "category": "gc", "value": 251, "return": [], "parameter": ["arrayref", "i32", "any"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 22 },
276-
"array.len": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["arrayref"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 23 },
277-
"i31.new": { "category": "gc", "value": 251, "return": ["i31ref"], "parameter": ["i32"], "immediate": [], "extendedOp": 32 },
278-
"i31.get_s": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["i31ref"], "immediate": [], "extendedOp": 33 },
279-
"i31.get_u": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["i31ref"], "immediate": [], "extendedOp": 34 },
266+
"struct.new_canon": { "category": "gc", "value": 251, "return": ["ref_type"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}], "description": "allocates a new structure", "extendedOp": 1 },
267+
"struct.new_canon_default": { "category": "gc", "value": 251, "return": ["ref_type"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}], "description": "allocates a new structure with default field values", "extendedOp": 2 },
268+
"struct.get": { "category": "gc", "value": 251, "return": ["any"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "field_index","type": "varuint32"}],"description": "reads the field from a structure", "extendedOp": 3 },
269+
"struct.set": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["ref_type"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "field_index","type": "varuint32"}],"description": "sets the field from a structure", "extendedOp": 6 },
270+
"struct.new": { "category": "gc", "value": 251, "return": ["ref_type"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}], "description": "allocates a new structure", "extendedOp": 7 },
271+
"struct.new_default": { "category": "gc", "value": 251, "return": ["ref_type"], "parameter": [], "immediate": [{"name": "type_index", "type": "varuint32"}], "description": "allocates a new structure with default field values", "extendedOp": 8 },
272+
"array.new": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["any", "i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 17 },
273+
"array.new_default": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 18 },
274+
"array.get": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 19 },
275+
"array.set": { "category": "gc", "value": 251, "return": [], "parameter": ["arrayref", "i32", "any"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 22 },
276+
"array.get_s": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 20 },
277+
"array.get_u": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 21 },
278+
"array.len": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["arrayref"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 23 },
279+
"i31.new": { "category": "gc", "value": 251, "return": ["i31ref"], "parameter": ["i32"], "immediate": [], "extendedOp": 32 },
280+
"i31.get_s": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["i31ref"], "immediate": [], "extendedOp": 33 },
281+
"i31.get_u": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["i31ref"], "immediate": [], "extendedOp": 34 },
280282

281283
"i32.trunc_sat_f32_s": { "category": "conversion", "value": 252, "return": ["i32"], "parameter": ["f32"], "immediate": [], "extendedOp": 0 },
282284
"i32.trunc_sat_f32_u": { "category": "conversion", "value": 252, "return": ["i32"], "parameter": ["f32"], "immediate": [], "extendedOp": 1 },

Source/JavaScriptCore/bytecode/BytecodeList.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,6 +1899,7 @@
18991899
args: {
19001900
dst: VirtualRegister,
19011901
typeIndex: unsigned,
1902+
useDefault: bool,
19021903
firstValue: VirtualRegister,
19031904
}
19041905

Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ struct AirIRGeneratorBase {
408408
PartialResult WARN_UNUSED_RETURN addArraySet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType value);
409409
PartialResult WARN_UNUSED_RETURN addArrayLen(ExpressionType arrayref, ExpressionType& result);
410410
PartialResult WARN_UNUSED_RETURN addStructNew(uint32_t index, Vector<ExpressionType>& args, ExpressionType& result);
411+
PartialResult WARN_UNUSED_RETURN addStructNewDefault(uint32_t index, ExpressionType& result);
411412
PartialResult WARN_UNUSED_RETURN addStructGet(ExpressionType structReference, const StructType&, uint32_t fieldIndex, ExpressionType& result);
412413
PartialResult WARN_UNUSED_RETURN addStructSet(ExpressionType structReference, const StructType&, uint32_t fieldIndex, ExpressionType value);
413414

@@ -2609,6 +2610,33 @@ auto AirIRGeneratorBase<Derived, ExpressionType>::addStructNew(uint32_t typeInde
26092610
return { };
26102611
}
26112612

2613+
template <typename Derived, typename ExpressionType>
2614+
auto AirIRGeneratorBase<Derived, ExpressionType>::addStructNewDefault(uint32_t typeIndex, ExpressionType& result) -> PartialResult
2615+
{
2616+
ASSERT(typeIndex < m_info.typeCount());
2617+
result = self().tmpForType(Type { TypeKind::Ref, m_info.typeSignatures[typeIndex]->index() });
2618+
// FIXME: inline the allocation.
2619+
// https://bugs.webkit.org/show_bug.cgi?id=244388
2620+
self().emitCCall(&operationWasmStructNewEmpty, result, instanceValue(), self().addConstant(Types::I32, typeIndex));
2621+
2622+
const auto& structType = *m_info.typeSignatures[typeIndex]->template as<StructType>();
2623+
for (StructFieldCount i = 0; i < structType.fieldCount(); ++i) {
2624+
ExpressionType tmpForValue;
2625+
if (Wasm::isRefType(structType.field(i).type))
2626+
tmpForValue = self().addConstant(structType.field(i).type.template as<Type>(), JSValue::encode(jsNull()));
2627+
else {
2628+
tmpForValue = self().g64();
2629+
self().emitZeroInitialize(tmpForValue);
2630+
}
2631+
2632+
auto status = self().addStructSet(result, structType, i, tmpForValue);
2633+
if (!status)
2634+
return status;
2635+
}
2636+
2637+
return { };
2638+
}
2639+
26122640
template <typename Derived, typename ExpressionType>
26132641
auto AirIRGeneratorBase<Derived, ExpressionType>::addStructGet(ExpressionType structReference, const StructType& structType, uint32_t fieldIndex, ExpressionType& result) -> PartialResult
26142642
{

0 commit comments

Comments
 (0)