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:
parent
e6c008a269
commit
5fa9747105
10 changed files with 130 additions and 57 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue