Skip to content

Commit

Permalink
[gc] Fix subtype check in call_indirect, binary encoding of recursion…
Browse files Browse the repository at this point in the history
… groups
  • Loading branch information
titzer committed Sep 14, 2023
1 parent 596ed56 commit 5a35af2
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/engine/BpConstants.v3
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ enum BpDefTypeCode(code: byte, val: i7) {
Struct (0x5F, -33),
Array (0x5E, -34),
SUB (0x50, -48),
REC (0x4F, -49),
SUB_FINAL (0x4E, -50)
SUB_FINAL (0x4F, -49),
REC (0x4E, -50)
}

// Utilities associated with binary sections and other quantities.
Expand Down
7 changes: 4 additions & 3 deletions src/engine/Canon.v3
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ def hashHeapTypeDecl(decl: HeapTypeDecl) -> int {
def hashSigDecl(sig: SigDecl) -> int {
if (sig == null) return BpTypeCode.FUNCREF.code;
if (sig.hash != 0) return sig.hash;
var h = sig.params.length; // XXX: hash supertypes?
var h = sig.params.length + if(sig.final, 22); // XXX: hash supertypes?
for (t in sig.params) h = h * 31 + hashValueType(t);
for (t in sig.results) h = h * 31 + hashValueType(t);
return sig.hash = h | int.min;
}
def hashStructDecl(decl: StructDecl) -> int {
if (decl == null) return BpTypeCode.ANYREF.code; // TODO: STRUCTREF
if (decl.hash != 0) return decl.hash;
var h = 1; // XXX: hash supertypes?
var h = if(decl.final, 21, 23); // XXX: hash supertypes?
for (st in decl.field_types) {
h = h * 31 + hashValueType(st.valtype) + st.packing.tag + if(st.mutable, 33);
}
Expand All @@ -63,13 +63,14 @@ def hashStructDecl(decl: StructDecl) -> int {
def hashArrayDecl(decl: ArrayDecl) -> int {
if (decl == null) return BpTypeCode.ARRAYREF.code;
if (decl.hash >= 0) return decl.hash;
var h = 2; // XXX: hash supertypes?
var h = if(decl.final, 24, 25); // XXX: hash supertypes?
for (st in decl.elem_types) {
h = h * 31 + hashValueType(st.valtype) + st.packing.tag + if(st.mutable, 33);
}
return decl.hash = h | int.min;
}
def eqHeapTypeDecl(x: HeapTypeDecl, y: HeapTypeDecl) -> bool {
if (x.final != y.final) return false;
if (!Arrays.allTrue(x.supertypes, y.supertypes, HeapType.==)) return false;
match (x) {
px: StructDecl => match (y) {
Expand Down
5 changes: 4 additions & 1 deletion src/engine/Type.v3
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ component ValueTypes {
def isCompatibleParamType = ValueType.==; // invariant function types for now
def isCompatibleReturnType = ValueType.==; // invariant function types for now
def isAssignableHeap(from: HeapTypeDecl, to: HeapTypeDecl) -> bool {
return isAssignable(Ref(false, from), Ref(false, to)); // XXX: make more efficient
if (from == to) return true;
if (from.canon_id == to.canon_id) return true; // TODO: fix .dup() in unittests
var eq = TypeRelation.compareSuperTypeChain(from, to);
return eq == TypeEquiv.EQUAL || eq == TypeEquiv.SUB;
}
def hasDefaultValue(t: ValueType) -> bool {
match (t) {
Expand Down
13 changes: 6 additions & 7 deletions src/engine/v3/V3Interpreter.v3
Original file line number Diff line number Diff line change
Expand Up @@ -1107,13 +1107,12 @@ component V3Interpreter {
trap(TrapReason.FUNC_INVALID);
return null;
}
var expected = instance.sig_ids[sig_index];
var got = table.ids[func_index];
if (expected != got) {
trap(if(got < 0, TrapReason.FUNC_INVALID, TrapReason.FUNC_SIG_MISMATCH));
return null;
}
return table.funcs[func_index];
var expected = SigDecl.!(instance.heaptypes[sig_index]);
var f = table.funcs[func_index];
if (f == null) { trap(TrapReason.FUNC_INVALID); return null; }
Trace.OUT.put3("lookupIndirect %d, expected.uid = %d, f.uid = %d", sig_index, expected.canon_id, f.sig.canon_id).outln();
if (!ValueTypes.isAssignableHeap(f.sig, expected)) { trap(TrapReason.FUNC_SIG_MISMATCH); return null; }
return f;
}
def doInvokeHostFunction(hf: HostFunction) -> HostResult {
if (Trace.interpreter) Execute.traceCallHostFunction(hf);
Expand Down
21 changes: 21 additions & 0 deletions test/regress/ext:gc/type-rec0.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
;; Link-time matching of recursive function types

(module $M
(rec (type $f1 (func)) (type (struct)))
(func (export "f") (type $f1))
)
(register "M" $M)

(module
(rec (type $f2 (func)) (type (struct)))
(func (import "M" "f") (type $f2))
)

(assert_unlinkable
(module
(rec (type (struct)) (type $f2 (func)))
(func (import "M" "f") (type $f2))
)
"incompatible import type"
)

60 changes: 60 additions & 0 deletions test/regress/ext:gc/type-subtyping.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
(module
(type $t0 (sub (func (result (ref null func)))))
(rec (type $t1 (sub $t0 (func (result (ref null $t1))))))
(rec (type $t2 (sub $t1 (func (result (ref null $t2))))))

(func $f0 (type $t0) (ref.null func))
(func $f1 (type $t1) (ref.null $t1))
(func $f2 (type $t2) (ref.null $t2))
(table funcref (elem $f0 $f1 $f2))

(func (export "run")
(block (result (ref null func)) (call_indirect (type $t0) (i32.const 0)))
(block (result (ref null func)) (call_indirect (type $t0) (i32.const 1)))
(block (result (ref null func)) (call_indirect (type $t0) (i32.const 2)))
(block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 1)))
(block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 2)))
(block (result (ref null $t2)) (call_indirect (type $t2) (i32.const 2)))

(block (result (ref null $t0)) (ref.cast (ref $t0) (table.get (i32.const 0))))
(block (result (ref null $t0)) (ref.cast (ref $t0) (table.get (i32.const 1))))
(block (result (ref null $t0)) (ref.cast (ref $t0) (table.get (i32.const 2))))
(block (result (ref null $t1)) (ref.cast (ref $t1) (table.get (i32.const 1))))
(block (result (ref null $t1)) (ref.cast (ref $t1) (table.get (i32.const 2))))
(block (result (ref null $t2)) (ref.cast (ref $t2) (table.get (i32.const 2))))
(br 0)
)

(func (export "fail1")
(block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 0)))
(br 0)
)
(func (export "fail2")
(block (result (ref null $t1)) (call_indirect (type $t2) (i32.const 0)))
(br 0)
)
(func (export "fail3")
(block (result (ref null $t1)) (call_indirect (type $t2) (i32.const 1)))
(br 0)
)

(func (export "fail4")
(ref.cast (ref $t1) (table.get (i32.const 0)))
(br 0)
)
(func (export "fail5")
(ref.cast (ref $t2) (table.get (i32.const 0)))
(br 0)
)
(func (export "fail6")
(ref.cast (ref $t2) (table.get (i32.const 1)))
(br 0)
)
)
(assert_return (invoke "run"))
(assert_trap (invoke "fail1") "indirect call")
(assert_trap (invoke "fail2") "indirect call")
(assert_trap (invoke "fail3") "indirect call")
(assert_trap (invoke "fail4") "cast")
(assert_trap (invoke "fail5") "cast")
(assert_trap (invoke "fail6") "cast")
18 changes: 18 additions & 0 deletions test/regress/ext:gc/type-subtyping1.bin.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
(module binary
"\00\61\73\6d\01\00\00\00\01\89\80\80\80\00\02\50"
"\00\60\00\00\60\00\00\03\87\80\80\80\00\06\00\01"
"\01\01\01\01\04\85\80\80\80\00\01\70\01\02\02\07"
"\a1\80\80\80\00\04\05\66\61\69\6c\31\00\02\05\66"
"\61\69\6c\32\00\03\05\66\61\69\6c\33\00\04\05\66"
"\61\69\6c\34\00\05\09\8e\80\80\80\00\01\06\00\41"
"\00\0b\70\02\d2\00\0b\d2\01\0b\0a\cb\80\80\80\00"
"\06\82\80\80\80\00\00\0b\82\80\80\80\00\00\0b\8a"
"\80\80\80\00\00\02\40\41\01\11\00\00\0b\0b\8a\80"
"\80\80\00\00\02\40\41\00\11\01\00\0b\0b\8a\80\80"
"\80\00\00\41\01\25\00\fb\16\00\1a\0b\8a\80\80\80"
"\00\00\41\00\25\00\fb\16\01\1a\0b"
)
(assert_trap (invoke "fail1") "indirect call")
(assert_trap (invoke "fail2") "indirect call")
(assert_trap (invoke "fail3") "cast")
(assert_trap (invoke "fail4") "cast")
28 changes: 28 additions & 0 deletions test/regress/ext:gc/type-subtyping1.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
(module
(type $t1 (sub (func)))
(type $t2 (sub final (func)))

(func $f1 (type $t1))
(func $f2 (type $t2))
(table funcref (elem $f1 $f2))

(func (export "fail1")
(block (call_indirect (type $t1) (i32.const 1)))
)
(func (export "fail2")
(block (call_indirect (type $t2) (i32.const 0)))
)

(func (export "fail3")
(ref.cast (ref $t1) (table.get (i32.const 1)))
(drop)
)
(func (export "fail4")
(ref.cast (ref $t2) (table.get (i32.const 0)))
(drop)
)
)
(assert_trap (invoke "fail1") "indirect call")
(assert_trap (invoke "fail2") "indirect call")
(assert_trap (invoke "fail3") "cast")
(assert_trap (invoke "fail4") "cast")
24 changes: 24 additions & 0 deletions test/regress/ext:gc/type-subtyping2.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(module
(type $t1 (sub (func)))
(type $t2 (sub final (func)))
(func (export "f1") (type $t1))
(func (export "f2") (type $t2))
)
(register "M2")

(assert_unlinkable
(module
(type $t1 (sub (func)))
(type $t2 (sub final (func)))
(func (import "M2" "f1") (type $t2))
)
"incompatible import type"
)
(assert_unlinkable
(module
(type $t1 (sub (func)))
(type $t2 (sub final (func)))
(func (import "M2" "f2") (type $t1))
)
"incompatible import type"
)

0 comments on commit 5a35af2

Please sign in to comment.