LibWasm: Implement parsing/validation for proposal exception-handling

Actual execution traps for now.
This commit is contained in:
Ali Mohammad Pur 2025-09-25 03:35:34 +02:00 committed by Ali Mohammad Pur
parent 8138c2f48b
commit d99f663b1a
14 changed files with 614 additions and 19 deletions

View File

@ -76,6 +76,20 @@ Optional<ElementAddress> Store::allocate(ValueType const& type, Vector<Reference
return address;
}
Optional<TagAddress> Store::allocate(FunctionType const& type, TagType::Flags flags)
{
TagAddress address { m_tags.size() };
m_tags.append({ type, flags });
return address;
}
Optional<ExceptionAddress> Store::allocate(TagInstance const& tag_instance, Vector<Value> params)
{
ExceptionAddress address { m_exceptions.size() };
m_exceptions.append(ExceptionInstance { tag_instance, move(params) });
return address;
}
FunctionInstance* Store::get(FunctionAddress address)
{
auto value = address.value();
@ -132,6 +146,22 @@ DataInstance* Store::get(DataAddress address)
return &m_datas[value];
}
TagInstance* Store::get(TagAddress address)
{
auto value = address.value();
if (m_tags.size() <= value)
return nullptr;
return &m_tags[value];
}
ExceptionInstance* Store::get(ExceptionAddress address)
{
auto value = address.value();
if (m_exceptions.size() <= value)
return nullptr;
return &m_exceptions[value];
}
ErrorOr<void, ValidationError> AbstractMachine::validate(Module& module)
{
if (module.validation_status() != Module::ValidationStatus::Unchecked) {
@ -203,6 +233,19 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
return ByteString::formatted("Function import and extern do not match, parameters: {} vs {}", type.parameters(), other_type.parameters());
return {};
},
[&](TagType const& type) -> Optional<ByteString> {
if (!extern_.has<TagAddress>())
return "Expected tag import"sv;
auto other_tag_instance = m_store.get(extern_.get<TagAddress>());
if (other_tag_instance->flags() != type.flags())
return "Tag import and extern do not match"sv;
auto& this_type = module.type_section().types()[type.type().value()];
if (other_tag_instance->type().parameters() != this_type.parameters())
return "Tag import and extern do not match"sv;
return {};
},
[&](TypeIndex type_index) -> Optional<ByteString> {
if (!extern_.has<FunctionAddress>())
return "Expected function import"sv;
@ -407,7 +450,8 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
[&](FunctionAddress const& address) { module_instance.functions().append(address); },
[&](TableAddress const& address) { module_instance.tables().append(address); },
[&](MemoryAddress const& address) { module_instance.memories().append(address); },
[&](GlobalAddress const& address) { module_instance.globals().append(address); });
[&](GlobalAddress const& address) { module_instance.globals().append(address); },
[&](TagAddress const& address) { module_instance.tags().append(address); });
}
module_instance.functions().extend(own_functions);
@ -434,8 +478,15 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
index++;
}
for (auto& entry : module.tag_section().tags()) {
auto& type = module.type_section().types()[entry.type().value()];
auto address = m_store.allocate(type, entry.flags());
VERIFY(address.has_value());
module_instance.tags().append(*address);
}
for (auto& entry : module.export_section().entries()) {
Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress, Empty> address {};
Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress, TagAddress, Empty> address {};
entry.description().visit(
[&](FunctionIndex const& index) {
if (module_instance.functions().size() > index.value())
@ -460,6 +511,12 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
address = GlobalAddress { module_instance.globals()[index.value()] };
else
dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), module_instance.globals().size());
},
[&](TagIndex const& index) {
if (module_instance.tags().size() > index.value())
address = TagAddress { module_instance.tags()[index.value()] };
else
dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), module_instance.tags().size());
});
if (address.has<Empty>()) {
@ -469,7 +526,7 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
module_instance.exports().append(ExportInstance {
entry.name(),
move(address).downcast<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>(),
move(address).downcast<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress, TagAddress>(),
});
}

View File

