From 81a4c9b9308fef36c378f6c084c32e62e18d83c9 Mon Sep 17 00:00:00 2001 From: chicoxyzzy Date: Mon, 10 Aug 2026 18:58:30 +0200 Subject: [PATCH] Treat exn as a heap type so (ref exn) is non-nullable exnref is the nullable valtype (0x69). The non-nullable form is (ref exn) (0x64 0x69), matching funcref vs (ref func). Parse (ref exn) / (ref null exn), read and write the typed-ref encoding, and apply the same nullability subtyping as FuncRef and ExternRef. catch_ref still produces exnref, matching the current exception-handling testsuite. --- include/wabt/type.h | 18 ++++++---- src/binary-reader.cc | 9 +++-- src/type-checker.cc | 4 ++- src/validator.cc | 1 + src/wast-parser.cc | 43 ++++++++++++++++++------ test/parse/expr/bad-exn-ref-nullable.txt | 11 ++++++ test/parse/expr/exn-ref-as-non-null.txt | 6 ++++ test/parse/expr/exn-ref.txt | 12 +++++++ test/roundtrip/exn-ref.txt | 24 +++++++++++++ 9 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 test/parse/expr/bad-exn-ref-nullable.txt create mode 100644 test/parse/expr/exn-ref-as-non-null.txt create mode 100644 test/parse/expr/exn-ref.txt create mode 100644 test/roundtrip/exn-ref.txt diff --git a/include/wabt/type.h b/include/wabt/type.h index 8ef73112db..10692b67ff 100644 --- a/include/wabt/type.h +++ b/include/wabt/type.h @@ -60,7 +60,7 @@ class Type { I32U = 7, // Not actually specified, but used internally with load/store }; - // Used by FuncRef / ExternRef + // Used by FuncRef / ExternRef / ExnRef enum GenericReferenceType : uint32_t { ReferenceOrNull = 0, ReferenceNonNull = 1, @@ -105,14 +105,18 @@ class Type { } bool IsNullableRef() const { - return enum_ == Type::Reference || enum_ == Type::ExnRef || + return enum_ == Type::Reference || enum_ == Type::RefNull || - ((enum_ == Type::ExternRef || enum_ == Type::FuncRef) && type_index_ == ReferenceOrNull); + ((enum_ == Type::ExternRef || enum_ == Type::FuncRef || + enum_ == Type::ExnRef) && + type_index_ == ReferenceOrNull); } bool IsNonNullableRef() const { return enum_ == Type::Ref || - ((enum_ == Type::ExternRef || enum_ == Type::FuncRef) && type_index_ != ReferenceOrNull); + ((enum_ == Type::ExternRef || enum_ == Type::FuncRef || + enum_ == Type::ExnRef) && + type_index_ != ReferenceOrNull); } bool IsReferenceWithIndex() const { return EnumIsReferenceWithIndex(enum_); } @@ -135,7 +139,8 @@ class Type { case Type::V128: return "v128"; case Type::I8: return "i8"; case Type::I16: return "i16"; - case Type::ExnRef: return "exnref"; + case Type::ExnRef: + return type_index_ == ReferenceOrNull ? "exnref" : "(ref exn)"; case Type::Func: return "func"; case Type::Void: return "void"; case Type::Any: return "any"; @@ -218,7 +223,8 @@ class Type { } static bool EnumIsNonTypedRef(Enum value) { - return value == Type::ExternRef || value == Type::FuncRef; + return value == Type::ExternRef || value == Type::FuncRef || + value == Type::ExnRef; } private: diff --git a/src/binary-reader.cc b/src/binary-reader.cc index d400107151..b241f13803 100644 --- a/src/binary-reader.cc +++ b/src/binary-reader.cc @@ -377,9 +377,12 @@ Result BinaryReader::ReadType(Type* out_value, const char* desc) { if (static_cast(heap_type) < 0 || static_cast(heap_type) >= kInvalidIndex) { Type::Enum heap_type_code = static_cast(heap_type); - ERROR_UNLESS( - heap_type_code == Type::FuncRef || heap_type_code == Type::ExternRef, - "Reference type is limited to func and extern: %s", desc); + ERROR_UNLESS(heap_type_code == Type::FuncRef || + heap_type_code == Type::ExternRef || + (heap_type_code == Type::ExnRef && + options_.features.exceptions_enabled()), + "Reference type is limited to func, extern, and exn: %s", + desc); type = (static_cast(type) == Type::Ref) ? Type::ReferenceNonNull : Type::ReferenceOrNull; diff --git a/src/type-checker.cc b/src/type-checker.cc index ada0ab0388..42cc1f2f3c 100644 --- a/src/type-checker.cc +++ b/src/type-checker.cc @@ -277,6 +277,7 @@ Result TypeChecker::CheckType(Type actual, Type expected) { switch (actual_type) { case Type::ExternRef: case Type::FuncRef: + case Type::ExnRef: return (expected.IsNullableNonTypedRef() || !actual.IsNullableNonTypedRef()) ? Result::Ok @@ -617,7 +618,8 @@ Result TypeChecker::OnBrIf(Index depth) { } static Type convertRefNullToRef(Type type) { - if (type == Type::ExternRef || type == Type::FuncRef) { + if (type == Type::ExternRef || type == Type::FuncRef || + type == Type::ExnRef) { return Type(type, Type::ReferenceNonNull); } diff --git a/src/validator.cc b/src/validator.cc index 295f1c28d6..185e035b77 100644 --- a/src/validator.cc +++ b/src/validator.cc @@ -207,6 +207,7 @@ static Result CheckType(Type actual, Type expected) { switch (actual_type) { case Type::ExternRef: case Type::FuncRef: + case Type::ExnRef: return (expected.IsNullableNonTypedRef() || !actual.IsNullableNonTypedRef()) ? Result::Ok diff --git a/src/wast-parser.cc b/src/wast-parser.cc index 4fcfd4dbdc..bddda9357b 100644 --- a/src/wast-parser.cc +++ b/src/wast-parser.cc @@ -945,22 +945,43 @@ Result WastParser::ParseRefDeclaration(Var* out_type) { EXPECT(Lpar); EXPECT(Ref); - Type::Enum opt_type = Type::Reference; + bool saw_null = false; + if (options_->features.function_references_enabled() || + options_->features.exceptions_enabled()) { + saw_null = Match(TokenType::Null); + } + Type::Enum opt_type = Type::Reference; if (options_->features.function_references_enabled()) { - opt_type = Type::Ref; + opt_type = saw_null ? Type::RefNull : Type::Ref; + } else if (saw_null) { + opt_type = Type::RefNull; + } - if (Match(TokenType::Null)) { - opt_type = Type::RefNull; + if (PeekMatch(TokenType::Func) || PeekMatch(TokenType::Extern) || + PeekMatch(TokenType::Exn)) { + Token token = Consume(); + TokenType token_type = token.token_type(); + Type::Enum ref_enum = Type::FuncRef; + if (token_type == TokenType::Extern) { + ref_enum = Type::ExternRef; + } else if (token_type == TokenType::Exn) { + ref_enum = Type::ExnRef; + } + + if (ref_enum == Type::ExnRef && !options_->features.exceptions_enabled()) { + Error(token.loc, "value type not allowed: %s", + Type(ref_enum).GetName().c_str()); + return Result::Error; } - } - if (PeekMatch(TokenType::Func) || PeekMatch(TokenType::Extern)) { - TokenType token = Consume().token_type(); - out_type->set_opt_type(token == TokenType::Func ? Type::FuncRef - : Type::ExternRef); - out_type->set_index(opt_type == Type::Ref ? Type::ReferenceNonNull - : Type::ReferenceOrNull); + out_type->set_opt_type(ref_enum); + // (ref exn) is non-null even without function-references; (ref func) / + // (ref extern) only become non-null when that proposal is enabled. + bool non_null = + (ref_enum == Type::ExnRef) ? !saw_null : (opt_type == Type::Ref); + out_type->set_index(non_null ? Type::ReferenceNonNull + : Type::ReferenceOrNull); } else { CHECK_RESULT(ParseVar(out_type)); out_type->set_opt_type(opt_type); diff --git a/test/parse/expr/bad-exn-ref-nullable.txt b/test/parse/expr/bad-exn-ref-nullable.txt new file mode 100644 index 0000000000..ee6f956767 --- /dev/null +++ b/test/parse/expr/bad-exn-ref-nullable.txt @@ -0,0 +1,11 @@ +;;; TOOL: wat2wasm +;;; ARGS: --enable-exceptions +;;; ERROR: 1 +(module + (func (param exnref) (result (ref exn)) + (local.get 0))) +(;; STDERR ;;; +out/test/parse/expr/bad-exn-ref-nullable.txt:6:6: error: type mismatch in implicit return, expected [(ref exn)] but got [exnref] + (local.get 0))) + ^^^^^^^^^ +;;; STDERR ;;) diff --git a/test/parse/expr/exn-ref-as-non-null.txt b/test/parse/expr/exn-ref-as-non-null.txt new file mode 100644 index 0000000000..68c444c3b4 --- /dev/null +++ b/test/parse/expr/exn-ref-as-non-null.txt @@ -0,0 +1,6 @@ +;;; TOOL: wat2wasm +;;; ARGS: --enable-exceptions --enable-function-references +(module + (func (result (ref exn)) + (ref.as_non_null (ref.null exn))) +) diff --git a/test/parse/expr/exn-ref.txt b/test/parse/expr/exn-ref.txt new file mode 100644 index 0000000000..ea75d82dc2 --- /dev/null +++ b/test/parse/expr/exn-ref.txt @@ -0,0 +1,12 @@ +;;; TOOL: wat2wasm +;;; ARGS: --enable-exceptions +(module + (func (param (ref exn)) + (throw_ref (local.get 0))) + (func (param (ref null exn)) + (throw_ref (local.get 0))) + (func (param exnref) + (throw_ref (local.get 0))) + (func (result (ref exn)) + (unreachable)) +) diff --git a/test/roundtrip/exn-ref.txt b/test/roundtrip/exn-ref.txt new file mode 100644 index 0000000000..2d97b2dbd8 --- /dev/null +++ b/test/roundtrip/exn-ref.txt @@ -0,0 +1,24 @@ +;;; TOOL: run-roundtrip +;;; ARGS: --stdout --enable-exceptions +(module + (func $nonnull (param (ref exn)) + (throw_ref (local.get 0))) + (func $nullable (param (ref null exn)) + (throw_ref (local.get 0))) + (func $abbrev (param exnref) + (throw_ref (local.get 0))) +) +(;; STDOUT ;;; +(module + (type (;0;) (func (param (ref exn)))) + (type (;1;) (func (param exnref))) + (func (;0;) (type 0) (param (ref exn)) + local.get 0 + throw_ref) + (func (;1;) (type 1) (param exnref) + local.get 0 + throw_ref) + (func (;2;) (type 1) (param exnref) + local.get 0 + throw_ref)) +;;; STDOUT ;;)