Skip to content

Commit

Permalink
[gc] Fix checking of finality of supertype declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
titzer committed Dec 2, 2024
1 parent c4c72e6 commit 2cf561a
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 12 deletions.
2 changes: 1 addition & 1 deletion issues.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Prio Component Issue
interpreter compress ctlxfer entries (4 byte cases)
eh implement catch in SPC

gc Test for final types
gc tests for binary iso-rec canonicalization
gc tests for verifying supertype decls
gc implement array.new_data, array.new_elem in init expressions
Expand Down Expand Up @@ -254,6 +253,7 @@ Inline WasmObject.values array

Done
--------------------------------
gc Test for final types
monitor Track runtime function argument consts to detect specialization/inlining opportunities
ext extended constants
fast-int non-final subtyping check for call_indirect
Expand Down
4 changes: 4 additions & 0 deletions src/engine/Type.v3
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ component HeapTypeDecls {
for (s in t.supertypes) {
if (!HeapType.Struct.?(s)) return error.abs(addr).IllegalSupertype(x, s);
var y = HeapType.Struct.!(s).sdecl;
if (y.final) return error.abs(addr).FinalSupertype(x, s);
if (x.field_types.length < y.field_types.length) return error.abs(addr).IllegalSupertype(x, s);
for (i < y.field_types.length) {
if (!checkStorageType(x.field_types[i], y.field_types[i])) return error.abs(addr).IllegalSupertype(x, s);
Expand All @@ -467,13 +468,15 @@ component HeapTypeDecls {
for (s in t.supertypes) {
if (!HeapType.Array.?(s)) return error.abs(addr).IllegalSupertype(x, s);
var y = HeapType.Array.!(s).array;
if (y.final) return error.abs(addr).FinalSupertype(x, s);
if (!Arrays.allTrue(x.elem_types, y.elem_types, checkStorageType)) return error.abs(addr).IllegalSupertype(x, s);
}
}
x: SigDecl => {
for (s in t.supertypes) {
if (!HeapType.Func.?(s)) return error.abs(addr).IllegalSupertype(x, s);
var y = HeapType.Func.!(s).sig;
if (y.final) return error.abs(addr).FinalSupertype(x, s);
if (!Arrays.allTrue(x.results, y.results, ValueTypes.isAssignable)) return error.abs(addr).IllegalSupertype(x, s);
if (!Arrays.allTrue(y.params, x.params, ValueTypes.isAssignable)) return error.abs(addr).IllegalSupertype(x, s);
}
Expand All @@ -482,6 +485,7 @@ component HeapTypeDecls {
for (s in t.supertypes) {
if (!HeapType.Cont.?(s)) return error.abs(addr).IllegalSupertype(x, s);
var y = HeapType.Cont.!(s).cont.sig;
if (y.final) return error.abs(addr).FinalSupertype(x, s);
if (!Arrays.allTrue(x.sig.results, y.results, ValueTypes.isAssignable)) return error.abs(addr).IllegalSupertype(x, s);
if (!Arrays.allTrue(y.params, x.sig.params, ValueTypes.isAssignable)) return error.abs(addr).IllegalSupertype(x, s);
}
Expand Down
10 changes: 10 additions & 0 deletions src/util/ErrorGen.v3
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,16 @@ class ErrorGen(filename: string) {
setc(WasmError.ILLEGAL_SUPERTYPE,
Strings.format2("%s type definition cannot have a %q as supertype", kind, ValueTypes.renderHeapType(_, null, false, sup)));
}
def FinalSupertype(decl: HeapTypeDecl, sup: HeapType) {
var kind = "decl";
match (decl) {
x: StructDecl => kind = "struct";
x: ArrayDecl => kind = "array";
x: SigDecl => kind = "func";
}
setc(WasmError.ILLEGAL_SUPERTYPE,
Strings.format2("%s type definition cannot have final type %q as supertype", kind, ValueTypes.renderHeapType(_, null, false, sup)));
}
def InvalidTagAttribute(b: byte) {
setc(WasmError.INVALID_ATTRIBUTE,
Strings.format1("invalid tag attribute, expected 0, got %x", b));
Expand Down
7 changes: 7 additions & 0 deletions test/regress/ext:gc/type-subtyping3.bin.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(assert_invalid
(module binary
"\00\61\73\6d\01\00\00\00\01\8a\80\80\80\00\02\60"
"\00\00\50\01\00\60\00\00"
)
"sub type"
)
7 changes: 7 additions & 0 deletions test/regress/ext:gc/type-subtyping3.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(assert_invalid
(module
(type $t (func))
(type $s (sub $t (func)))
)
"sub type"
)
10 changes: 10 additions & 0 deletions test/regress/ext:gc/type-subtyping4.bin.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(module binary
"\00\61\73\6d\01\00\00\00\01\b9\80\80\80\00\03\4e"
"\02\50\00\60\00\00\50\00\5f\01\64\00\00\4e\02\50"
"\00\60\00\00\50\00\5f\01\64\02\00\4e\02\50\01\02"
"\60\00\00\50\01\03\5f\05\64\00\00\64\02\00\64\00"
"\00\64\02\00\64\04\00\03\82\80\80\80\00\01\04\07"
"\85\80\80\80\00\01\01\67\00\00\0a\88\80\80\80\00"
"\01\82\80\80\80\00\00\0b"
)
(register "M4")
10 changes: 10 additions & 0 deletions test/regress/ext:gc/type-subtyping4.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(module
(rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1))))))
(rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2))))))
(rec
(type $g2 (sub $f2 (func)))
(type (sub $s2 (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2)))))
)
(func (export "g") (type $g2))
)
(register "M4")
39 changes: 38 additions & 1 deletion test/unittest/BinParserTest.v3
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def Z = void(
T("bad_sub0", test_bad_sub0),
T("bad_sub1", test_bad_sub1),
T("bad_sub2", test_bad_sub2),
T("bad_sub3", test_bad_sub3),
// TODO: tests for proper decoding of struct and array types
()
);
Expand Down Expand Up @@ -1528,6 +1529,7 @@ def test_sub2(t: BinParserTester) {
var s = BinSectionTester.new(t, BpSection.Type);
var sect1: Array<byte> = [
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.I32.code,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.I32.code
Expand All @@ -1543,20 +1545,23 @@ def test_bad_sub0(t: BinParserTester) {
var s = BinSectionTester.new(t, BpSection.Type);
s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // func <: func
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.I32.code,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.I64.code
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // func <: struct
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Struct.code, 0,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.F64.code
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // func <: array
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Array.code, BpTypeCode.I8.code, 0,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.F32.code
Expand All @@ -1569,20 +1574,23 @@ def test_bad_sub1(t: BinParserTester) {
var s = BinSectionTester.new(t, BpSection.Type);
s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // struct <: struct
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Struct.code, 1, BpTypeCode.I8.code, 0,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Struct.code, 0
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // struct <: funct
s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // struct <: func
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.F64.code,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Struct.code, 0
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // struct <: array
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.F32.code,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Struct.code, 0
Expand All @@ -1595,22 +1603,51 @@ def test_bad_sub2(t: BinParserTester) {
var s = BinSectionTester.new(t, BpSection.Type);
s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // array <: array
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Array.code, BpTypeCode.I8.code, 0,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Array.code, BpTypeCode.I16.code, 0
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // array <: struct
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Struct.code, 0,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Array.code, BpTypeCode.I8.code, 0
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // array <: func
2,
BpDefTypeCode.SUB.code, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.F32.code,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Array.code, BpTypeCode.I8.code, 0
]);
}

def test_bad_sub3(t: BinParserTester) {
t.extensions |= Extension.GC;

var s = BinSectionTester.new(t, BpSection.Type);
s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // struct <: final struct
2,
BpDefTypeCode.Struct.code, 0,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Struct.code, 0
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // array <: final array
2,
BpDefTypeCode.Array.code, BpTypeCode.I8.code, 0,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Array.code, BpTypeCode.I8.code, 0
]);

s.invalid(WasmError.ILLEGAL_SUPERTYPE, [ // func <: final func
2,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.I32.code,
BpDefTypeCode.SUB.code, 1, 0,
BpDefTypeCode.Function.code, 0, 1, BpTypeCode.I32.code
]);
}
4 changes: 2 additions & 2 deletions test/unittest/CodeValidatorTest.v3
Original file line number Diff line number Diff line change
Expand Up @@ -1450,7 +1450,7 @@ def test_call_ref0(t: CodeValidatorTester) {
var sig_v_v = t.addSig(SigCache.v_v);
var ref_i_i = ValueTypes.RefFunc(false, sig_i_i);
var ref_v_v = ValueTypes.RefFunc(false, sig_v_v);

var code1_i_i: Array<byte> = [Opcode.CALL_REF.code, heapIndexByte(sig_i_i)];
var code1_v_v: Array<byte> = [Opcode.CALL_REF.code, heapIndexByte(sig_v_v)];
var code2_i_i: Array<byte> = [Opcode.LOCAL_GET.code, 0, Opcode.CALL_REF.code, heapIndexByte(sig_i_i)];
Expand Down Expand Up @@ -1517,7 +1517,7 @@ def test_retcall_ref0(t: CodeValidatorTester) {
if (sig == sig_v_v) t.valid(code3);
else if (sig.params.length == 1 && sig.params[0] == p && sig.results.length == 0) t.valid(code3);
else t.TypeError(code3, 5);

t.sig0([p, ref], [p]);
if (sig.results.length == 1 && sig.results[0] == p &&
sig.params.length == 1 && sig.params[0] == p) t.valid(code3);
Expand Down
61 changes: 53 additions & 8 deletions test/unittest/HeapTypeTest.v3
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ def X_ = void(
T("canon_mixed0", test_canon_mixed0),
T("canon_mixed1", test_canon_mixed1),
T("valid_suptype0", test_valid_suptype0),
T("valid_suptype0b", test_valid_suptype0b),
T("valid_suptype1", test_valid_suptype1),
T("valid_suptype1b", test_valid_suptype1b),
T("invalid_suptype0", test_invalid_suptype0),
T("invalid_suptype1", test_invalid_suptype1),
T("invalid_suptype2", test_invalid_suptype2),
Expand All @@ -33,6 +35,9 @@ def newArray = ArrayDecl.new(true, NO_SUPERS, _);
def finalSig = SigDecl.new(true, _, _, _);
def finalStruct = StructDecl.new(true, _, _);
def finalArray = ArrayDecl.new(true, _, _);
def nonfinalSig = SigDecl.new(false, _, _, _);
def nonfinalStruct = StructDecl.new(false, _, _);
def nonfinalArray = ArrayDecl.new(false, _, _);

class HeapTypeTester(t: Tester) {
def test_id_cached(a: Array<HeapTypeDecl>) {
Expand Down Expand Up @@ -276,13 +281,28 @@ def test_valid_suptype0(t: HeapTypeTester) {
var mut_int_field = StorageType(ValueType.I32, Packedness.UNPACKED, true);

var s0 = newStruct(no_fields);
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], no_fields));
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], [mut_int_field]));