@ -38,6 +38,8 @@ AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, GlobalAddress, Arithmetic, Comparison,
AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, ElementAddress, Arithmetic, Comparison, Increment);
AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, DataAddress, Arithmetic, Comparison, Increment);
AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, MemoryAddress, Arithmetic, Comparison, Increment);
AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, TagAddress, Arithmetic, Comparison, Increment);
AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, ExceptionAddress, Arithmetic, Comparison, Increment);
// FIXME: These should probably be made generic/virtual if/when we decide to do something more
// fancy than just a dumb interpreter.
@ -53,8 +55,11 @@ public:
struct Extern {
ExternAddress address;
};
struct Exception {
ExceptionAddress address;
};
using RefType = Variant<Null, Func, Extern>;
using RefType = Variant<Null, Func, Extern, Exception>;
explicit Reference(RefType ref)
: m_ref(move(ref))
{
@ -90,6 +95,10 @@ public:
// ref.null externref
m_value = u128(0, 3);
break;
case ValueType::ExceptionReference:
// ref.null exnref
m_value = u128(0, 4);
break;
}
}
@ -136,10 +145,14 @@ public:
// 1: externref
// 2: null funcref
// 3: null externref
// 4: null exnref
// 5: exnref
ref.ref().visit(
[&](Reference::Func const& func) { m_value = u128(bit_cast<u64>(func.address), bit_cast<u64>(func.source_module.ptr())); },
[&](Reference::Extern const& func) { m_value = u128(bit_cast<u64>(func.address), 1); },
[&](Reference::Null const& null) { m_value = u128(0, null.type.kind() == ValueType::Kind::FunctionReference ? 2 : 3); });
[&](Reference::Null const& null) { m_value = u128(0, null.type.kind() == ValueType::Kind::FunctionReference ? 2 : null.type.kind() == ValueType::Kind::ExceptionReference ? 4
: 3); },
[&](Reference::Exception const& exn) { m_value = u128(bit_cast<u64>(exn.address), 5); });
}
template<SameAs<u128> T>
@ -184,6 +197,10 @@ public:
return Reference { Reference::Null { ValueType(ValueType::Kind::FunctionReference) } };
case 3:
return Reference { Reference::Null { ValueType(ValueType::Kind::ExternReference) } };
case 4:
return Reference { Reference::Null { ValueType(ValueType::Kind::ExceptionReference) } };
case 5:
return Reference { Reference::Exception { bit_cast<ExceptionAddress>(m_value.low()) } };
}
}
VERIFY_NOT_REACHED();
@ -273,7 +290,7 @@ struct InstantiationError {
InstantiationErrorSource source { InstantiationErrorSource::Linking };
};
using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>;
using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress, TagAddress>;
class ExportInstance {
public:
@ -296,13 +313,16 @@ public:
explicit ModuleInstance(
Vector<FunctionType> types, Vector<FunctionAddress> function_addresses, Vector<TableAddress> table_addresses,
Vector<MemoryAddress> memory_addresses, Vector<GlobalAddress> global_addresses, Vector<DataAddress> data_addresses,
Vector<TagAddress> tag_addresses, Vector<TagType> tag_types,
Vector<ExportInstance> exports)
: m_types(move(types))
, m_tag_types(move(tag_types))
, m_functions(move(function_addresses))
, m_tables(move(table_addresses))
, m_memories(move(memory_addresses))
, m_globals(move(global_addresses))
, m_datas(move(data_addresses))
, m_tags(move(tag_addresses))
, m_exports(move(exports))
{
}
@ -317,6 +337,8 @@ public:
auto& elements() const { return m_elements; }
auto& datas() const { return m_datas; }
auto& exports() const { return m_exports; }
auto& tags() const { return m_tags; }
auto& tag_types() const { return m_tag_types; }
auto& types() { return m_types; }
auto& functions() { return m_functions; }
@ -326,15 +348,19 @@ public:
auto& elements() { return m_elements; }
auto& datas() { return m_datas; }
auto& exports() { return m_exports; }
auto& tags() { return m_tags; }
auto& tag_types() { return m_tag_types; }
private:
Vector<FunctionType> m_types;
Vector<TagType> m_tag_types;
Vector<FunctionAddress> m_functions;
Vector<TableAddress> m_tables;
Vector<MemoryAddress> m_memories;
Vector<GlobalAddress> m_globals;
Vector<ElementAddress> m_elements;
Vector<DataAddress> m_datas;
Vector<TagAddress> m_tags;
Vector<ExportInstance> m_exports;
};
@ -552,6 +578,38 @@ private:
Vector<Reference> m_references;
};
class TagInstance {
public:
TagInstance(FunctionType const& type, TagType::Flags flags)
: m_type(type)
, m_flags(flags)
{
}
auto& type() const { return m_type; }
auto flags() const { return m_flags; }
private:
FunctionType m_type;
TagType::Flags m_flags;
};
class ExceptionInstance {
public:
explicit ExceptionInstance(TagInstance const& type, Vector<Value> params)
: m_type(type)
, m_params(move(params))
{
}
auto& type() const { return m_type; }
auto& params() const { return m_params; }
private:
TagInstance m_type;
Vector<Value> m_params;
};
class WASM_API Store {
public:
Store() = default;
@ -563,6 +621,8 @@ public:
Optional<DataAddress> allocate_data(Vector<u8>);
Optional<GlobalAddress> allocate(GlobalType const&, Value);
Optional<ElementAddress> allocate(ValueType const&, Vector<Reference>);
Optional<TagAddress> allocate(FunctionType const&, TagType::Flags);
Optional<ExceptionAddress> allocate(TagInstance const&, Vector<Value>);
Module const* get_module_for(FunctionAddress);
FunctionInstance* get(FunctionAddress);
@ -571,6 +631,8 @@ public:
GlobalInstance* get(GlobalAddress);
DataInstance* get(DataAddress);
ElementInstance* get(ElementAddress);
TagInstance* get(TagAddress);
ExceptionInstance* get(ExceptionAddress);
MemoryInstance* unsafe_get(MemoryAddress address) { return &m_memories.data()[address.value()]; }
@ -581,6 +643,8 @@ private:
Vector<GlobalInstance> m_globals;
Vector<ElementInstance> m_elements;
Vector<DataInstance> m_datas;
Vector<TagInstance> m_tags;
Vector<ExceptionInstance> m_exceptions;
};
class Label {

View File

@ -3754,6 +3754,36 @@ HANDLE_INSTRUCTION(i32x4_relaxed_dot_i8x16_i7x16_add_s)
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
}
HANDLE_INSTRUCTION(throw_ref)
{
interpreter.set_trap("Not Implemented: Proposal 'Exception-handling'"sv);
return Outcome::Return;
}
HANDLE_INSTRUCTION(throw_)
{
{
auto tag_address = configuration.frame().module().tags()[instruction->arguments().get<TagIndex>().value()];
auto& tag_instance = *configuration.store().get(tag_address);
auto& type = tag_instance.type();
auto values = Vector<Value>(configuration.value_stack().span().slice_from_end(type.parameters().size()));
configuration.value_stack().shrink(configuration.value_stack().size() - type.parameters().size());
auto exception_address = configuration.store().allocate(tag_instance, move(values));
if (!exception_address.has_value()) {
interpreter.set_trap("Out of memory"sv);
return Outcome::Return;
}
configuration.value_stack().append(Value(Reference { Reference::Exception { *exception_address } }));
}
TAILCALL return InstructionHandler<Instructions::throw_ref.value()>::operator()<HasDynamicInsnLimit, Continue>(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
}
HANDLE_INSTRUCTION(try_table)
{
interpreter.set_trap("Not Implemented: Proposal 'Exception-handling'"sv);
return Outcome::Return;
}
template<u64 opcode, bool HasDynamicInsnLimit, typename Continue, typename... Args>
constexpr static auto handle_instruction(Args&&... a)
{

View File

@ -58,6 +58,10 @@ ErrorOr<void, ValidationError> Validator::validate(Module& module)
m_globals_without_internal_globals.append(type);
m_context.globals.append(type);
return {};
},
[&](TagType const&) -> ErrorOr<void, ValidationError> {
m_context.tags.append(import_.description().get<TagType>());
return {};
}));
}
@ -89,6 +93,10 @@ ErrorOr<void, ValidationError> Validator::validate(Module& module)
m_context.datas.resize(module.data_section().data().size());
m_context.tags.ensure_capacity(m_context.tags.size() + module.tag_section().tags().size());
for (auto& tag : module.tag_section().tags())
m_context.tags.append(TagType(tag.type(), tag.flags()));
// We need to build the set of declared functions to check that `ref.func` uses a specific set of predetermined functions, found in:
// - Element initializer expressions
// - Global initializer expressions
@ -277,6 +285,17 @@ ErrorOr<void, ValidationError> Validator::validate(MemoryType const& type)
return validate(type.limits(), 1 << 16);
}
ErrorOr<void, ValidationError> Validator::validate(Wasm::TagType const& tag_type)
{
// The function type t1^n -> t2^m must be valid
TRY(validate(tag_type.type()));
auto& type = m_context.types[tag_type.type().value()];
// The type sequence t2^m must be empty
if (!type.results().is_empty())
return Errors::invalid("TagType"sv);
return {};
}
ErrorOr<FunctionType, ValidationError> Validator::validate(BlockType const& type)
{
if (type.kind() == BlockType::Index) {
@ -2030,6 +2049,85 @@ VALIDATE_INSTRUCTION(if_)
return {};
}
// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-x
VALIDATE_INSTRUCTION(throw_)
{
auto tag_index = instruction.arguments().get<TagIndex>();
TRY(validate(tag_index));
auto tag_type = m_context.tags[tag_index.value()];
auto& type = m_context.types[tag_type.type().value()];
if (!type.results().is_empty())
return Errors::invalid("throw type"sv, "empty"sv, type.results());
for (auto const& parameter : type.parameters().in_reverse())
TRY(stack.take(parameter));
m_frames.last().unreachable = true;
stack.resize(m_frames.last().initial_size);
return {};
}
// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-ref
VALIDATE_INSTRUCTION(throw_ref)
{
TRY(stack.take<ValueType::ExceptionReference>());
m_frames.last().unreachable = true;
stack.resize(m_frames.last().initial_size);
return {};
}
// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-try-table-xref-syntax-instructions-syntax-blocktype-mathit-blocktype-xref-syntax-instructions-syntax-catch-mathit-catch-ast-xref-syntax-instructions-syntax-instr-mathit-instr-ast-xref-syntax-instructions-syntax-instr-control-mathsf-end
// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-x-l
// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-ref-x-l
// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-all-l
// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-all-ref-l
VALIDATE_INSTRUCTION(try_table)
{
auto& args = instruction.arguments().get<Instruction::TryTableArgs>();
auto block_type = TRY(validate(args.try_.block_type));
for (auto& catch_ : args.catches) {
auto label = catch_.target_label();
TRY(validate(label));
auto& target_label_type = m_frames[(m_frames.size() - 1) - label.value()].labels();
if (auto tag = catch_.matching_tag_index(); tag.has_value()) {
TRY(validate(tag.value()));
auto tag_type = m_context.tags[tag->value()];
auto& type = m_context.types[tag_type.type().value()];
if (!type.results().is_empty())
return Errors::invalid("catch type"sv, "empty"sv, type.results());
Span<ValueType const> parameters_to_check = type.parameters().span();
if (catch_.is_ref()) {
// catch_ref x l
auto& parameters = type.parameters();
if (parameters.is_empty() || parameters.last().kind() != ValueType::ExceptionReference)
return Errors::invalid("catch_ref type"sv, "[..., exnref]"sv, parameters);
parameters_to_check = parameters_to_check.slice(0, parameters.size() - 1);
} else {
// catch x l
// (noop here)
}
if (parameters_to_check != target_label_type.span())
return Errors::non_conforming_types("catch"sv, parameters_to_check, target_label_type.span());
} else {
if (catch_.is_ref()) {
// catch_all_ref l
if (target_label_type.size() != 1 || target_label_type[0].kind() != ValueType::ExceptionReference)
return Errors::invalid("catch_all_ref type"sv, "[exnref]"sv, target_label_type);
} else {
// catch_all l
if (!target_label_type.is_empty())
return Errors::invalid("catch_all type"sv, "empty"sv, target_label_type);
}
}
}
return {};
}
VALIDATE_INSTRUCTION(br)
{
auto label = instruction.arguments().get<LabelIndex>();

View File

@ -30,6 +30,7 @@ struct Context {
COWVector<ValueType> elements;
COWVector<bool> datas;
COWVector<ValueType> locals;
COWVector<TagType> tags;
Optional<u32> data_count;
RefPtr<RefRBTree> references { make_ref_counted<RefRBTree>() };
size_t imported_function_count { 0 };
@ -68,6 +69,7 @@ public:
ErrorOr<void, ValidationError> validate(MemorySection const&);
ErrorOr<void, ValidationError> validate(TableSection const&);
ErrorOr<void, ValidationError> validate(CodeSection const&);
ErrorOr<void, ValidationError> validate(TagSection const&);
ErrorOr<void, ValidationError> validate(FunctionSection const&) { return {}; }
ErrorOr<void, ValidationError> validate(DataCountSection const&) { return {}; }
ErrorOr<void, ValidationError> validate(TypeSection const&) { return {}; }
@ -136,6 +138,13 @@ public:
return Errors::invalid("TableIndex"sv);
}
ErrorOr<void, ValidationError> validate(TagIndex index) const
{
if (index.value() < m_context.tags.size())
return {};
return Errors::invalid("TagIndex"sv);
}
enum class FrameKind {
Block,
Loop,
@ -293,6 +302,7 @@ public:
ErrorOr<void, ValidationError> validate(TableType const&);
ErrorOr<void, ValidationError> validate(MemoryType const&);
ErrorOr<void, ValidationError> validate(GlobalType const&) { return {}; }
ErrorOr<void, ValidationError> validate(TagType const&);
// Proposal 'memory64'
ErrorOr<void, ValidationError> take_memory_address(Stack& stack, MemoryType const& memory, Instruction::MemoryArgument const& arg)

View File

@ -34,6 +34,7 @@ static constexpr auto extern_function_tag = 0x00;
static constexpr auto extern_table_tag = 0x01;
static constexpr auto extern_memory_tag = 0x02;
static constexpr auto extern_global_tag = 0x03;
static constexpr auto extern_tag_tag = 0x04; // Proposal "exception-handling"
static constexpr auto page_size = 64 * KiB;

View File

@ -22,6 +22,8 @@ namespace Instructions {
M(loop, 0x03, 0, -1) \
M(if_, 0x04, 1, -1) \
M(structured_else, 0x05, -1, -1) \
M(throw_, 0x08, -1, -1) \
M(throw_ref, 0x0a, 1, -1) \
M(structured_end, 0x0b, -1, -1) \
M(br, 0x0c, 0, -1) \
M(br_if, 0x0d, 1, -1) \
@ -34,6 +36,7 @@ namespace Instructions {
M(drop, 0x1a, 1, 0) \
M(select, 0x1b, 3, 1) \
M(select_typed, 0x1c, 3, 1) \
M(try_table, 0x1f, 0, 0) \
M(local_get, 0x20, 0, 1) \
M(local_set, 0x21, 1, 0) \
M(local_tee, 0x22, 1, 1) \

View File

@ -202,6 +202,16 @@ ParseResult<GlobalType> GlobalType::parse(ConstrainedStream& stream)
return GlobalType { type_result, mutable_ == 0x01 };
}
ParseResult<TagType> TagType::parse(ConstrainedStream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("TagType"sv);
auto flags = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
if (flags != 0)
return ParseError::InvalidTag;
auto index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
return TagType { index, static_cast<TagType::Flags>(flags) };
}
ParseResult<BlockType> BlockType::parse(ConstrainedStream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("BlockType"sv);
@ -231,6 +241,38 @@ ParseResult<BlockType> BlockType::parse(ConstrainedStream& stream)
return BlockType { TypeIndex(index_value) };
}
ParseResult<Catch> Catch::parse(ConstrainedStream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("Catch"sv);
auto kind = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
switch (kind) {
case 0: {
// catch x l
auto tag_index = TRY(GenericIndexParser<TagIndex>::parse(stream));
auto label_index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Catch { false, tag_index, label_index };
}
case 1: {
// catch_ref x l
auto tag_index = TRY(GenericIndexParser<TagIndex>::parse(stream));
auto label_index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Catch { true, tag_index, label_index };
}
case 2: {
// catch_all l
auto label_index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Catch { false, {}, label_index };
}
case 3: {
// catch_all_ref l
auto label_index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Catch { true, {}, label_index };
}
default:
return ParseError::InvalidTag;
}
}
ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("Instruction"sv);
@ -249,6 +291,19 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
opcode, StructuredInstructionArgs { block_type, {}, {} }
};
}
case Instructions::try_table.value(): {
// try_table block_type (catch*) (instruction*) end
auto block_type = TRY(BlockType::parse(stream));
auto catch_types = TRY(parse_vector<Catch>(stream));
auto structured_args = StructuredInstructionArgs { block_type, {}, {} };
return Instruction {
opcode, TryTableArgs { move(structured_args), move(catch_types) }
};
}
case Instructions::throw_.value(): {
auto tag_index = TRY(GenericIndexParser<TagIndex>::parse(stream));
return Instruction { opcode, tag_index };
}
case Instructions::br.value():
case Instructions::br_if.value(): {
// branches with a single label immediate
@ -384,6 +439,7 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
auto index = TRY(GenericIndexParser<FunctionIndex>::parse(stream));
return Instruction { opcode, index };
}
case Instructions::throw_ref.value():
case Instructions::structured_end.value():
case Instructions::structured_else.value():
case Instructions::ref_is_null.value():
@ -944,6 +1000,8 @@ ParseResult<ImportSection::Import> ImportSection::Import::parse(ConstrainedStrea
return parse_with_type<MemoryType>(stream, module, name);
case Constants::extern_global_tag:
return parse_with_type<GlobalType>(stream, module, name);
case Constants::extern_tag_tag:
return parse_with_type<TagType>(stream, module, name);
default:
return ParseError::InvalidTag;
}
@ -1012,6 +1070,7 @@ ParseResult<Expression> Expression::parse(ConstrainedStream& stream, Optional<si
case Instructions::block.value():
case Instructions::loop.value():
case Instructions::if_.value():
case Instructions::try_table.value():
stack.append(ip);
break;
case Instructions::structured_end.value(): {
@ -1019,10 +1078,16 @@ ParseResult<Expression> Expression::parse(ConstrainedStream& stream, Optional<si
instructions.empend(Instructions::synthetic_end_expression); // Synthetic noop to mark the end of the expression.
return Expression { move(instructions) };
}
auto entry = stack.take_last();
auto& args = instructions[entry.value()].arguments().get<Instruction::StructuredInstructionArgs>();
// Patch the end_ip of the last structured instruction
args.end_ip = ip + (args.else_ip.has_value() ? 1 : 0);
auto entry = stack.take_last();
instructions[entry.value()].arguments().visit(
[&](Instruction::StructuredInstructionArgs& args) {
args.end_ip = ip + (args.else_ip.has_value() ? 1 : 0);
},
[&](Instruction::TryTableArgs& args) {
args.try_.end_ip = ip + 1;
},
[](auto&) { VERIFY_NOT_REACHED(); });
break;
}
case Instructions::structured_else.value(): {
@ -1075,6 +1140,8 @@ ParseResult<ExportSection::Export> ExportSection::Export::parse(ConstrainedStrea
return Export { name, ExportDesc { MemoryIndex { index } } };
case Constants::extern_global_tag:
return Export { name, ExportDesc { GlobalIndex { index } } };
case Constants::extern_tag_tag:
return Export { name, ExportDesc { TagIndex { index } } };
default:
return ParseError::InvalidTag;
}
@ -1256,6 +1323,25 @@ ParseResult<DataCountSection> DataCountSection::parse(ConstrainedStream& stream)
return DataCountSection { value };
}
ParseResult<TagSection> TagSection::parse(ConstrainedStream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("TagSection"sv);
// https://webassembly.github.io/exception-handling/core/binary/modules.html#binary-tagsec
auto tags = TRY(parse_vector<Tag>(stream));
return TagSection { move(tags) };
}
ParseResult<TagSection::Tag> TagSection::Tag::parse(ConstrainedStream& stream)
{
// https://webassembly.github.io/exception-handling/core/binary/modules.html#binary-tagsec
ScopeLogger<WASM_BINPARSER_DEBUG> logger("Tag"sv);
auto flag = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
if (flag != 0)
return ParseError::InvalidTag; // currently the only valid flag is 0
auto type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
return TagSection::Tag { type_index, static_cast<TagSection::Tag::Flags>(flag) };
}
ParseResult<SectionId> SectionId::parse(Stream& stream)
{
u8 id = TRY_READ(stream, u8, ParseError::ExpectedIndex);
@ -1286,6 +1372,8 @@ ParseResult<SectionId> SectionId::parse(Stream& stream)
return SectionId(SectionIdKind::Data);
case 0x0c:
return SectionId(SectionIdKind::DataCount);
case 0x0d:
return SectionId(SectionIdKind::Tag);
default:
return ParseError::InvalidIndex;
}
@ -1357,14 +1445,15 @@ ParseResult<NonnullRefPtr<Module>> Module::parse(Stream& stream)
case SectionId::SectionIdKind::DataCount:
module.data_count_section() = TRY(DataCountSection::parse(section_stream));
break;
case SectionId::SectionIdKind::Tag:
module.tag_section() = TRY(TagSection::parse(section_stream));
break;
default:
return ParseError::InvalidIndex;
}
if (section_id.kind() != SectionId::SectionIdKind::Custom) {
if (section_id.kind() < last_section_id)
return ParseError::SectionOutOfOrder;
last_section_id = section_id.kind();
}
if (!section_id.can_appear_after(last_section_id))
return ParseError::SectionOutOfOrder;
last_section_id = section_id.kind();
if (section_stream.remaining() != 0)
return ParseError::SectionSizeMismatch;
}

View File

@ -252,7 +252,8 @@ void Printer::print(Wasm::ExportSection::Export const& entry)
[this](FunctionIndex const& index) { print("(function index {})\n", index.value()); },
[this](TableIndex const& index) { print("(table index {})\n", index.value()); },
[this](MemoryIndex const& index) { print("(memory index {})\n", index.value()); },
[this](GlobalIndex const& index) { print("(global index {})\n", index.value()); });
[this](GlobalIndex const& index) { print("(global index {})\n", index.value()); },
[this](TagIndex const& index) { print("(tag index {})\n", index.value()); });
}
print_indent();
print(")\n");
@ -398,6 +399,18 @@ void Printer::print(Wasm::GlobalType const& type)
print(")\n");
}
void Printer::print(Wasm::TagType const& type)
{
print_indent();
print("(type tag\n");
{
TemporaryChange change { m_indent, m_indent + 1 };
print(type.type());
}
print_indent();
print(")\n");
}
void Printer::print(Wasm::ImportSection const& section)
{
if (section.imports().is_empty())
@ -432,6 +445,35 @@ void Printer::print(Wasm::ImportSection::Import const& import)
print(")\n");
}
void Printer::print(Wasm::TagSection const& section)
{
// (section tag\n[ ](tag type)*)
if (section.tags().is_empty())
return;
print_indent();
print("(section tag\n");
{
TemporaryChange change { m_indent, m_indent + 1 };
for (auto& tag : section.tags())
print(tag);
}
print_indent();
print(")\n");
}
void Printer::print(Wasm::TagSection::Tag const& tag)
{
print_indent();
print("(tag (type index {}))\n", tag.type().value());
}
void Printer::print(Wasm::TypeIndex const& index)
{
print_indent();
print("(type index {})\n", index.value());
}
void Printer::print(Wasm::Instruction const& instruction)
{
print_indent();
@ -472,6 +514,16 @@ void Printer::print(Wasm::Instruction const& instruction)
print_indent();
print("(else {}) (end {}))", args.else_ip.has_value() ? ByteString::number(args.else_ip->value()) : "(none)", args.end_ip.value());
},
[&](Instruction::TryTableArgs const& args) {
print("(try_table ");
print(args.try_.block_type);
print(" (catches\n");
TemporaryChange change { m_indent, m_indent + 1 };
for (auto& catch_ : args.catches)
print(catch_);
print_indent();
print(") (end {}))", args.try_.end_ip.value());
},
[&](Instruction::TableBranchArgs const& args) {
print("(table_branch");
for (auto& label : args.labels)
@ -491,6 +543,24 @@ void Printer::print(Wasm::Instruction const& instruction)
}
}
void Printer::print(Catch const& catch_)
{
print_indent();
StringBuilder name_builder;
name_builder.appendff("catch{}{}", catch_.matching_tag_index().has_value() ? ""sv : "_all"sv, catch_.is_ref() ? "_ref"sv : ""sv);
print("({} ", name_builder.string_view());
if (auto index = catch_.matching_tag_index(); index.has_value())
print("(tag index {})", index.value());
print("\n");
{
TemporaryChange change { m_indent, m_indent + 1 };
print_indent();
print("(label index {})\n", catch_.target_label().value());
}
print_indent();
print(")\n");
}
void Printer::print(Wasm::Limits const& limits)
{
print_indent();
@ -566,6 +636,7 @@ void Printer::print(Wasm::Module const& module)
print(module.function_section());
print(module.table_section());
print(module.memory_section());
print(module.tag_section());
print(module.global_section());
print(module.export_section());
print(module.start_section());
@ -682,9 +753,11 @@ void Printer::print(Wasm::Value const& value, Wasm::ValueType const& type)
break;
case ValueType::FunctionReference:
case ValueType::ExternReference:
case ValueType::ExceptionReference:
print("addr({})",
value.to<Reference>().ref().visit(
[](Wasm::Reference::Null const&) { return ByteString("null"); },
[](Wasm::Reference::Exception const&) { return ByteString("exception"); },
[](auto const& ref) { return ByteString::number(ref.address.value()); }));
break;
}
@ -705,6 +778,7 @@ void Printer::print(Wasm::Reference const& value)
"addr({})\n",
value.ref().visit(
[](Wasm::Reference::Null const&) { return ByteString("null"); },
[](Wasm::Reference::Exception const&) { return ByteString("exception"); },
[](auto const& ref) { return ByteString::number(ref.address.value()); }));
}
@ -716,6 +790,9 @@ HashMap<Wasm::OpCode, ByteString> Wasm::Names::instruction_names {
{ Instructions::block, "block" },
{ Instructions::loop, "loop" },
{ Instructions::if_, "if" },
{ Instructions::try_table, "try_table" },
{ Instructions::throw_, "throw" },
{ Instructions::throw_ref, "throw_ref" },
{ Instructions::br, "br" },
{ Instructions::br_if, "br.if" },
{ Instructions::br_table, "br.table" },

View File

@ -44,6 +44,8 @@ struct WASM_API Printer {
void print(Wasm::GlobalType const&);
void print(Wasm::ImportSection const&);
void print(Wasm::ImportSection::Import const&);
void print(Wasm::TagSection const&);
void print(Wasm::TagSection::Tag const&);
void print(Wasm::Instruction const&);
void print(Wasm::Limits const&);
void print(Wasm::Locals const&);
@ -59,8 +61,11 @@ struct WASM_API Printer {
void print(Wasm::TableType const&);
void print(Wasm::TypeSection const&);
void print(Wasm::ValueType const&);
void print(Wasm::TagType const&);
void print(Wasm::Value const&);
void print(Wasm::Value const&, ValueType const&);
void print(Wasm::Catch const&);
void print(Wasm::TypeIndex const&);
private:
void print_indent();

View File

@ -72,6 +72,7 @@ AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, FunctionIndex);
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, TableIndex);
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, ElementIndex);
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, MemoryIndex);
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, TagIndex);
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, LocalIndex);
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, GlobalIndex);
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, LabelIndex);
@ -167,6 +168,7 @@ public:
V128,
FunctionReference,
ExternReference,
ExceptionReference,
};
explicit ValueType(Kind kind)
@ -200,6 +202,8 @@ public:
return "funcref";
case ExternReference:
return "externref";
case ExceptionReference:
return "exnref";
}
VERIFY_NOT_REACHED();
}
@ -336,6 +340,29 @@ private:
bool m_is_mutable { false };
};
// https://webassembly.github.io/exception-handling/core/binary/types.html#tag-types
class TagType {
public:
enum Flags : u8 {
None = 0
};
TagType(TypeIndex type, Flags flags)
: m_flags(flags)
, m_type(type)
{
}
auto& type() const { return m_type; }
auto flags() const { return m_flags; }
static ParseResult<TagType> parse(ConstrainedStream& stream);
private:
Flags m_flags { None };
TypeIndex m_type;
};
// https://webassembly.github.io/spec/core/bikeshed/#binary-blocktype
class BlockType {
public:
@ -386,6 +413,35 @@ private:
};
};
// Proposal "exception-handling"
// https://webassembly.github.io/exception-handling/core/binary/instructions.html
class Catch {
public:
Catch(bool ref, TagIndex index, LabelIndex label) // catch[_ref] x l
: m_matching_tag_index(index)
, m_target_label(label)
, m_is_ref(ref)
{
}
explicit Catch(bool ref, LabelIndex label) // catch_all[_ref] l
: m_target_label(label)
, m_is_ref(ref)
{
}
auto& matching_tag_index() const { return m_matching_tag_index; }
auto& target_label() const { return m_target_label; }
auto is_ref() const { return m_is_ref; }
static ParseResult<Catch> parse(ConstrainedStream& stream);
private:
Optional<TagIndex> m_matching_tag_index; // None for catch_all
LabelIndex m_target_label;
bool m_is_ref = false; // true if catch*_ref
};
// https://webassembly.github.io/spec/core/bikeshed/#binary-instr
// https://webassembly.github.io/spec/core/bikeshed/#reference-instructions%E2%91%A6
// https://webassembly.github.io/spec/core/bikeshed/#parametric-instructions%E2%91%A6
@ -457,6 +513,12 @@ public:
MemoryIndex memory_index;
};
// Proposal "exception-handling"
struct TryTableArgs {
StructuredInstructionArgs try_; // "else" unused.
Vector<Catch> catches;
};
struct ShuffleArgument {
explicit ShuffleArgument(u8 (&lanes)[16])
: lanes {
@ -509,6 +571,7 @@ private:
ElementIndex,
FunctionIndex,
GlobalIndex,
TagIndex,
IndirectCallArgs,
LabelIndex,
LaneIndex,
@ -524,6 +587,7 @@ private:
TableElementArgs,
TableIndex,
TableTableArgs,
TryTableArgs,
ValueType,
Vector<ValueType>,
double,
@ -568,6 +632,17 @@ struct CompiledInstructions {
bool direct = false; // true if all dispatches contain handler_ptr, otherwise false and all contain instruction_opcode.
};
template<Enum auto... Vs>
consteval auto as_ordered()
{
using Type = CommonType<decltype(to_underlying(Vs))...>;
Array<Type, sizeof...(Vs)> result;
[&]<Type... Is>(IntegerSequence<Type, Is...>) {
(void)((result[to_underlying(Vs)] = Is), ...);
}(MakeIntegerSequence<Type, static_cast<Type>(sizeof...(Vs))>());
return result;
}
struct SectionId {
public:
enum class SectionIdKind : u8 {
@ -584,14 +659,44 @@ public:
DataCount,
Code,
Data,
Tag,
};
constexpr inline static auto section_order = as_ordered<
SectionIdKind::Type,
SectionIdKind::Import,
SectionIdKind::Function,
SectionIdKind::Table,
SectionIdKind::Memory,
SectionIdKind::Tag,
SectionIdKind::Global,
SectionIdKind::Export,
SectionIdKind::Start,
SectionIdKind::Element,
SectionIdKind::DataCount,
SectionIdKind::Code,
SectionIdKind::Data,
SectionIdKind::Custom>();
explicit SectionId(SectionIdKind kind)
: m_kind(kind)
{
}
SectionIdKind kind() const { return m_kind; }
bool can_appear_after(SectionIdKind other) const
{
if (kind() == SectionIdKind::Custom || other == SectionIdKind::Custom)
return true;
auto index = section_order[to_underlying(kind())];
auto other_index = section_order[to_underlying(other)];
return index >= other_index;
}
SectionIdKind kind() const
{
return m_kind;
}
static ParseResult<SectionId> parse(Stream& stream);
@ -638,7 +743,7 @@ class ImportSection {
public:
class Import {
public:
using ImportDesc = Variant<TypeIndex, TableType, MemoryType, GlobalType, FunctionType>;
using ImportDesc = Variant<TypeIndex, TableType, MemoryType, GlobalType, FunctionType, TagType>;
Import(ByteString module, ByteString name, ImportDesc description)
: m_module(move(module))
, m_name(move(name))
@ -826,7 +931,7 @@ private:
class ExportSection {
private:
using ExportDesc = Variant<FunctionIndex, TableIndex, MemoryIndex, GlobalIndex>;
using ExportDesc = Variant<FunctionIndex, TableIndex, MemoryIndex, GlobalIndex, TagIndex>;
public:
class Export {
@ -1059,6 +1164,43 @@ private:
Optional<u32> m_count;
};
class TagSection {
public:
class Tag {
public:
using Flags = TagType::Flags;
Tag(TypeIndex type, Flags flags)
: m_type(type)
, m_flags(flags)
{
}
auto type() const { return m_type; }
auto flags() const { return m_flags; }
static ParseResult<Tag> parse(ConstrainedStream& stream);
private:
TypeIndex m_type;
Flags m_flags { Flags::None };
};
TagSection() = default;
explicit TagSection(Vector<Tag> tags)
: m_tags(move(tags))
{
}
auto& tags() const { return m_tags; }
static ParseResult<TagSection> parse(ConstrainedStream& stream);
private:
Vector<Tag> m_tags;
};
class WASM_API Module : public RefCounted<Module>
, public Weakable<Module> {
public:
@ -1099,6 +1241,8 @@ public:
auto& data_section() const { return m_data_section; }
auto& data_count_section() { return m_data_count_section; }
auto& data_count_section() const { return m_data_count_section; }
auto& tag_section() { return m_tag_section; }
auto& tag_section() const { return m_tag_section; }
void set_validation_status(ValidationStatus status, Badge<Validator>) { set_validation_status(status); }
ValidationStatus validation_status() const { return m_validation_status; }
@ -1123,6 +1267,7 @@ private:
CodeSection m_code_section;
DataSection m_data_section;
DataCountSection m_data_count_section;
TagSection m_tag_section;
ValidationStatus m_validation_status { ValidationStatus::Unchecked };
Optional<ByteString> m_validation_error;

View File

@ -617,6 +617,8 @@ JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM& vm, JS::Value va
cache.add_extern_value(extern_addr, value);
return Wasm::Value { Wasm::Reference { Wasm::Reference::Extern { extern_addr } } };
}
case Wasm::ValueType::ExceptionReference:
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);
}
@ -636,6 +638,8 @@ Wasm::Value default_webassembly_value(JS::VM& vm, Wasm::ValueType type)
return Wasm::Value(type);
case Wasm::ValueType::ExternReference:
return MUST(to_webassembly_value(vm, JS::js_undefined(), type));
case Wasm::ValueType::ExceptionReference:
return Wasm::Value(type);
}
VERIFY_NOT_REACHED();
}
@ -680,6 +684,7 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type)
return value.release_value();
}
case Wasm::ValueType::V128:
case Wasm::ValueType::ExceptionReference:
VERIFY_NOT_REACHED();
}
VERIFY_NOT_REACHED();

