LibWasm: Parse and validate typeuse references

This adds parsing of `(ref typeidx)` and validates that `typeidx` is a
valid index. Currently, nullability of the reference is lost.

A bug causing the code below to fail parsing has been fixed.
```wat
(module
  (type $T (struct (field i32) (field f32)))
  (type $T1 (struct (field i32) (field f32)))
  (; many more types... ;)
  (type $T64 (struct (field i32) (field f32)))
  (type $f (func (result (ref null $T64))))
)
```

The spec tests type-equivalence.{0,1,3,13} have been disabled as they
were previously false positives.
This commit is contained in:
Marcus Nilsson 2026-02-08 17:51:47 +01:00 committed by Ali Mohammad Pur
parent e6c008a269
commit 5fa9747105
10 changed files with 130 additions and 57 deletions

View file

@ -103,6 +103,7 @@ public:
// ref.null exnref
m_value = u128(0, 4);
break;
case ValueType::TypeUseReference:
case ValueType::UnsupportedHeapReference:
// ref.null (todo)
m_value = u128(0, 5);

View file

@ -313,6 +313,7 @@ ErrorOr<void, ValidationError> Validator::validate(TagSection const& section)
ErrorOr<void, ValidationError> Validator::validate(TableType const& type)
{
TRY(validate(type.element_type()));
Optional<u64> bound = type.limits().address_type() == AddressType::I64 ? Optional<u64> {} : (1ull << 32) - 1;
return validate(type.limits(), bound);
}
@ -339,6 +340,33 @@ ErrorOr<void, ValidationError> Validator::validate(Wasm::TagType const& tag_type
return {};
}
ErrorOr<void, ValidationError> Validator::validate(ValueType const& type)
{
if (type.is_typeuse()) {
TRY(validate(type.unsafe_typeindex()));
}
return {};
}
ErrorOr<void, ValidationError> Validator::validate(FunctionType const& type)
{
for (auto param : type.parameters()) {
TRY(validate(param));
}
for (auto param : type.results()) {
TRY(validate(param));
}
return {};
}
ErrorOr<void, ValidationError> Validator::validate(GlobalType const& type)
{
return validate(type.type());
}
ErrorOr<FunctionType, ValidationError> Validator::validate(BlockType const& type)
{
if (type.kind() == BlockType::Index) {

View file

@ -302,11 +302,12 @@ public:
// Types
ErrorOr<void, ValidationError> validate(Limits const&, Optional<u64> bound); // n <= bound && m? <= bound
ErrorOr<FunctionType, ValidationError> validate(BlockType const&);
ErrorOr<void, ValidationError> validate(FunctionType const&) { return {}; }
ErrorOr<void, ValidationError> validate(FunctionType const&);
ErrorOr<void, ValidationError> validate(TableType const&);
ErrorOr<void, ValidationError> validate(MemoryType const&);
ErrorOr<void, ValidationError> validate(GlobalType const&) { return {}; }
ErrorOr<void, ValidationError> validate(GlobalType const&);
ErrorOr<void, ValidationError> validate(TagType const&);
ErrorOr<void, ValidationError> validate(ValueType const&);
// Proposal 'memory64'
ErrorOr<void, ValidationError> take_memory_address(Stack& stack, MemoryType const& memory, Instruction::MemoryArgument const& arg)
@ -407,7 +408,7 @@ struct AK::Formatter<Wasm::Validator::StackEntry> : public AK::Formatter<StringV
ErrorOr<void> format(FormatBuilder& builder, Wasm::Validator::StackEntry const& value)
{
if (value.is_known)
return Formatter<StringView>::format(builder, Wasm::ValueType::kind_name(value.concrete_type.kind()));
return Formatter<StringView>::format(builder, value.concrete_type.kind_name());
return Formatter<StringView>::format(builder, "<unknown>"sv);
}
@ -425,7 +426,7 @@ template<>
struct AK::Formatter<Wasm::ValueType> : public AK::Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, Wasm::ValueType const& value)
{
return Formatter<StringView>::format(builder, Wasm::ValueType::kind_name(value.kind()));
return Formatter<StringView>::format(builder, value.kind_name());
}
};

View file