var a0 = finalArray(NO_SUPERS, [mut_int_field]);
t.test_supertype_checking(false, finalArray([HeapType.Array(a0)], [mut_int_field]));

var f0 = SigCache.v_v;
t.test_supertype_checking(false, finalSig([HeapType.Func(f0)], SigCache.arr_v, SigCache.arr_v));
}

def test_valid_suptype0b(t: HeapTypeTester) {
var no_fields = Array<StorageType>.new(0);
var mut_int_field = StorageType(ValueType.I32, Packedness.UNPACKED, true);

var s0 = nonfinalStruct(NO_SUPERS, no_fields);
t.test_supertype_checking(true, finalStruct([HeapType.Struct(s0)], no_fields));
t.test_supertype_checking(true, finalStruct([HeapType.Struct(s0)], [mut_int_field]));

var a0 = finalArray(NO_SUPERS, [mut_int_field]);
var a0 = nonfinalArray(NO_SUPERS, [mut_int_field]);
t.test_supertype_checking(true, finalArray([HeapType.Array(a0)], [mut_int_field]));

var f0 = SigCache.v_v;
var f0 = nonfinalSig(NO_SUPERS, [], []);
t.test_supertype_checking(true, finalSig([HeapType.Func(f0)], SigCache.arr_v, SigCache.arr_v));
}

