diff --git a/include/wabt/type.h b/include/wabt/type.h index 8ef73112d..10692b67f 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 d40010715..b241f1380 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 ada0ab038..42cc1f2f3 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 295f1c28d..185e035b7 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 4fcfd4dbd..bddda9357 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 000000000..ee6f95676 --- /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 000000000..68c444c3b --- /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 000000000..ea75d82dc --- /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 000000000..2d97b2dbd --- /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 ;;)