@ -99,6 +99,45 @@ static ParseResult<ByteString> parse_name(ConstrainedStream& stream)
return string;
}
static ParseResult<ValueType> parse_reference_type(Stream& stream, u8 tag)
{
switch (tag) {
case Constants::function_reference_tag:
return ValueType(ValueType::FunctionReference);
case Constants::extern_reference_tag:
return ValueType(ValueType::ExternReference);
case Constants::array_reference_tag:
case Constants::struct_reference_tag:
case Constants::i31_reference_tag:
case Constants::eq_reference_tag:
case Constants::any_reference_tag:
case Constants::none_reference_tag:
case Constants::noextern_reference_tag:
case Constants::nofunc_reference_tag:
case Constants::noexn_heap_reference_tag:
// FIXME: Implement these when we support wasm-gc properly.
return ValueType(ValueType::UnsupportedHeapReference);
case Constants::nullable_reference_tag_tag:
case Constants::non_nullable_reference_tag_tag: {
tag = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
return parse_reference_type(stream, tag);
}
default: {
ReconsumableStream new_stream { stream };
new_stream.unread({ &tag, 1 });
// FIXME: should be an i33. Right now, we're missing a potential last bit at
// the end. See https://webassembly.github.io/spec/core/bikeshed/#heap-types%E2%91%A6
i32 type_index = TRY_READ(new_stream, LEB128<i32>, ParseError::ExpectedIndex);
if (type_index < 0) {
return with_eof_check(stream, ParseError::InvalidIndex);
}
return ValueType(ValueType::TypeUseReference, TypeIndex(type_index));
}
}
}
ParseResult<ValueType> ValueType::parse(Stream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("ValueType"sv);
@ -115,28 +154,8 @@ ParseResult<ValueType> ValueType::parse(Stream& stream)
return ValueType(F64);
case Constants::v128_tag:
return ValueType(V128);
case Constants::function_reference_tag:
return ValueType(FunctionReference);
case Constants::extern_reference_tag:
return ValueType(ExternReference);
case Constants::array_reference_tag:
case Constants::struct_reference_tag:
case Constants::i31_reference_tag:
case Constants::eq_reference_tag:
case Constants::any_reference_tag:
case Constants::none_reference_tag:
case Constants::noextern_reference_tag:
case Constants::nofunc_reference_tag:
case Constants::noexn_heap_reference_tag:
// FIXME: Implement these when we support wasm-gc properly.
return ValueType(UnsupportedHeapReference);
case Constants::nullable_reference_tag_tag:
case Constants::non_nullable_reference_tag_tag:
tag = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
(void)tag;
return ValueType(UnsupportedHeapReference);
default:
return ParseError::InvalidTag;
return parse_reference_type(stream, tag);
}
}
@ -253,25 +272,14 @@ ParseResult<BlockType> BlockType::parse(ConstrainedStream& stream)
if (kind == Constants::empty_block_tag)
return BlockType {};
{
FixedMemoryStream value_stream { ReadonlyBytes { &kind, 1 } };
if (auto value_type = ValueType::parse(value_stream); !value_type.is_error())
return BlockType { value_type.release_value() };
ReconsumableStream value_stream { stream };
value_stream.unread({ &kind, 1 });
auto value_type = TRY(ValueType::parse(value_stream));
if (value_type.is_typeuse()) {
return BlockType { value_type.unsafe_typeindex() };
}
ReconsumableStream new_stream { stream };
new_stream.unread({ &kind, 1 });
// FIXME: should be an i33. Right now, we're missing a potential last bit at
// the end. See https://webassembly.github.io/spec/core/binary/instructions.html#binary-blocktype
i32 index_value = TRY_READ(new_stream, LEB128<i32>, ParseError::ExpectedIndex);
if (index_value < 0) {
dbgln("Invalid type index {}", index_value);
return with_eof_check(stream, ParseError::InvalidIndex);
}
return BlockType { TypeIndex(index_value) };
return BlockType { value_type };
}
ParseResult<Catch> Catch::parse(ConstrainedStream& stream)

View file