View File

@ -314,9 +314,11 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export)
}
case Wasm::ValueType::FunctionReference:
case Wasm::ValueType::ExternReference:
case Wasm::ValueType::ExceptionReference:
auto ref = global->value().to<Wasm::Reference>();
return ref.ref().visit(
[&](Wasm::Reference::Null const&) -> JS::Value { return JS::js_null(); },
[](Wasm::Reference::Exception const&) -> JS::Value { return JS::js_undefined(); },
[&](auto const& ref) -> JS::Value { return JS::Value(static_cast<double>(ref.address.value())); });
}
}
@ -398,6 +400,12 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
}
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Extern { static_cast<u64>(double_value) } }));
break;
case Wasm::ValueType::Kind::ExceptionReference:
if (argument.is_null())
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExceptionReference) } }));
else
return vm.throw_completion<JS::TypeError>("Exception references are not supported"sv);
break;
}
}
@ -431,7 +439,9 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
}
case Wasm::ValueType::FunctionReference:
case Wasm::ValueType::ExternReference:
return (value.to<Wasm::Reference>()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](auto const& ref) { return JS::Value(static_cast<double>(ref.address.value())); });
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();
}
VERIFY_NOT_REACHED();
};

View File

@ -233,6 +233,7 @@ static ErrorOr<ParsedValue> parse_value(StringView spec)
case Wasm::ValueType::V128:
case Wasm::ValueType::FunctionReference:
case Wasm::ValueType::ExternReference:
case Wasm::ValueType::ExceptionReference:
VERIFY_NOT_REACHED();
}
last_value = parsed.value.value();