diff --git a/src/engine/BpConstants.v3 b/src/engine/BpConstants.v3 index f3b1a941..dd59bcda 100644 --- a/src/engine/BpConstants.v3 +++ b/src/engine/BpConstants.v3 @@ -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. diff --git a/src/engine/Canon.v3 b/src/engine/Canon.v3 index f4bba6ee..176bc649 100644 --- a/src/engine/Canon.v3 +++ b/src/engine/Canon.v3 @@ -46,7 +46,7 @@ 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; @@ -54,7 +54,7 @@ def hashSigDecl(sig: SigDecl) -> int { 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); } @@ -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) { diff --git a/src/engine/Type.v3 b/src/engine/Type.v3 index ae758001..15849f5f 100644 --- a/src/engine/Type.v3 +++ b/src/engine/Type.v3 @@ -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) { diff --git a/src/engine/v3/V3Interpreter.v3 b/src/engine/v3/V3Interpreter.v3 index e5055f21..faf59822 100644 --- a/src/engine/v3/V3Interpreter.v3 +++ b/src/engine/v3/V3Interpreter.v3 @@ -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); diff --git a/test/regress/ext:gc/type-rec0.wast b/test/regress/ext:gc/type-rec0.wast new file mode 100644 index 00000000..7f719999 --- /dev/null +++ b/test/regress/ext:gc/type-rec0.wast @@ -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" +) + diff --git a/test/regress/ext:gc/type-subtyping.wast b/test/regress/ext:gc/type-subtyping.wast new file mode 100644 index 00000000..5d0142ba --- /dev/null +++ b/test/regress/ext:gc/type-subtyping.wast @@ -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") diff --git a/test/regress/ext:gc/type-subtyping1.bin.wast b/test/regress/ext:gc/type-subtyping1.bin.wast new file mode 100644 index 00000000..10ad67a3 --- /dev/null +++ b/test/regress/ext:gc/type-subtyping1.bin.wast @@ -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") diff --git a/test/regress/ext:gc/type-subtyping1.wast b/test/regress/ext:gc/type-subtyping1.wast new file mode 100644 index 00000000..6b81b5ba --- /dev/null +++ b/test/regress/ext:gc/type-subtyping1.wast @@ -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") diff --git a/test/regress/ext:gc/type-subtyping2.wast b/test/regress/ext:gc/type-subtyping2.wast new file mode 100644 index 00000000..afb3b8af --- /dev/null +++ b/test/regress/ext:gc/type-subtyping2.wast @@ -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" +)