@ -770,7 +770,7 @@ void Printer::print(Wasm::FieldType const& type)
void Printer::print(Wasm::ValueType const& type)
{
print_indent();
print("(type {})\n", ValueType::kind_name(type.kind()));
print("(type {})\n", type.kind_name());
}
void Printer::print(Wasm::Value const& value, Wasm::ValueType const& type)
@ -801,6 +801,9 @@ void Printer::print(Wasm::Value const& value, Wasm::ValueType const& type)
[](Wasm::Reference::Exception const&) { return ByteString("exception"); },
[](auto const& ref) { return ByteString::number(ref.address.value()); }));
break;
case ValueType::TypeUseReference:
print("unsupported-type-use-ref({})", type.unsafe_typeindex());
break;
case ValueType::UnsupportedHeapReference:
print("unsupported-heap-ref");
break;

View file

@ -173,6 +173,7 @@ public:
FunctionReference,
ExternReference,
ExceptionReference,
TypeUseReference,
UnsupportedHeapReference, // Stub for wasm-gc proposal's reference types.
};
@ -181,18 +182,28 @@ public:
{
}
template<typename T>
explicit ValueType(Kind kind, T argument)
: m_kind(kind)
, m_argument(move(argument))
{
}
bool operator==(ValueType const&) const = default;
auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference || m_kind == UnsupportedHeapReference; }
auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference || m_kind == TypeUseReference || m_kind == UnsupportedHeapReference; }
auto is_vector() const { return m_kind == V128; }
auto is_numeric() const { return !is_reference() && !is_vector(); }
auto is_typeuse() const { return m_kind == TypeUseReference; }
auto kind() const { return m_kind; }
auto unsafe_typeindex() const { return m_argument.unsafe_get<TypeIndex>(); }
static ParseResult<ValueType> parse(Stream& stream);
static ByteString kind_name(Kind kind)
ByteString kind_name() const
{
switch (kind) {
switch (m_kind) {
case I32:
return "i32";
case I64:
@ -209,6 +220,8 @@ public:
return "externref";
case ExceptionReference:
return "exnref";
case TypeUseReference:
return ByteString::formatted("ref null {}", unsafe_typeindex().value());
case UnsupportedHeapReference:
return "todo.heapref";
}
@ -217,6 +230,7 @@ public:
private:
Kind m_kind;
Variant<TypeIndex, Empty> m_argument;
};
// https://webassembly.github.io/spec/core/bikeshed/#result-types%E2%91%A2

View file

@ -690,6 +690,7 @@ JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM& vm, JS::Value va
return Wasm::Value(Wasm::ValueType { Wasm::ValueType::Kind::ExceptionReference });
case Wasm::ValueType::V128:
return vm.throw_completion<JS::TypeError>("Cannot convert a vector value to a javascript value"sv);
case Wasm::ValueType::TypeUseReference:
case Wasm::ValueType::UnsupportedHeapReference:
return vm.throw_completion<JS::TypeError>("Unsupported heap reference"sv);
}
@ -711,6 +712,8 @@ Wasm::Value default_webassembly_value(JS::VM& vm, Wasm::ValueType type)
return MUST(to_webassembly_value(vm, JS::js_undefined(), type));
case Wasm::ValueType::ExceptionReference:
return Wasm::Value(type);
case Wasm::ValueType::TypeUseReference:
return Wasm::Value(type);
case Wasm::ValueType::UnsupportedHeapReference:
return Wasm::Value(type);
}
@ -758,6 +761,7 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type)
}
case Wasm::ValueType::V128:
case Wasm::ValueType::ExceptionReference:
case Wasm::ValueType::TypeUseReference:
case Wasm::ValueType::UnsupportedHeapReference:
VERIFY_NOT_REACHED();
}

View file

@ -130,6 +130,9 @@ module try_table.2
module try_table.5
module type-canon.0
module type-canon.1
module type-equivalence.0
module type-equivalence.1
module type-equivalence.13
module type-equivalence.14
module type-equivalence.15
module type-equivalence.16
@ -139,6 +142,7 @@ module type-equivalence.19
module type-equivalence.2
module type-equivalence.20
module type-equivalence.21
module type-equivalence.3
module type-equivalence.4
module type-equivalence.5
module type-equivalence.7

View file