Expand All @@ -293,19 +313,44 @@ def test_valid_suptype1(t: HeapTypeTester) {
var imm_nullref_field = StorageType(ValueTypes.NULLREF, Packedness.UNPACKED, false);

var s0 = newStruct([imm_anyref_field]);
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], [imm_anyref_field]));
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], [imm_nullref_field]));
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], [imm_funcref_field]));

var a0 = finalArray(NO_SUPERS, [imm_anyref_field]);
t.test_supertype_checking(false, finalArray([HeapType.Array(a0)], [imm_anyref_field]));

var f0 = finalSig(NO_SUPERS, SigCache.arr_v, [ValueTypes.ANYREF]);
t.test_supertype_checking(false, finalSig([HeapType.Func(f0)], SigCache.arr_v, [ValueTypes.ANYREF])); // covariant ret
t.test_supertype_checking(false, finalSig([HeapType.Func(f0)], SigCache.arr_v, [ValueTypes.NULLREF])); // covariant ret
t.test_supertype_checking(false, finalSig([HeapType.Func(f0)], SigCache.arr_v, [ValueTypes.FUNCREF])); // covariant ret

var f1 = finalSig(NO_SUPERS, [ValueTypes.NULLREF], SigCache.arr_v);
t.test_supertype_checking(false, finalSig([HeapType.Func(f1)], [ValueTypes.NULLREF], SigCache.arr_v)); // contra-variant param
t.test_supertype_checking(false, finalSig([HeapType.Func(f1)], [ValueTypes.ANYREF], SigCache.arr_v)); // contra-variant param
t.test_supertype_checking(false, finalSig([HeapType.Func(f1)], [ValueTypes.FUNCREF], SigCache.arr_v)); // contra-variant param
}

