From d321fc7722b066a7e4ed50c5bfbe9248d1fb80c0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 28 Mar 2025 05:30:43 -0700 Subject: [PATCH 1/2] Parse and emit exact heap types Implement text and binary parsing for exact heap types as well as binary emitting. --- src/parser/contexts.h | 4 +++ src/parser/parsers.h | 10 ++++++++ src/wasm-binary.h | 2 ++ src/wasm/wasm-binary.cpp | 41 +++++++++++++++++++++--------- src/wasm/wasm-type.cpp | 4 +-- test/lit/basic/exact.wast | 52 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 test/lit/basic/exact.wast diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 4c56780323b..030d404adfe 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -167,6 +167,8 @@ struct NullTypeParserCtx { Result getTypeIndex(Name) { return 1; } Result getHeapTypeFromIdx(Index) { return Ok{}; } + HeapTypeT makeExact(HeapTypeT) { return Ok{}; } + DataStringT makeDataString() { return Ok{}; } void appendDataString(DataStringT&, std::string_view) {} @@ -251,6 +253,8 @@ template struct TypeParserCtx { return HeapTypes::nocont.getBasic(share); } + HeapTypeT makeExact(HeapTypeT type) { return type.with(Exact); } + TypeT makeI32() { return Type::i32; } TypeT makeI64() { return Type::i64; } TypeT makeF32() { return Type::f32; } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 33e9d20fdc9..2e2a6da7e39 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -434,6 +434,7 @@ Result absheaptype(Ctx& ctx, Shareability share) { } // heaptype ::= x:typeidx => types[x] +// | '(' 'exact' x:typeidx ')' => exact types[x] // | t:absheaptype => unshared t // | '(' 'shared' t:absheaptype ')' => shared t template Result heaptype(Ctx& ctx) { @@ -442,6 +443,15 @@ template Result heaptype(Ctx& ctx) { return *t; } + if (ctx.in.takeSExprStart("exact"sv)) { + auto t = typeidx(ctx); + CHECK_ERR(t); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of exact heap type"); + } + return ctx.makeExact(*t); + } + auto share = ctx.in.takeSExprStart("shared"sv) ? Shared : Unshared; auto t = absheaptype(ctx, share); CHECK_ERR(t); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index c20e8e5ab0e..fda3f1e741f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -343,6 +343,8 @@ enum EncodedType { SubFinal = 0x4f, Shared = 0x65, SharedLEB = -0x1b, // Also 0x65 as an SLEB128 + Exact = 0x62, + ExactLEB = -0x1e, // Also 0x62 as an SLEB128 Rec = 0x4e, Descriptor = 0x4d, Describes = 0x4c, diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index c18416a727f..53b736fcf4c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -719,7 +719,7 @@ uint32_t WasmBinaryWriter::getElementSegmentIndex(Name name) const { } uint32_t WasmBinaryWriter::getTypeIndex(HeapType type) const { - auto it = indexedTypes.indices.find(type); + auto it = indexedTypes.indices.find(type.with(Inexact)); #ifndef NDEBUG if (it == indexedTypes.indices.end()) { std::cout << "Missing type: " << type << '\n'; @@ -1668,8 +1668,10 @@ void WasmBinaryWriter::writeHeapType(HeapType type) { if (!wasm->features.hasGC()) { type = type.getTop(); } - if (!type.isBasic()) { + if (type.isExact()) { + o << uint8_t(BinaryConsts::EncodedType::Exact); + } o << S64LEB(getTypeIndex(type)); // TODO: Actually s33 return; } @@ -2183,12 +2185,20 @@ Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { auto type = getS64LEB(); // TODO: Actually s33 + auto exactness = Inexact; + if (type == BinaryConsts::EncodedType::ExactLEB) { + exactness = Exact; + type = getS64LEB(); // TODO: Actually s33 + } // Single heap types are negative; heap type indices are non-negative if (type >= 0) { if (size_t(type) >= types.size()) { - throwError("invalid signature index: " + std::to_string(type)); + throwError("invalid type index: " + std::to_string(type)); } - return types[type]; + return types[type].with(exactness); + } + if (exactness == Exact) { + throwError("invalid type index: " + std::to_string(type)); } auto share = Unshared; if (type == BinaryConsts::EncodedType::SharedLEB) { @@ -2198,10 +2208,8 @@ HeapType WasmBinaryReader::getHeapType() { HeapType ht; if (getBasicHeapType(type, ht)) { return ht.getBasic(share); - } else { - throwError("invalid wasm heap type: " + std::to_string(type)); } - WASM_UNREACHABLE("unexpected type"); + throwError("invalid wasm heap type: " + std::to_string(type)); } HeapType WasmBinaryReader::getIndexedHeapType() { @@ -2325,6 +2333,20 @@ void WasmBinaryReader::readTypes() { auto readHeapType = [&]() -> HeapType { int64_t htCode = getS64LEB(); // TODO: Actually s33 + auto exactness = Inexact; + if (htCode == BinaryConsts::EncodedType::ExactLEB) { + exactness = Exact; + htCode = getS64LEB(); // TODO: Actually s33 + } + if (htCode >= 0) { + if (size_t(htCode) >= builder.size()) { + throwError("invalid type index: " + std::to_string(htCode)); + } + return builder.getTempHeapType(size_t(htCode)).with(exactness); + } + if (exactness == Exact) { + throwError("invalid type index: " + std::to_string(htCode)); + } auto share = Unshared; if (htCode == BinaryConsts::EncodedType::SharedLEB) { share = Shared; @@ -2334,10 +2356,7 @@ void WasmBinaryReader::readTypes() { if (getBasicHeapType(htCode, ht)) { return ht.getBasic(share); } - if (size_t(htCode) >= builder.size()) { - throwError("invalid type index: " + std::to_string(htCode)); - } - return builder.getTempHeapType(size_t(htCode)); + throwError("invalid wasm heap type: " + std::to_string(htCode)); }; auto makeType = [&](int32_t typeCode) { Type type; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index d198919882d..6fc43c2e091 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1621,9 +1621,9 @@ void TypePrinter::printHeapTypeName(HeapType type) { if (type.isBasic()) { print(type); } else { - generator(type).name.print(os); + generator(type.with(Inexact)).name.print(os); #if TRACE_CANONICALIZATION - os << "(;" << ((type.getID() >> 4) % 1000) << ";) "; + os << "(;" << ((type.with(Inexact).getID() >> 4) % 1000) << ";) "; #endif } if (type.isExact()) { diff --git a/test/lit/basic/exact.wast b/test/lit/basic/exact.wast new file mode 100644 index 00000000000..13adca540ee --- /dev/null +++ b/test/lit/basic/exact.wast @@ -0,0 +1,52 @@ +;; 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 + +(module + (rec + ;; CHECK-TEXT: (rec + ;; CHECK-TEXT-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) + ;; CHECK-BIN: (rec + ;; CHECK-BIN-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) + (type $a (struct (field (ref null (exact 0)) (ref (exact 1))))) + ;; CHECK-TEXT: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) + ;; CHECK-BIN: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) + (type $b (struct (field (ref (exact $a)) (ref null (exact $b))))) + ) + + ;; CHECK-TEXT: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) + + ;; CHECK-TEXT: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) + ;; CHECK-TEXT-NEXT: (local $2 (ref null (exact $a))) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) + + ;; CHECK-BIN: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) + ;; CHECK-BIN-NEXT: (local $2 (ref null (exact $a))) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $foo (param (ref null (exact $a)) (ref (exact $b))) + (result (ref (exact $a)) (ref null (exact $b))) + (local (ref null (exact $a))) + (unreachable) + ) +) +;; CHECK-BIN-NODEBUG: (rec +;; CHECK-BIN-NODEBUG-NEXT: (type $0 (struct (field (ref null (exact $0))) (field (ref (exact $1))))) + +;; CHECK-BIN-NODEBUG: (type $1 (struct (field (ref (exact $0))) (field (ref null (exact $1))))) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref null (exact $0)) (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))))) + +;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref null (exact $0))) (param $1 (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))) +;; CHECK-BIN-NODEBUG-NEXT: (local $2 (ref null (exact $0))) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) From 8d840be02d63d315cdd4b0cfc6e94f30319fa0af Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 2 Apr 2025 13:58:40 -0700 Subject: [PATCH 2/2] skip fuzzing --- scripts/test/fuzzing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index bceb6ae1c55..30c3930bd98 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -113,6 +113,8 @@ 'vacuum-stack-switching.wast' # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', + # TODO: fuzzer support for exact heap types + 'exact.wast', ]