diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 270a9cf6b80..32b4f44f078 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -100,7 +100,9 @@ 'stack_switching_suspend.wast', 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', - 'stack_switching_switch.wast' + 'stack_switching_switch.wast', + # TODO: fuzzer support for exact references + 'exact-references.wast', ] diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 55601b174c5..3b9cb21c3e0 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -127,7 +127,9 @@ struct NullTypeParserCtx { TypeT makeF64() { return Ok{}; } TypeT makeV128() { return Ok{}; } - TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; } + TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; } + + HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; } TupleElemListT makeTupleElemList() { return Ok{}; } void appendTupleElem(TupleElemListT&, TypeT) {} @@ -257,10 +259,13 @@ template struct TypeParserCtx { TypeT makeF64() { return Type::f64; } TypeT makeV128() { return Type::v128; } - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return Type(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return Type(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + std::vector makeTupleElemList() { return {}; } void appendTupleElem(std::vector& elems, Type elem) { elems.push_back(elem); @@ -1115,10 +1120,13 @@ struct ParseTypeDefsCtx : TypeParserCtx { : TypeParserCtx(typeIndices), in(in), builder(builder), names(builder.size()) {} - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return builder.getTempRefType(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return builder.getTempRefType(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + TypeT makeTupleType(const std::vector types) { return builder.getTempTupleType(types); } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 4ba88fd8bf2..b54de9979c6 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -30,6 +30,8 @@ using namespace std::string_view_literals; template Result absheaptype(Ctx&, Shareability); template Result heaptype(Ctx&); +template +MaybeResult maybeReftypeAbbrev(Ctx&); template MaybeResult maybeReftype(Ctx&); template Result reftype(Ctx&); template MaybeResult tupletype(Ctx&); @@ -456,68 +458,85 @@ template Result heaptype(Ctx& ctx) { // | 'i31ref' => i31ref // | 'structref' => structref // | 'arrayref' => arrayref -// | '(' ref null? t:heaptype ')' => ref null? t -template MaybeResult maybeReftype(Ctx& ctx) { +// | ... +template +MaybeResult maybeReftypeAbbrev(Ctx& ctx) { if (ctx.in.takeKeyword("funcref"sv)) { - return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("externref"sv)) { - return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("anyref"sv)) { - return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("eqref"sv)) { - return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("i31ref"sv)) { - return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable); + return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("structref"sv)) { - return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("arrayref"sv)) { - return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("exnref"sv)) { - return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("stringref"sv)) { - return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("contref"sv)) { - return ctx.makeRefType(ctx.makeContType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullref"sv)) { - return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexternref"sv)) { - return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullfuncref"sv)) { - return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexnref"sv)) { - return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullcontref"sv)) { - return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact); } + return {}; +} - if (!ctx.in.takeSExprStart("ref"sv)) { - return {}; +// reftype ::= ... +// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht +// | '(' ref null? exact? ht:heaptype ')' => ref null? t +template MaybeResult maybeReftype(Ctx& ctx) { + if (ctx.in.takeSExprStart("exact"sv)) { + auto rt = maybeReftypeAbbrev(ctx); + CHECK_ERR(rt); + if (!rt) { + return ctx.in.err("expected reftype shorthand"); + } + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact); } - auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; - - auto type = heaptype(ctx); - CHECK_ERR(type); - - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); + if (ctx.in.takeSExprStart("ref"sv)) { + auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; + auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact; + auto type = heaptype(ctx); + CHECK_ERR(type); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(*type, nullability, exactness); } - return ctx.makeRefType(*type, nullability); + return maybeReftypeAbbrev(ctx); } template Result reftype(Ctx& ctx) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 2915db46864..bf5d9708583 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -327,6 +327,7 @@ enum EncodedType { eqref = -0x13, // 0x6d nonnullable = -0x1c, // 0x64 nullable = -0x1d, // 0x63 + exact = -0x1e, // 0x62 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 // exception handling @@ -1497,6 +1498,7 @@ class WasmBinaryReader { Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. Type getType(int code); + Type getTypeNoExact(int code); HeapType getHeapType(); HeapType getIndexedHeapType(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 7099df8f2fe..f7abd4908f3 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1551,6 +1551,12 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { void WasmBinaryWriter::writeType(Type type) { if (type.isRef()) { + // Exact references are introduced by the custom descriptors feature, but + // can be used internally even when it is not enabled. In that case, we have + // to generalize the types to be inexact before writing them. + if (!wasm->features.hasCustomDescriptors()) { + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } // The only reference types allowed without GC are funcref, externref, and // exnref. We internally use more refined versions of those types, but we // cannot emit those without GC. @@ -1567,6 +1573,12 @@ void WasmBinaryWriter::writeType(Type type) { type = Type(type.getHeapType().getTop(), Nullable); } } + // If the type is exact, emit the exact prefix and continue on without + // considering exactness. + if (type.isExact()) { + o << S32LEB(BinaryConsts::EncodedType::exact); + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { switch (heapType.getBasic(Unshared)) { @@ -2155,7 +2167,7 @@ Signature WasmBinaryReader::getBlockType() { return Signature(Type::none, getType(code)); } -Type WasmBinaryReader::getType(int code) { +Type WasmBinaryReader::getTypeNoExact(int code) { Type type; if (getBasicType(code, type)) { return type; @@ -2171,6 +2183,17 @@ Type WasmBinaryReader::getType(int code) { WASM_UNREACHABLE("unexpected type"); } +Type WasmBinaryReader::getType(int code) { + if (code == BinaryConsts::EncodedType::exact) { + auto type = getTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("invalid exact prefix on non-reference type"); + } + return Type(type.getHeapType(), type.getNullability(), Exact); + } + return getTypeNoExact(code); +} + Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { @@ -2331,7 +2354,7 @@ void WasmBinaryReader::readTypes() { } return builder.getTempHeapType(size_t(htCode)); }; - auto makeType = [&](int32_t typeCode) { + auto makeTypeNoExact = [&](int32_t typeCode) { Type type; if (getBasicType(typeCode, type)) { return type; @@ -2356,6 +2379,17 @@ void WasmBinaryReader::readTypes() { } WASM_UNREACHABLE("unexpected type"); }; + auto makeType = [&](int32_t typeCode) { + if (typeCode == BinaryConsts::EncodedType::exact) { + auto type = makeTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("unexpected exact prefix on non-reference type"); + } + return builder.getTempRefType( + type.getHeapType(), type.getNullability(), Exact); + } + return makeTypeNoExact(typeCode); + }; auto readType = [&]() { return makeType(getS32LEB()); }; auto readSignatureDef = [&]() { diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast new file mode 100644 index 00000000000..73130afbd33 --- /dev/null +++ b/test/lit/basic/exact-references.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +;; Also check that if we emit a binary without custom descriptors enabled, the +;; types are generalized to be inexact. + +;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o - | wasm-opt -all -S -o - \ +;; RUN: | filecheck %s --check-prefix=NO-EXACT + +(module + ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo)))) + (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) + + + ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) + ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) + ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) + (import "" "g1" (global $g1 (exact anyref))) + + ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any))) + ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any))) + ;; NO-EXACT: (import "" "g2" (global $g2 (ref any))) + (import "" "g2" (global $g2 (ref exact any))) + + ;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo))) + (import "" "g3" (global $g3 (ref null exact $foo))) + + ;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo))) + ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) + ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) + (import "" "g4" (global $g4 (ref exact $foo))) +) +;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) + +;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) + +;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) + +;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) + +;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0)))