def test_valid_suptype1b(t: HeapTypeTester) {
var no_fields = Array<StorageType>.new(0);
var imm_anyref_field = StorageType(ValueTypes.ANYREF, Packedness.UNPACKED, false);
var imm_funcref_field = StorageType(ValueTypes.FUNCREF, Packedness.UNPACKED, false);
var imm_nullref_field = StorageType(ValueTypes.NULLREF, Packedness.UNPACKED, false);

var s0 = nonfinalStruct(NO_SUPERS, [imm_anyref_field]);
t.test_supertype_checking(true, finalStruct([HeapType.Struct(s0)], [imm_anyref_field]));
t.test_supertype_checking(true, finalStruct([HeapType.Struct(s0)], [imm_nullref_field]));
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], [imm_funcref_field]));

var a0 = finalArray(NO_SUPERS, [imm_anyref_field]);
var a0 = nonfinalArray(NO_SUPERS, [imm_anyref_field]);
t.test_supertype_checking(true, finalArray([HeapType.Array(a0)], [imm_anyref_field]));

var f0 = finalSig(NO_SUPERS, SigCache.arr_v, [ValueTypes.ANYREF]);
var f0 = nonfinalSig(NO_SUPERS, SigCache.arr_v, [ValueTypes.ANYREF]);
t.test_supertype_checking(true, finalSig([HeapType.Func(f0)], SigCache.arr_v, [ValueTypes.ANYREF])); // covariant ret
t.test_supertype_checking(true, finalSig([HeapType.Func(f0)], SigCache.arr_v, [ValueTypes.NULLREF])); // covariant ret
t.test_supertype_checking(false, finalSig([HeapType.Func(f0)], SigCache.arr_v, [ValueTypes.FUNCREF])); // covariant ret

var f1 = finalSig(NO_SUPERS, [ValueTypes.NULLREF], SigCache.arr_v);
var f1 = nonfinalSig(NO_SUPERS, [ValueTypes.NULLREF], SigCache.arr_v);
t.test_supertype_checking(true, finalSig([HeapType.Func(f1)], [ValueTypes.NULLREF], SigCache.arr_v)); // contra-variant param
t.test_supertype_checking(true, finalSig([HeapType.Func(f1)], [ValueTypes.ANYREF], SigCache.arr_v)); // contra-variant param
t.test_supertype_checking(false, finalSig([HeapType.Func(f1)], [ValueTypes.FUNCREF], SigCache.arr_v)); // contra-variant param
Expand Down Expand Up @@ -352,17 +397,17 @@ def test_invalid_suptype2(t: HeapTypeTester) {
var mut_anyref_field = StorageType(ValueTypes.ANYREF, Packedness.UNPACKED, true);
var mut_funcref_field = StorageType(ValueTypes.FUNCREF, Packedness.UNPACKED, true);

var s0 = newStruct([mut_anyref_field]);
var s0 = nonfinalStruct(NO_SUPERS, [mut_anyref_field]);
t.test_supertype_checking(true, finalStruct([HeapType.Struct(s0)], [mut_anyref_field])); // ok
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], [mut_int_field])); // field type mismatch
t.test_supertype_checking(false, finalStruct([HeapType.Struct(s0)], [mut_funcref_field])); // field type mismatch

var a0 = finalArray(NO_SUPERS, [mut_anyref_field]);
var a0 = nonfinalArray(NO_SUPERS, [mut_anyref_field]);
t.test_supertype_checking(true, finalArray([HeapType.Array(a0)], [mut_anyref_field])); // ok
t.test_supertype_checking(false, finalArray([HeapType.Array(a0)], [mut_int_field])); // elem type mismatch
t.test_supertype_checking(false, finalArray([HeapType.Array(a0)], [mut_funcref_field])); // elem type mismatch

var f0 = finalSig(NO_SUPERS, [ValueTypes.ANYREF], [ValueTypes.FUNCREF]);
var f0 = nonfinalSig(NO_SUPERS, [ValueTypes.ANYREF], [ValueTypes.FUNCREF]);
t.test_supertype_checking(true, finalSig([HeapType.Func(f0)], [ValueTypes.ANYREF], [ValueTypes.FUNCREF])); // ok
t.test_supertype_checking(false, finalSig([HeapType.Func(f0)], [ValueType.I32], [ValueTypes.FUNCREF])); // param type
t.test_supertype_checking(false, finalSig([HeapType.Func(f0)], [ValueTypes.FUNCREF], [ValueTypes.FUNCREF])); // param type
Expand Down

0 comments on commit 2cf561a

Please sign in to comment.