@ -334,6 +334,7 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export)
[](Wasm::Reference::Exception const&) -> JS::Value { return JS::js_undefined(); },
[&](auto const& ref) -> JS::Value { return JS::Value(static_cast<double>(ref.address.value())); });
}
case Wasm::ValueType::TypeUseReference:
case Wasm::ValueType::UnsupportedHeapReference:
return vm.throw_completion<JS::TypeError>("Unsupported heap reference"sv);
}
@ -422,6 +423,12 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
else
return vm.throw_completion<JS::TypeError>("Exception references are not supported"sv);
break;
case Wasm::ValueType::Kind::TypeUseReference:
if (argument.is_null())
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::TypeUseReference, param.unsafe_typeindex()) } }));
else
return vm.throw_completion<JS::TypeError>("GC Heap references are not supported"sv);
break;
case Wasm::ValueType::Kind::UnsupportedHeapReference:
return vm.throw_completion<JS::TypeError>("GC Heap references are not supported"sv);
}
@ -460,6 +467,8 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
return (value.to<Wasm::Reference>()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast<double>(ref.address.value())); });
case Wasm::ValueType::ExceptionReference:
return JS::js_null();
case Wasm::ValueType::TypeUseReference:
return JS::js_null();
case Wasm::ValueType::UnsupportedHeapReference:
return vm.throw_completion<JS::TypeError>("Unsupported heap reference"sv);
}

View file

@ -234,6 +234,7 @@ static ErrorOr<ParsedValue> parse_value(StringView spec)
case Wasm::ValueType::FunctionReference:
case Wasm::ValueType::ExternReference:
case Wasm::ValueType::ExceptionReference:
case Wasm::ValueType::TypeUseReference:
case Wasm::ValueType::UnsupportedHeapReference:
VERIFY_NOT_REACHED();
}
@ -343,7 +344,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
}
auto fn_name = lexer.consume_until(is_any_of("(=:"sv));
struct Arg {
Wasm::ValueType::Kind type;
Wasm::ValueType type;
StringView name;
};
Vector<Arg> formal_params;
@ -354,24 +355,24 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
warnln("Invalid JS export argument name in '{}'", str);
return false;
}
auto type = Wasm::ValueType::I32;
auto type_kind = Wasm::ValueType::I32;
if (lexer.consume_specific(':')) {
if (lexer.consume_specific("i32"sv)) {
type = Wasm::ValueType::I32;
type_kind = Wasm::ValueType::I32;
} else if (lexer.consume_specific("i64"sv)) {
type = Wasm::ValueType::I64;
type_kind = Wasm::ValueType::I64;
} else if (lexer.consume_specific("f32"sv)) {
type = Wasm::ValueType::F32;
type_kind = Wasm::ValueType::F32;
} else if (lexer.consume_specific("f64"sv)) {
type = Wasm::ValueType::F64;
type_kind = Wasm::ValueType::F64;
} else if (lexer.consume_specific("v128"sv)) {
type = Wasm::ValueType::V128;
type_kind = Wasm::ValueType::V128;
} else {
warnln("Invalid JS export argument type in '{}'", str);
return false;
}
}
formal_params.append(Arg { type, name });
formal_params.append(Arg { Wasm::ValueType(type_kind), name });
lexer.consume_specific(',');
}
}
@ -451,7 +452,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
return Wasm::Trap { ByteString("Not enough arguments") };
}
auto& arg = args[i];
switch (type) {
switch (type.kind()) {
case Wasm::ValueType::I32:
js_args.append(JS::Value(arg.to<u32>()));
break;
@ -471,7 +472,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
break;
}
default:
warnln("Unsupported argument type '{}' for JS export function '{}'", Wasm::ValueType::kind_name(type), name);
warnln("Unsupported argument type '{}' for JS export function '{}'", type.kind_name(), name);
return Wasm::Trap { ByteString("Unsupported argument type") };
}
}
@ -864,7 +865,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
} else if (param == values_to_push.last().type) {
values.append(values_to_push.take_last().value);
} else {
warnln("Type mismatch in argument: expected {}, but got {}", Wasm::ValueType::kind_name(param.kind()), Wasm::ValueType::kind_name(values_to_push.last().type.kind()));
warnln("Type mismatch in argument: expected {}, but got {}", param.kind_name(), values_to_push.last().type.kind_name());
return 1;
}
}