LibWasm: Validate the wasm-gc and function-references instructions

Actual implementations are still trap-on-exec.
This commit is contained in:
Ali Mohammad Pur 2026-06-11 07:15:32 +02:00 committed by Ali Mohammad Pur
parent 42c0a430f5
commit 62cb073ada
12 changed files with 1003 additions and 55 deletions

View file

@ -606,7 +606,23 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
auxiliary_instance.globals().append(addr);
}
if (auto result = allocate_all_initial_phase(module, main_module_instance, externs, global_values, module_functions); result.has_value())
Vector<Value> table_initial_values;
for (auto& table : module.table_section().tables()) {
Configuration config { m_store };
if (m_should_limit_instruction_count)
config.enable_instruction_count_limit();
config.set_frame(IsTailcall::No,
auxiliary_instance,
Vector<Value, ArgumentsStaticSize> {},
table.initializer(),
1uz);
auto result = config.execute(interpreter);
if (result.is_trap())
return InstantiationError { "Table initializer trapped", move(result.trap()) };
table_initial_values.append(result.values().first());
}
if (auto result = allocate_all_initial_phase(module, main_module_instance, externs, global_values, table_initial_values, module_functions); result.has_value())
return result.release_value();
for (auto& segment : module.element_section().segments()) {
@ -753,7 +769,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
return InstantiationResult { move(main_module_instance_pointer) };
}
Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module const& module, ModuleInstance& module_instance, Vector<ExternValue>& externs, Vector<Value>& global_values, Vector<FunctionAddress>& own_functions)
Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module const& module, ModuleInstance& module_instance, Vector<ExternValue>& externs, Vector<Value>& global_values, Vector<Value>& table_initial_values, Vector<FunctionAddress>& own_functions)
{
Optional<InstantiationError> result;
@ -771,11 +787,21 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
// https://webassembly.github.io/spec/core/valid/conventions.html#aux-clostype
TypeContext const module_type_context { module.canonical_types().span() };
for (auto& table : module.table_section().tables()) {
for (auto [table_index, table] : enumerate(module.table_section().tables())) {
auto table_address = m_store.allocate(TableType { canonicalized(table.type().element_type(), module_type_context), table.type().limits() });
if (!table_address.has_value())
return InstantiationError { "Failed to allocate a table instance" };
module_instance.tables().append(*table_address);
auto reference = table_initial_values[table_index].to<Reference>();
if (!reference.ref().has<Reference::Null>()) {
auto* table_instance = m_store.get(*table_address);
RefPtr<ModuleInstance const> anchor;
if (reference.ref().has<Reference::Func>())
anchor = &module_instance;
for (size_t i = 0; i < table_instance->elements().size(); ++i)
table_instance->set_element(i, reference, anchor);
}
}
for (auto& memory : module.memory_section().memories()) {

View file

@ -856,7 +856,7 @@ private:
return InterpreterHandle(*this, interpreter);
}
Optional<InstantiationError> allocate_all_initial_phase(Module const&, ModuleInstance&, Vector<ExternValue>&, Vector<Value>& global_values, Vector<FunctionAddress>& own_functions);
Optional<InstantiationError> allocate_all_initial_phase(Module const&, ModuleInstance&, Vector<ExternValue>&, Vector<Value>& global_values, Vector<Value>& table_initial_values, Vector<FunctionAddress>& own_functions);
Optional<InstantiationError> allocate_all_final_phase(Module const&, ModuleInstance&, Vector<Vector<Reference>>& elements);
Store m_store;
StackInfo m_stack_info;

View file

@ -5515,6 +5515,53 @@ HANDLE_INSTRUCTION(try_table)
return Outcome::Return;
}
// Proposal "GC"; FIXME: Actually implement these :)
#define HANDLE_WASM_GC_STUB(name) \
HANDLE_INSTRUCTION(name) \
{ \
LOG_INSN; \
interpreter.set_trap("Not Implemented: wasm-gc"sv); \
return Outcome::Return; \
}
HANDLE_WASM_GC_STUB(ref_eq)
HANDLE_WASM_GC_STUB(ref_as_non_null)
HANDLE_WASM_GC_STUB(br_on_null)
HANDLE_WASM_GC_STUB(br_on_non_null)
HANDLE_WASM_GC_STUB(struct_new)
HANDLE_WASM_GC_STUB(struct_new_default)
HANDLE_WASM_GC_STUB(struct_get)
HANDLE_WASM_GC_STUB(struct_get_s)
HANDLE_WASM_GC_STUB(struct_get_u)
HANDLE_WASM_GC_STUB(struct_set)
HANDLE_WASM_GC_STUB(array_new)
HANDLE_WASM_GC_STUB(array_new_default)
HANDLE_WASM_GC_STUB(array_new_fixed)
HANDLE_WASM_GC_STUB(array_new_data)
HANDLE_WASM_GC_STUB(array_new_elem)
HANDLE_WASM_GC_STUB(array_get)
HANDLE_WASM_GC_STUB(array_get_s)
HANDLE_WASM_GC_STUB(array_get_u)
HANDLE_WASM_GC_STUB(array_set)
HANDLE_WASM_GC_STUB(array_len)
HANDLE_WASM_GC_STUB(array_fill)
HANDLE_WASM_GC_STUB(array_copy)
HANDLE_WASM_GC_STUB(array_init_data)
HANDLE_WASM_GC_STUB(array_init_elem)
HANDLE_WASM_GC_STUB(ref_test)
HANDLE_WASM_GC_STUB(ref_test_null)
HANDLE_WASM_GC_STUB(ref_cast)
HANDLE_WASM_GC_STUB(ref_cast_null)
HANDLE_WASM_GC_STUB(br_on_cast)
HANDLE_WASM_GC_STUB(br_on_cast_fail)
HANDLE_WASM_GC_STUB(any_convert_extern)
HANDLE_WASM_GC_STUB(extern_convert_any)
HANDLE_WASM_GC_STUB(ref_i31)
HANDLE_WASM_GC_STUB(i31_get_s)
HANDLE_WASM_GC_STUB(i31_get_u)
#undef HANDLE_WASM_GC_STUB
bool BytecodeInterpreter::trap_if_insufficient_native_stack_space(size_t minimum_native_stack_space_to_keep_free)
{
return trap_if_not(m_stack_info.size_free() >= minimum_native_stack_space_to_keep_free, Constants::stack_exhaustion_message);

View file

@ -332,9 +332,10 @@ ErrorOr<void, ValidationError> Validator::validate(ElementSection const& section
[](ElementSection::Declarative const&) -> ErrorOr<void, ValidationError> { return {}; },
[](ElementSection::Passive const&) -> ErrorOr<void, ValidationError> { return {}; },
[&](ElementSection::Active const& active) -> ErrorOr<void, ValidationError> {
// https://webassembly.github.io/spec/core/valid/modules.html#element-segments
TRY(validate(active.index));
auto table = m_context.tables[active.index.value()];
if (table.element_type() != segment.type)
if (!matches_reference_type(segment.type, table.element_type(), m_context.type_context()))
return Errors::invalid("active element reference type"sv);
auto at = table.limits().address_value_type();
auto expression_result = TRY(validate(active.expression, { at }));
@ -358,14 +359,19 @@ ErrorOr<void, ValidationError> Validator::validate(ElementSection const& section
ErrorOr<void, ValidationError> Validator::validate(GlobalSection const& section)
{
// https://webassembly.github.io/spec/core/valid/modules.html#modules
auto section_validator = fork();
section_validator.m_context.globals = m_globals_without_internal_globals;
for (auto& entry : section.entries()) {
auto& type = entry.type();
TRY(validate(type));
auto expression_result = TRY(validate(entry.expression(), { type.type() }));
TRY(section_validator.validate(type));
auto expression_result = TRY(section_validator.validate(entry.expression(), { type.type() }));
if (!expression_result.is_constant)
return Errors::invalid("global variable initializer"sv);
if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(type.type().kind()))
if (expression_result.result_types.size() != 1)
return Errors::invalid("global variable initializer type"sv, ValueType(ValueType::I32), expression_result.result_types);
section_validator.m_context.globals.append(type);
}
return {};
@ -378,10 +384,20 @@ ErrorOr<void, ValidationError> Validator::validate(MemorySection const& section)
return {};
}
// https://webassembly.github.io/spec/core/valid/modules.html#tables
ErrorOr<void, ValidationError> Validator::validate(TableSection const& section)
{
for (auto& entry : section.tables())
TRY(validate(entry.type()));
auto section_validator = fork();
section_validator.m_context.globals = m_globals_without_internal_globals;
for (auto& entry : section.tables()) {
TRY(section_validator.validate(entry.type()));
auto expression_result = TRY(section_validator.validate(entry.initializer(), { entry.type().element_type() }));
if (!expression_result.is_constant)
return Errors::invalid("table initializer"sv);
if (expression_result.result_types.size() != 1)
return Errors::invalid("table initializer type"sv, entry.type().element_type(), expression_result.result_types);
}
return {};
}
@ -404,8 +420,19 @@ ErrorOr<void, ValidationError> Validator::validate(CodeSection const& section)
function_validator.m_context.locals.append(local.type());
}
function_validator.m_frames.empend(function_type, FrameKind::Function, (size_t)0);
function_validator.m_max_frame_size = max(function_validator.m_max_frame_size, function_validator.m_frames.size());
// https://webassembly.github.io/spec/core/valid/modules.html#functions
function_validator.m_local_initialized.clear_with_capacity();
function_validator.m_local_init_log.clear_with_capacity();
for (auto& parameter : function_type.parameters()) {
(void)parameter;
function_validator.m_local_initialized.append(true);
}
for (auto& local : function.locals()) {
for (size_t i = 0; i < local.n(); ++i)
function_validator.m_local_initialized.append(local.type().is_defaultable());
}
function_validator.push_frame(Frame { function_type, FrameKind::Function, (size_t)0 });
auto results = TRY(function_validator.validate(function.body(), function_type.results()));
if (results.result_types.size() != function_type.results().size())
@ -1612,30 +1639,39 @@ VALIDATE_INSTRUCTION(select_typed)
}
// https://webassembly.github.io/spec/core/bikeshed/#variable-instructions%E2%91%A2
// https://webassembly.github.io/spec/core/valid/instructions.html#variable-instructions
VALIDATE_INSTRUCTION(local_get)
{
auto index = TRY(validate(instruction.local_index()));
if (!m_local_initialized[index.value()])
return Errors::invalid("local.get of an uninitialized non-defaultable local"sv);
stack.append(m_context.locals[index.value()]);
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#variable-instructions
VALIDATE_INSTRUCTION(local_set)
{
auto index = TRY(validate(instruction.local_index()));
auto& value_type = m_context.locals[index.value()];
TRY(stack.take(value_type));
mark_local_initialized(index.value());
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#variable-instructions
// local.tee x is valid with the instruction type t ->_x t; afterwards the local counts as
// initialized (the init set x).
VALIDATE_INSTRUCTION(local_tee)
{
auto index = TRY(validate(instruction.local_index()));
auto& value_type = m_context.locals[index.value()];
TRY(stack.take(value_type));
mark_local_initialized(index.value());
stack.append(value_type);
return {};
@ -2293,6 +2329,10 @@ VALIDATE_INSTRUCTION(structured_end)
for (auto& result : results)
stack.append(result);
// Locals initialized within the block become uninitialized again when it exits.
// https://webassembly.github.io/spec/core/appendix/algorithm.html
roll_back_local_initializations(last_frame.local_init_log_height);
m_frames.take_last();
return {};
@ -2319,6 +2359,10 @@ VALIDATE_INSTRUCTION(structured_else)
frame.kind = FrameKind::Else;
frame.unreachable = false;
// https://webassembly.github.io/spec/core/appendix/algorithm.html
roll_back_local_initializations(frame.local_init_log_height);
for (auto& parameter : block_type.parameters())
stack.append(parameter);
@ -2334,8 +2378,7 @@ VALIDATE_INSTRUCTION(block)
for (size_t i = 1; i <= parameters.size(); ++i)
TRY(stack.take(parameters[parameters.size() - i]));
m_frames.empend(block_type, FrameKind::Block, stack.size());
m_max_frame_size = max(m_max_frame_size, m_frames.size());
push_frame(Frame { block_type, FrameKind::Block, stack.size() });
for (auto& parameter : parameters)
stack.append(parameter);
@ -2359,8 +2402,7 @@ VALIDATE_INSTRUCTION(loop)
auto const tier_up_eligible = parameters.is_empty() && stack.size() == 0;
m_frames.empend(block_type, FrameKind::Loop, stack.size());
m_max_frame_size = max(m_max_frame_size, m_frames.size());
push_frame(Frame { block_type, FrameKind::Loop, stack.size() });
for (auto& parameter : parameters)
stack.append(parameter);
@ -2386,8 +2428,7 @@ VALIDATE_INSTRUCTION(if_)
for (size_t i = 1; i <= parameters.size(); ++i)
TRY(stack.take(parameters[parameters.size() - i]));
m_frames.empend(block_type, FrameKind::If, stack.size());
m_max_frame_size = max(m_max_frame_size, m_frames.size());
push_frame(Frame { block_type, FrameKind::If, stack.size() });
for (auto& parameter : parameters)
stack.append(parameter);
@ -2496,8 +2537,7 @@ VALIDATE_INSTRUCTION(try_table)
.tier_up_eligible = false,
};
m_frames.empend(block_type, FrameKind::TryTable, stack.size());
m_max_frame_size = max(m_max_frame_size, m_frames.size());
push_frame(Frame { block_type, FrameKind::TryTable, stack.size() });
for (auto& parameter : parameters)
stack.append(parameter);
@ -4492,6 +4532,527 @@ VALIDATE_INSTRUCTION(i32x4_relaxed_dot_i8x16_i7x16_add_s)
return stack.take_and_put<ValueType::V128, ValueType::V128, ValueType::V128>(ValueType::V128);
}
// https://webassembly.github.io/spec/core/valid/instructions.html#reference-instructions
VALIDATE_INSTRUCTION(ref_eq)
{
TRY(stack.take(ValueType(ValueType::EqReference)));
TRY(stack.take(ValueType(ValueType::EqReference)));
stack.append(ValueType(ValueType::I32));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#reference-instructions
VALIDATE_INSTRUCTION(ref_as_non_null)
{
auto entry = TRY(stack.take_last());
if (entry.is_known && !entry.concrete_type.is_reference())
return Errors::invalid_stack_state(stack, Tuple { "reference" });
if (entry.is_known) {
auto type = entry.concrete_type;
type.set_nullable(false);
stack.append(type);
} else {
stack.append(entry);
}
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#control-instructions
VALIDATE_INSTRUCTION(br_on_null)
{
auto& args = instruction.arguments().get<Instruction::BranchArgs>();
TRY(validate(args.label));
auto entry = TRY(stack.take_last());
if (entry.is_known && !entry.concrete_type.is_reference())
return Errors::invalid_stack_state(stack, Tuple { "reference" });
auto& target = m_frames[(m_frames.size() - 1) - args.label.value()];
auto& type = target.labels();
for (size_t i = 0; i < type.size(); ++i)
TRY(stack.take(type[type.size() - i - 1]));
for (auto& label_type : type)
stack.append(label_type);
if (entry.is_known) {
auto result = entry.concrete_type;
result.set_nullable(false);
stack.append(result);
} else {
stack.append(entry);
}
args.has_stack_adjustment = target.initial_size != stack.size();
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#control-instructions
VALIDATE_INSTRUCTION(br_on_non_null)
{
auto& args = instruction.arguments().get<Instruction::BranchArgs>();
TRY(validate(args.label));
auto& target = m_frames[(m_frames.size() - 1) - args.label.value()];
auto& type = target.labels();
if (type.is_empty() || !type.last().is_reference())
return Errors::invalid("br_on_non_null label type"sv, "t* (ref null? ht)"sv, type);
auto expected = type.last();
expected.set_nullable(true);
TRY(stack.take(expected));
for (size_t i = 0; i + 1 < type.size(); ++i)
TRY(stack.take(type[type.size() - i - 2]));
for (size_t i = 0; i + 1 < type.size(); ++i)
stack.append(type[i]);
args.has_stack_adjustment = target.initial_size != stack.size();
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(struct_new)
{
auto type_index = instruction.arguments().get<TypeIndex>();
TRY(validate(type_index));
auto const& type = m_context.types[type_index.value()];
if (!type.is_struct())
return Errors::invalid("struct.new type"sv, "a struct type"sv, type);
auto& fields = type.struct_().fields();
for (size_t i = 0; i < fields.size(); ++i)
TRY(stack.take(fields[fields.size() - i - 1].type().unpacked()));
stack.append(ValueType(ValueType::TypeUseReference, type_index, false));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(struct_new_default)
{
auto type_index = instruction.arguments().get<TypeIndex>();
TRY(validate(type_index));
auto const& type = m_context.types[type_index.value()];
if (!type.is_struct())
return Errors::invalid("struct.new_default type"sv, "a struct type"sv, type);
for (auto& field : type.struct_().fields()) {
if (!field.type().is_defaultable())
return Errors::invalid("struct.new_default field type, must be defaultable"sv);
}
stack.append(ValueType(ValueType::TypeUseReference, type_index, false));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
ErrorOr<void, ValidationError> Validator::validate_struct_get(Stack& stack, Instruction const& instruction, bool requires_packed)
{
auto& args = instruction.arguments().get<Instruction::StructFieldArgs>();
TRY(validate(args.type_index));
auto const& type = m_context.types[args.type_index.value()];
if (!type.is_struct())
return Errors::invalid("struct.get type"sv, "a struct type"sv, type);
auto& fields = type.struct_().fields();
if (args.field_index >= fields.size())
return Errors::invalid("struct.get field index"sv);
auto field_type = fields[args.field_index].type();
if (field_type.is_packed() != requires_packed)
return Errors::invalid("struct.get signedness, present iff the field type is packed"sv);
TRY(stack.take(ValueType(ValueType::TypeUseReference, args.type_index)));
stack.append(field_type.unpacked());
return {};
}
VALIDATE_INSTRUCTION(struct_get)
{
return validate_struct_get(stack, instruction, false);
}
VALIDATE_INSTRUCTION(struct_get_s)
{
return validate_struct_get(stack, instruction, true);
}
VALIDATE_INSTRUCTION(struct_get_u)
{
return validate_struct_get(stack, instruction, true);
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(struct_set)
{
auto& args = instruction.arguments().get<Instruction::StructFieldArgs>();
TRY(validate(args.type_index));
auto const& type = m_context.types[args.type_index.value()];
if (!type.is_struct())
return Errors::invalid("struct.set type"sv, "a struct type"sv, type);
auto& fields = type.struct_().fields();
if (args.field_index >= fields.size())
return Errors::invalid("struct.set field index"sv);
auto& field = fields[args.field_index];
if (!field.is_mutable())
return Errors::invalid("struct.set field, must be mutable"sv);
TRY(stack.take(field.type().unpacked()));
TRY(stack.take(ValueType(ValueType::TypeUseReference, args.type_index)));
return {};
}
ErrorOr<FieldType, ValidationError> Validator::array_field_type(TypeIndex type_index, StringView instruction_name, bool requires_mutable)
{
TRY(validate(type_index));
auto const& type = m_context.types[type_index.value()];
if (!type.is_array())
return Errors::invalid(instruction_name, "an array type"sv, type);
if (requires_mutable && !type.array().type().is_mutable())
return Errors::invalid(instruction_name, "a mutable array type"sv, type);
return type.array().type();
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_new)
{
auto type_index = instruction.arguments().get<TypeIndex>();
auto field = TRY(array_field_type(type_index, "array.new type"sv, false));
TRY(stack.take<ValueType::I32>());
TRY(stack.take(field.type().unpacked()));
stack.append(ValueType(ValueType::TypeUseReference, type_index, false));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_new_default)
{
auto type_index = instruction.arguments().get<TypeIndex>();
auto field = TRY(array_field_type(type_index, "array.new_default type"sv, false));
if (!field.type().is_defaultable())
return Errors::invalid("array.new_default element type, must be defaultable"sv);
TRY(stack.take<ValueType::I32>());
stack.append(ValueType(ValueType::TypeUseReference, type_index, false));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_new_fixed)
{
auto& args = instruction.arguments().get<Instruction::ArrayNewFixedArgs>();
auto field = TRY(array_field_type(args.type_index, "array.new_fixed type"sv, false));
for (size_t i = 0; i < args.count; ++i)
TRY(stack.take(field.type().unpacked()));
stack.append(ValueType(ValueType::TypeUseReference, args.type_index, false));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_new_data)
{
auto& args = instruction.arguments().get<Instruction::ArrayDataArgs>();
auto field = TRY(array_field_type(args.type_index, "array.new_data type"sv, false));
if (field.type().is_reference())
return Errors::invalid("array.new_data element type"sv, "a numeric or vector type"sv, field.type());
if (!m_context.data_count.has_value())
return Errors::invalid("array.new_data, requires data count section"sv);
TRY(validate(args.data_index));
TRY((stack.take<ValueType::I32, ValueType::I32>()));
stack.append(ValueType(ValueType::TypeUseReference, args.type_index, false));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_new_elem)
{
auto& args = instruction.arguments().get<Instruction::ArrayElemArgs>();
auto field = TRY(array_field_type(args.type_index, "array.new_elem type"sv, false));
if (!field.type().is_reference())
return Errors::invalid("array.new_elem element type"sv, "a reference type"sv, field.type());
TRY(validate(args.element_index));
auto segment_type = m_context.elements[args.element_index.value()];
if (!matches_reference_type(segment_type, field.type(), m_context.type_context()))
return Errors::invalid("array.new_elem element segment type"sv, field.type(), segment_type);
TRY((stack.take<ValueType::I32, ValueType::I32>()));
stack.append(ValueType(ValueType::TypeUseReference, args.type_index, false));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
ErrorOr<void, ValidationError> Validator::validate_array_get(Stack& stack, Instruction const& instruction, bool requires_packed)
{
auto type_index = instruction.arguments().get<TypeIndex>();
auto field = TRY(array_field_type(type_index, "array.get type"sv, false));
if (field.type().is_packed() != requires_packed)
return Errors::invalid("array.get signedness, present iff the element type is packed"sv);
TRY(stack.take<ValueType::I32>());
TRY(stack.take(ValueType(ValueType::TypeUseReference, type_index)));
stack.append(field.type().unpacked());
return {};
}
VALIDATE_INSTRUCTION(array_get)
{
return validate_array_get(stack, instruction, false);
}
VALIDATE_INSTRUCTION(array_get_s)
{
return validate_array_get(stack, instruction, true);
}
VALIDATE_INSTRUCTION(array_get_u)
{
return validate_array_get(stack, instruction, true);
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_set)
{
auto type_index = instruction.arguments().get<TypeIndex>();
auto field = TRY(array_field_type(type_index, "array.set type"sv, true));
TRY(stack.take(field.type().unpacked()));
TRY(stack.take<ValueType::I32>());
TRY(stack.take(ValueType(ValueType::TypeUseReference, type_index)));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_len)
{
TRY(stack.take(ValueType(ValueType::ArrayReference)));
stack.append(ValueType(ValueType::I32));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_fill)
{
auto type_index = instruction.arguments().get<TypeIndex>();
auto field = TRY(array_field_type(type_index, "array.fill type"sv, true));
TRY(stack.take<ValueType::I32>());
TRY(stack.take(field.type().unpacked()));
TRY(stack.take<ValueType::I32>());
TRY(stack.take(ValueType(ValueType::TypeUseReference, type_index)));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_copy)
{
auto& args = instruction.arguments().get<Instruction::ArrayCopyArgs>();
auto destination_field = TRY(array_field_type(args.destination_type_index, "array.copy destination type"sv, true));
auto source_field = TRY(array_field_type(args.source_type_index, "array.copy source type"sv, false));
if (!matches_field_type(FieldType { false, source_field.type() }, FieldType { false, destination_field.type() }, m_context.type_context()))
return Errors::invalid("array.copy source element type"sv, destination_field.type(), source_field.type());
TRY((stack.take<ValueType::I32, ValueType::I32>()));
TRY(stack.take(ValueType(ValueType::TypeUseReference, args.source_type_index)));
TRY(stack.take<ValueType::I32>());
TRY(stack.take(ValueType(ValueType::TypeUseReference, args.destination_type_index)));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_init_data)
{
auto& args = instruction.arguments().get<Instruction::ArrayDataArgs>();
auto field = TRY(array_field_type(args.type_index, "array.init_data type"sv, true));
if (field.type().is_reference())
return Errors::invalid("array.init_data element type"sv, "a numeric or vector type"sv, field.type());
if (!m_context.data_count.has_value())
return Errors::invalid("array.init_data, requires data count section"sv);
TRY(validate(args.data_index));
TRY((stack.take<ValueType::I32, ValueType::I32, ValueType::I32>()));
TRY(stack.take(ValueType(ValueType::TypeUseReference, args.type_index)));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions
VALIDATE_INSTRUCTION(array_init_elem)
{
auto& args = instruction.arguments().get<Instruction::ArrayElemArgs>();
auto field = TRY(array_field_type(args.type_index, "array.init_elem type"sv, true));
if (!field.type().is_reference())
return Errors::invalid("array.init_elem element type"sv, "a reference type"sv, field.type());
TRY(validate(args.element_index));
auto segment_type = m_context.elements[args.element_index.value()];
if (!matches_reference_type(segment_type, field.type(), m_context.type_context()))
return Errors::invalid("array.init_elem element segment type"sv, field.type(), segment_type);
TRY((stack.take<ValueType::I32, ValueType::I32, ValueType::I32>()));
TRY(stack.take(ValueType(ValueType::TypeUseReference, args.type_index)));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#reference-instructions
ErrorOr<void, ValidationError> Validator::validate_ref_test_or_cast(Stack& stack, Instruction const& instruction)
{
auto type = instruction.arguments().get<ValueType>();
TRY(validate(type));
auto top = ValueType(top_of_heap_type(type, m_context.type_context()));
TRY(stack.take(top));
return {};
}
VALIDATE_INSTRUCTION(ref_test)
{
TRY(validate_ref_test_or_cast(stack, instruction));
stack.append(ValueType(ValueType::I32));
return {};
}
VALIDATE_INSTRUCTION(ref_test_null)
{
TRY(validate_ref_test_or_cast(stack, instruction));
stack.append(ValueType(ValueType::I32));
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#reference-instructions
VALIDATE_INSTRUCTION(ref_cast)
{
TRY(validate_ref_test_or_cast(stack, instruction));
stack.append(instruction.arguments().get<ValueType>());
return {};
}
VALIDATE_INSTRUCTION(ref_cast_null)
{
TRY(validate_ref_test_or_cast(stack, instruction));
stack.append(instruction.arguments().get<ValueType>());
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#control-instructions
ErrorOr<void, ValidationError> Validator::validate_br_on_cast(Stack& stack, Instruction const& instruction, bool branch_on_failure)
{
auto& args = instruction.arguments().get<Instruction::BranchOnCastArgs>();
TRY(validate(args.branch.label));
TRY(validate(args.source_type));
TRY(validate(args.target_type));
if (!matches_reference_type(args.target_type, args.source_type, m_context.type_context()))
return Errors::invalid("br_on_cast target type"sv, args.source_type, args.target_type);
auto& target = m_frames[(m_frames.size() - 1) - args.branch.label.value()];
auto& label_types = target.labels();
if (label_types.is_empty() || !label_types.last().is_reference())
return Errors::invalid("br_on_cast label type"sv, "t* rt"sv, label_types);
auto& label_reference_type = label_types.last();
// https://webassembly.github.io/spec/core/valid/conventions.html#conventions
// rt1 \ rt2 = (ref ht1) if rt2 = (ref null ht2)
// = (ref null1? ht1) otherwise
auto difference = args.source_type;
if (args.target_type.is_nullable())
difference.set_nullable(false);
auto& branched_type = branch_on_failure ? difference : args.target_type;
if (!matches_reference_type(branched_type, label_reference_type, m_context.type_context()))
return Errors::invalid("br_on_cast label type"sv, label_reference_type, branched_type);
TRY(stack.take(args.source_type));
for (size_t i = 0; i + 1 < label_types.size(); ++i)
TRY(stack.take(label_types[label_types.size() - i - 2]));
for (size_t i = 0; i + 1 < label_types.size(); ++i)
stack.append(label_types[i]);
stack.append(branch_on_failure ? args.target_type : difference);
args.branch.has_stack_adjustment = target.initial_size != stack.size();
return {};
}
VALIDATE_INSTRUCTION(br_on_cast)
{
return validate_br_on_cast(stack, instruction, false);
}
VALIDATE_INSTRUCTION(br_on_cast_fail)
{
return validate_br_on_cast(stack, instruction, true);
}
// https://webassembly.github.io/spec/core/valid/instructions.html#external-reference-instructions
VALIDATE_INSTRUCTION(any_convert_extern)
{
auto entry = TRY(stack.take_last());
bool nullable = true;
if (entry.is_known) {
if (!matches_value_type(entry.concrete_type, ValueType(ValueType::ExternReference), m_context.type_context()))
return Errors::invalid("any.convert_extern operand"sv, ValueType(ValueType::ExternReference), entry);
nullable = entry.concrete_type.is_nullable();
} else {
nullable = false;
}
stack.append(ValueType(ValueType::AnyReference, nullable));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#external-reference-instructions
VALIDATE_INSTRUCTION(extern_convert_any)
{
auto entry = TRY(stack.take_last());
bool nullable = true;
if (entry.is_known) {
if (!matches_value_type(entry.concrete_type, ValueType(ValueType::AnyReference), m_context.type_context()))
return Errors::invalid("extern.convert_any operand"sv, ValueType(ValueType::AnyReference), entry);
nullable = entry.concrete_type.is_nullable();
} else {
nullable = false;
}
stack.append(ValueType(ValueType::ExternReference, nullable));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#scalar-reference-instructions
VALIDATE_INSTRUCTION(ref_i31)
{
TRY(stack.take<ValueType::I32>());
stack.append(ValueType(ValueType::I31Reference, false));
is_constant = true;
return {};
}
// https://webassembly.github.io/spec/core/valid/instructions.html#scalar-reference-instructions
VALIDATE_INSTRUCTION(i31_get_s)
{
TRY(stack.take(ValueType(ValueType::I31Reference)));
stack.append(ValueType(ValueType::I32));
return {};
}
VALIDATE_INSTRUCTION(i31_get_u)
{
TRY(stack.take(ValueType(ValueType::I31Reference)));
stack.append(ValueType(ValueType::I32));
return {};
}
VALIDATE_INSTRUCTION(synthetic_end_expression)
{
is_constant = true;
@ -4519,7 +5080,7 @@ ErrorOr<Validator::ExpressionTypeResult, ValidationError> Validator::validate(Ex
{
if (m_frames.is_empty())
m_frames.empend(FunctionType { {}, result_types }, FrameKind::Function, (size_t)0);
auto stack = Stack(m_frames);
auto stack = Stack(m_frames, m_context.type_context());
bool is_constant_expression = true;
for (auto& instruction : expression.instructions()) {

View file

@ -168,6 +168,9 @@ public:
size_t initial_size;
// Stack polymorphism is handled with this field
bool unreachable { false };
// Height of the local-initialization undo log when this frame was entered.
// https://webassembly.github.io/spec/core/appendix/algorithm.html
size_t local_init_log_height { 0 };
Vector<ValueType> const& labels() const
{
@ -241,13 +244,16 @@ public:
friend struct AK::Formatter;
public:
explicit Stack(Vector<Frame, 16>&&) = delete;
explicit Stack(Vector<Frame, 16>&&, TypeContext const&) = delete;
explicit Stack(Vector<Frame, 16> const& frames)
explicit Stack(Vector<Frame, 16> const& frames, TypeContext const& type_context)
: m_frames(frames)
, m_type_context(type_context)
{
}
TypeContext const& type_context() const { return m_type_context; }
bool is_empty() const { return m_entries.is_empty(); }
auto& last() const { return m_entries.last(); }
auto& last() { return m_entries.last(); }
@ -277,7 +283,7 @@ public:
ErrorOr<StackEntry, ValidationError> take(ValueType type, SourceLocation location = SourceLocation::current())
{
auto type_on_stack = TRY(take_last());
if (type_on_stack != type)
if (type_on_stack.is_known && !matches_value_type(type_on_stack.concrete_type, type, m_type_context))
return Errors::invalid("stack state"sv, type, type_on_stack, location);
return type_on_stack;
@ -309,6 +315,7 @@ public:
private:
Vector<StackEntry, 8> m_entries;
Vector<Frame, 16> const& m_frames;
TypeContext m_type_context;
size_t m_max_known_size { 0 };
};
@ -353,6 +360,12 @@ private:
{
}
ErrorOr<void, ValidationError> validate_struct_get(Stack&, Instruction const&, bool requires_packed);
ErrorOr<FieldType, ValidationError> array_field_type(TypeIndex, StringView instruction_name, bool requires_mutable);
ErrorOr<void, ValidationError> validate_array_get(Stack&, Instruction const&, bool requires_packed);
ErrorOr<void, ValidationError> validate_ref_test_or_cast(Stack&, Instruction const&);
ErrorOr<void, ValidationError> validate_br_on_cast(Stack&, Instruction const&, bool branch_on_failure);
struct Errors {
static ValidationError invalid(StringView name, SourceLocation location = SourceLocation::current())
{
@ -420,8 +433,30 @@ private:
static ByteString find_instruction_name(SourceLocation const&);
};
// https://webassembly.github.io/spec/core/valid/conventions.html#local-types
void push_frame(Frame frame)
{
frame.local_init_log_height = m_local_init_log.size();
m_frames.append(move(frame));
m_max_frame_size = max(m_max_frame_size, m_frames.size());
}
void mark_local_initialized(u32 local_index)
{
if (m_local_initialized[local_index])
return;
m_local_initialized[local_index] = true;
m_local_init_log.append(local_index);
}
void roll_back_local_initializations(size_t log_height)
{
while (m_local_init_log.size() > log_height)
m_local_initialized[m_local_init_log.take_last()] = false;
}
Context m_context;
Vector<Frame, 16> m_frames;
Vector<bool> m_local_initialized;
Vector<u32> m_local_init_log;
size_t m_max_frame_size { 0 };
COWVector<GlobalType> m_globals_without_internal_globals;
};

View file

@ -205,7 +205,11 @@ namespace Instructions {
M(i64_extend32_s, 0xc4, 1, 1) \
M(ref_null, 0xd0, 0, 1) \
M(ref_is_null, 0xd1, 1, 1) \
M(ref_func, 0xd2, 0, 1)
M(ref_func, 0xd2, 0, 1) \
M(ref_eq, 0xd3, 2, 1) \
M(ref_as_non_null, 0xd4, 1, 1) \
M(br_on_null, 0xd5, 1, -1) \
M(br_on_non_null, 0xd6, 1, -1)
// These are synthetic opcodes, they are _not_ seen in wasm with these values.
#define ENUMERATE_MULTI_BYTE_WASM_OPCODES(M) \
@ -483,6 +487,37 @@ namespace Instructions {
M(i16x8_relaxed_q15mulr_s, 0xfd000111u, 2, 1) \
M(i16x8_relaxed_dot_i8x16_i7x16_s, 0xfd000112u, 2, 1) \
M(i32x4_relaxed_dot_i8x16_i7x16_add_s, 0xfd000113u, 3, 1) \
M(struct_new, 0xfb000000u, -1, 1) \
M(struct_new_default, 0xfb000001u, 0, 1) \
M(struct_get, 0xfb000002u, 1, 1) \
M(struct_get_s, 0xfb000003u, 1, 1) \
M(struct_get_u, 0xfb000004u, 1, 1) \
M(struct_set, 0xfb000005u, 2, 0) \
M(array_new, 0xfb000006u, 2, 1) \
M(array_new_default, 0xfb000007u, 1, 1) \
M(array_new_fixed, 0xfb000008u, -1, 1) \
M(array_new_data, 0xfb000009u, 2, 1) \
M(array_new_elem, 0xfb00000au, 2, 1) \
M(array_get, 0xfb00000bu, 2, 1) \
M(array_get_s, 0xfb00000cu, 2, 1) \
M(array_get_u, 0xfb00000du, 2, 1) \
M(array_set, 0xfb00000eu, 3, 0) \
M(array_len, 0xfb00000fu, 1, 1) \
M(array_fill, 0xfb000010u, -1, 0) \
M(array_copy, 0xfb000011u, -1, 0) \
M(array_init_data, 0xfb000012u, -1, 0) \
M(array_init_elem, 0xfb000013u, -1, 0) \
M(ref_test, 0xfb000014u, 1, 1) \
M(ref_test_null, 0xfb000015u, 1, 1) \
M(ref_cast, 0xfb000016u, 1, 1) \
M(ref_cast_null, 0xfb000017u, 1, 1) \
M(br_on_cast, 0xfb000018u, 1, -1) \
M(br_on_cast_fail, 0xfb000019u, 1, -1) \
M(any_convert_extern, 0xfb00001au, 1, 1) \
M(extern_convert_any, 0xfb00001bu, 1, 1) \
M(ref_i31, 0xfb00001cu, 1, 1) \
M(i31_get_s, 0xfb00001du, 1, 1) \
M(i31_get_u, 0xfb00001eu, 1, 1) \
/* Synthetic fused insns */ \
ENUMERATE_SYNTHETIC_INSTRUCTION_OPCODES(M)

View file

@ -454,6 +454,14 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
auto index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Instruction { opcode, BranchArgs { index } };
}
// https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions
// 0xD5 l:labelidx => br_on_null l
// 0xD6 l:labelidx => br_on_non_null l
case Instructions::br_on_null.value():
case Instructions::br_on_non_null.value(): {
auto index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Instruction { opcode, BranchArgs { index } };
}
case Instructions::br_table.value(): {
// br_table label* label
auto labels = TRY(parse_vector<GenericIndexParser<LabelIndex>>(stream));
@ -590,6 +598,11 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
case Instructions::structured_end.value():
case Instructions::structured_else.value():
case Instructions::ref_is_null.value():
// https://webassembly.github.io/spec/core/binary/instructions.html#reference-instructions
// 0xD3 => ref.eq
// 0xD4 => ref.as_non_null
case Instructions::ref_eq.value():
case Instructions::ref_as_non_null.value():
case Instructions::unreachable.value():
case Instructions::nop.value():
case Instructions::return_.value():
@ -724,6 +737,7 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
case Instructions::i64_extend16_s.value():
case Instructions::i64_extend32_s.value():
return Instruction { opcode };
case 0xfb:
case 0xfc:
case 0xfd: {
// These are multibyte instructions.
@ -790,6 +804,123 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
auto index = TRY(GenericIndexParser<TableIndex>::parse(stream));
return Instruction { full_opcode, index };
}
// https://webassembly.github.io/spec/core/binary/instructions.html#aggregate-instructions
// instr ::= ...
// | 0xFB 0:u32 x:typeidx => struct.new x
// | 0xFB 1:u32 x:typeidx => struct.new_default x
case Instructions::struct_new.value():
case Instructions::struct_new_default.value(): {
auto type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
return Instruction { full_opcode, type_index };
}
// | 0xFB 2:u32 x:typeidx i:fieldidx => struct.get x i
// | 0xFB 3:u32 x:typeidx i:fieldidx => struct.get_s x i
// | 0xFB 4:u32 x:typeidx i:fieldidx => struct.get_u x i
// | 0xFB 5:u32 x:typeidx i:fieldidx => struct.set x i
case Instructions::struct_get.value():
case Instructions::struct_get_s.value():
case Instructions::struct_get_u.value():
case Instructions::struct_set.value(): {
auto type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
u32 field_index = TRY_READ(stream, LEB128<u32>, ParseError::ExpectedIndex);
return Instruction { full_opcode, StructFieldArgs { type_index, field_index } };
}
// | 0xFB 6:u32 x:typeidx => array.new x
// | 0xFB 7:u32 x:typeidx => array.new_default x
// | 0xFB 11:u32 x:typeidx => array.get x
// | 0xFB 12:u32 x:typeidx => array.get_s x
// | 0xFB 13:u32 x:typeidx => array.get_u x
// | 0xFB 14:u32 x:typeidx => array.set x
// | 0xFB 16:u32 x:typeidx => array.fill x
case Instructions::array_new.value():
case Instructions::array_new_default.value():
case Instructions::array_get.value():
case Instructions::array_get_s.value():
case Instructions::array_get_u.value():
case Instructions::array_set.value():
case Instructions::array_fill.value(): {
auto type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
return Instruction { full_opcode, type_index };
}
// | 0xFB 8:u32 x:typeidx n:u32 => array.new_fixed x n
case Instructions::array_new_fixed.value(): {
auto type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
u32 count = TRY_READ(stream, LEB128<u32>, ParseError::InvalidImmediate);
return Instruction { full_opcode, ArrayNewFixedArgs { type_index, count } };
}
// | 0xFB 9:u32 x:typeidx y:dataidx => array.new_data x y
// | 0xFB 18:u32 x:typeidx y:dataidx => array.init_data x y
case Instructions::array_new_data.value():
case Instructions::array_init_data.value(): {
auto type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
auto data_index = TRY(GenericIndexParser<DataIndex>::parse(stream));
return Instruction { full_opcode, ArrayDataArgs { type_index, data_index } };
}
// | 0xFB 10:u32 x:typeidx y:elemidx => array.new_elem x y
// | 0xFB 19:u32 x:typeidx y:elemidx => array.init_elem x y
case Instructions::array_new_elem.value():
case Instructions::array_init_elem.value(): {
auto type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
auto element_index = TRY(GenericIndexParser<ElementIndex>::parse(stream));
return Instruction { full_opcode, ArrayElemArgs { type_index, element_index } };
}
// | 0xFB 17:u32 x_1:typeidx x_2:typeidx => array.copy x_1 x_2
case Instructions::array_copy.value(): {
auto destination_type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
auto source_type_index = TRY(GenericIndexParser<TypeIndex>::parse(stream));
return Instruction { full_opcode, ArrayCopyArgs { destination_type_index, source_type_index } };
}
// https://webassembly.github.io/spec/core/binary/instructions.html#reference-instructions
// instr ::= ...
// | 0xFB 20:u32 ht:heaptype => ref.test (ref ht)
// | 0xFB 21:u32 ht:heaptype => ref.test (ref null ht)
// | 0xFB 22:u32 ht:heaptype => ref.cast (ref ht)
// | 0xFB 23:u32 ht:heaptype => ref.cast (ref null ht)
case Instructions::ref_test.value():
case Instructions::ref_test_null.value():
case Instructions::ref_cast.value():
case Instructions::ref_cast_null.value(): {
auto type = TRY(parse_heap_type(stream));
type.set_nullable(full_opcode == Instructions::ref_test_null || full_opcode == Instructions::ref_cast_null);
return Instruction { full_opcode, type };
}
// https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions
// instr ::= ...
// | 0xFB 24:u32 (null_1?, null_2?):castop l:labelidx ht_1:heaptype ht_2:heaptype
// => br_on_cast l (ref null_1? ht_1) (ref null_2? ht_2)
// | 0xFB 25:u32 (null_1?, null_2?):castop l:labelidx ht_1:heaptype ht_2:heaptype
// => br_on_cast_fail l (ref null_1? ht_1) (ref null_2? ht_2)
// castop ::= 0x00 => (ε, ε)
// | 0x01 => (null, ε)
// | 0x02 => (ε, null)
// | 0x03 => (null, null)
case Instructions::br_on_cast.value():
case Instructions::br_on_cast_fail.value(): {
auto cast_op = TRY_READ(stream, u8, ParseError::InvalidImmediate);
if (cast_op > 3)
return ParseError::InvalidImmediate;
auto label = TRY(GenericIndexParser<LabelIndex>::parse(stream));
auto source_type = TRY(parse_heap_type(stream));
source_type.set_nullable((cast_op & 0b01) != 0);
auto target_type = TRY(parse_heap_type(stream));
target_type.set_nullable((cast_op & 0b10) != 0);
return Instruction { full_opcode, BranchOnCastArgs { BranchArgs { label }, source_type, target_type } };
}
// https://webassembly.github.io/spec/core/binary/instructions.html#aggregate-instructions
// instr ::= ...
// | 0xFB 15:u32 => array.len
// | 0xFB 26:u32 => any.convert_extern
// | 0xFB 27:u32 => extern.convert_any
// | 0xFB 28:u32 => ref.i31
// | 0xFB 29:u32 => i31.get_s
// | 0xFB 30:u32 => i31.get_u
case Instructions::array_len.value():
case Instructions::any_convert_extern.value():
case Instructions::extern_convert_any.value():
case Instructions::ref_i31.value():
case Instructions::i31_get_s.value():
case Instructions::i31_get_u.value():
return Instruction { full_opcode };
case Instructions::v128_load.value():
case Instructions::v128_load8x8_s.value():
case Instructions::v128_load8x8_u.value():
@ -1239,18 +1370,42 @@ ParseResult<FunctionSection> FunctionSection::parse(ConstrainedStream& stream)
return FunctionSection { move(typed_indices) };
}
// https://webassembly.github.io/spec/core/binary/modules.html#table-section
// table ::= tt:tabletype => table tt (ref.null ht) (if tt = at lim (ref null? ht))
// | 0x40 0x00 tt:tabletype e:expr => table tt e
ParseResult<TableSection::Table> TableSection::Table::parse(ConstrainedStream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("Table"sv);
auto type = TRY(TableType::parse(stream));
return Table { type };
auto tag = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
if (tag == 0x40) {
auto reserved = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
if (reserved != 0x00)
return ParseError::InvalidTag;
auto type = TRY(TableType::parse(stream));
auto initializer = TRY(Expression::parse(stream));
return Table { type, move(initializer) };
}
auto element_type = TRY(parse_reference_type(stream, tag));
auto limits = TRY(Limits::parse(stream));
auto type = TableType { element_type, limits };
// Synthesize the implicit initializer (ref.null ht).
auto null_element = element_type;
null_element.set_nullable(true);
Vector<Instruction> instructions {
Instruction(Instructions::ref_null, null_element),
Instruction(Instructions::synthetic_end_expression),
};
return Table { type, Expression { move(instructions) } };
}
ParseResult<TableSection> TableSection::parse(ConstrainedStream& stream)
{
ScopeLogger<WASM_BINPARSER_DEBUG> logger("TableSection"sv);
auto tables = TRY(parse_vector<Table>(stream));
return TableSection { tables };
return TableSection { move(tables) };
}
ParseResult<MemorySection::Memory> MemorySection::Memory::parse(ConstrainedStream& stream)

View file

@ -535,6 +535,17 @@ void Printer::print(Wasm::Instruction const& instruction)
},
[&](Instruction::TableElementArgs const& args) { print("(table_element (table index {}) (element index {}))", args.table_index.value(), args.element_index.value()); },
[&](Instruction::TableTableArgs const& args) { print("(table_table (table index {}) (table index {}))", args.lhs.value(), args.rhs.value()); },
[&](Instruction::StructFieldArgs const& args) { print("(type index {}) (field index {})", args.type_index.value(), args.field_index); },
[&](Instruction::ArrayNewFixedArgs const& args) { print("(type index {}) (count {})", args.type_index.value(), args.count); },
[&](Instruction::ArrayDataArgs const& args) { print("(type index {}) (data index {})", args.type_index.value(), args.data_index.value()); },
[&](Instruction::ArrayElemArgs const& args) { print("(type index {}) (element index {})", args.type_index.value(), args.element_index.value()); },
[&](Instruction::ArrayCopyArgs const& args) { print("(to (type index {}) from (type index {}))", args.destination_type_index.value(), args.source_type_index.value()); },
[&](Instruction::BranchOnCastArgs const& args) {
print("(label index {}) (source (type {}{})) (target (type {}{}))",
args.branch.label.value(),
args.source_type.kind_name(), args.source_type.is_nullable() ? " nullable"sv : ""sv,
args.target_type.kind_name(), args.target_type.is_nullable() ? " nullable"sv : ""sv);
},
[&](ValueType const& type) { print(type); },
[&](Vector<ValueType> const&) { print("(types...)"); },
[&](auto const& value) { print("(const {})", value); });
@ -1046,6 +1057,10 @@ HashMap<Wasm::OpCode, ByteString>& Wasm::Names::instruction_names = *new HashMap
{ Instructions::ref_null, "ref.null" },
{ Instructions::ref_is_null, "ref.is.null" },
{ Instructions::ref_func, "ref.func" },
{ Instructions::ref_eq, "ref.eq" },
{ Instructions::ref_as_non_null, "ref.as_non_null" },
{ Instructions::br_on_null, "br_on_null" },
{ Instructions::br_on_non_null, "br_on_non_null" },
{ Instructions::i32_trunc_sat_f32_s, "i32.trunc_sat_f32_s" },
{ Instructions::i32_trunc_sat_f32_u, "i32.trunc_sat_f32_u" },
{ Instructions::i32_trunc_sat_f64_s, "i32.trunc_sat_f64_s" },
@ -1320,6 +1335,37 @@ HashMap<Wasm::OpCode, ByteString>& Wasm::Names::instruction_names = *new HashMap
{ Instructions::i16x8_relaxed_q15mulr_s, "i16x8.relaxed_q15mulr_s" },
{ Instructions::i16x8_relaxed_dot_i8x16_i7x16_s, "i16x8.relaxed_dot_i8x16_i7x16_s" },
{ Instructions::i32x4_relaxed_dot_i8x16_i7x16_add_s, "i32x4.relaxed_dot_i8x16_i7x16_add_s" },
{ Instructions::struct_new, "struct.new" },
{ Instructions::struct_new_default, "struct.new_default" },
{ Instructions::struct_get, "struct.get" },
{ Instructions::struct_get_s, "struct.get_s" },
{ Instructions::struct_get_u, "struct.get_u" },
{ Instructions::struct_set, "struct.set" },
{ Instructions::array_new, "array.new" },
{ Instructions::array_new_default, "array.new_default" },
{ Instructions::array_new_fixed, "array.new_fixed" },
{ Instructions::array_new_data, "array.new_data" },
{ Instructions::array_new_elem, "array.new_elem" },
{ Instructions::array_get, "array.get" },
{ Instructions::array_get_s, "array.get_s" },
{ Instructions::array_get_u, "array.get_u" },
{ Instructions::array_set, "array.set" },
{ Instructions::array_len, "array.len" },
{ Instructions::array_fill, "array.fill" },
{ Instructions::array_copy, "array.copy" },
{ Instructions::array_init_data, "array.init_data" },
{ Instructions::array_init_elem, "array.init_elem" },
{ Instructions::ref_test, "ref.test" },
{ Instructions::ref_test_null, "ref.test null" },
{ Instructions::ref_cast, "ref.cast" },
{ Instructions::ref_cast_null, "ref.cast null" },
{ Instructions::br_on_cast, "br_on_cast" },
{ Instructions::br_on_cast_fail, "br_on_cast_fail" },
{ Instructions::any_convert_extern, "any.convert_extern" },
{ Instructions::extern_convert_any, "extern.convert_any" },
{ Instructions::ref_i31, "ref.i31" },
{ Instructions::i31_get_s, "i31.get_s" },
{ Instructions::i31_get_u, "i31.get_u" },
{ Instructions::structured_else, "synthetic:else" },
{ Instructions::structured_end, "synthetic:end" },
{ Instructions::synthetic_i32_add2local, "synthetic:i32.add2local" },

View file

@ -320,7 +320,7 @@ bool matches_defined_type(DefinedType const& defined_type1, DefinedType const& d
}
// https://webassembly.github.io/spec/core/syntax/types.html#heap-types
static ValueType::Kind top_of_heap_type(ValueType const& type, TypeContext const& context)
ValueType::Kind top_of_heap_type(ValueType const& type, TypeContext const& context)
{
switch (type.kind()) {
case ValueType::AnyReference:

View file

@ -67,6 +67,7 @@ WASM_API DefinedType const* canonicalize_type(TypeSection::Type const&, TypeCont
WASM_API ValueType canonicalized(ValueType, TypeContext const&);
// https://webassembly.github.io/spec/core/valid/matching.html
WASM_API ValueType::Kind top_of_heap_type(ValueType const&, TypeContext const&);
WASM_API bool matches_heap_type(ValueType const& heap_type1, ValueType const& heap_type2, TypeContext const&);
WASM_API bool matches_reference_type(ValueType const& reference_type1, ValueType const& reference_type2, TypeContext const&);
WASM_API bool matches_value_type(ValueType const& value_type1, ValueType const& value_type2, TypeContext const&);

View file

@ -717,6 +717,38 @@ public:
MemoryIndex memory_index;
};
// Proposal "gc"
struct StructFieldArgs {
TypeIndex type_index;
u32 field_index;
};
struct ArrayNewFixedArgs {
TypeIndex type_index;
u32 count;
};
struct ArrayDataArgs {
TypeIndex type_index;
DataIndex data_index;
};
struct ArrayElemArgs {
TypeIndex type_index;
ElementIndex element_index;
};
struct ArrayCopyArgs {
TypeIndex destination_type_index;
TypeIndex source_type_index;
};
struct BranchOnCastArgs {
BranchArgs branch;
ValueType source_type; // Nullability carries the castop null_1? flag.
ValueType target_type; // Nullability carries the castop null_2? flag.
};
// Proposal "exception-handling"
struct TryTableArgs : StructuredInstructionArgsBase<OwnPtr<FixedArray<Catch>>> {
using Base = StructuredInstructionArgsBase<OwnPtr<FixedArray<Catch>>>;
@ -818,8 +850,13 @@ private:
LocalIndex m_local_index;
Variant<
ArrayCopyArgs,
ArrayDataArgs,
ArrayElemArgs,
ArrayNewFixedArgs,
BlockType,
BranchArgs,
BranchOnCastArgs,
DataIndex,
ElementIndex,
FunctionIndex,
@ -834,6 +871,7 @@ private:
MemoryCopyArgs,
MemoryIndexArgument,
MemoryInitArgs,
StructFieldArgs,
StructuredInstructionArgs,
ShuffleArgument,
TableBranchArgs,
@ -1212,21 +1250,48 @@ private:
Vector<TypeIndex> m_types;
};
class Expression {
public:
explicit Expression(Vector<Instruction> instructions)
: m_instructions(move(instructions))
{
}
auto& instructions() const { return m_instructions; }
static ParseResult<Expression> parse(ConstrainedStream& stream, Optional<size_t> size_hint = {});
void set_stack_usage_hint(size_t value) const { m_stack_usage_hint = value; }
auto stack_usage_hint() const { return m_stack_usage_hint; }
void set_frame_usage_hint(size_t value) const { m_frame_usage_hint = value; }
auto frame_usage_hint() const { return m_frame_usage_hint; }
mutable CompiledInstructions compiled_instructions;
private:
Vector<Instruction> m_instructions;
mutable Optional<size_t> m_stack_usage_hint;
mutable Optional<size_t> m_frame_usage_hint;
};
class TableSection {
public:
class Table {
public:
explicit Table(TableType type)
explicit Table(TableType type, Expression initializer)
: m_type(move(type))
, m_initializer(move(initializer))
{
}
auto& type() const { return m_type; }
auto& initializer() const { return m_initializer; }
static ParseResult<Table> parse(ConstrainedStream& stream);
private:
TableType m_type;
Expression m_initializer;
};
public:
@ -1278,30 +1343,6 @@ private:
Vector<Memory> m_memories;
};
class Expression {
public:
explicit Expression(Vector<Instruction> instructions)
: m_instructions(move(instructions))
{
}
auto& instructions() const { return m_instructions; }
static ParseResult<Expression> parse(ConstrainedStream& stream, Optional<size_t> size_hint = {});
void set_stack_usage_hint(size_t value) const { m_stack_usage_hint = value; }
auto stack_usage_hint() const { return m_stack_usage_hint; }
void set_frame_usage_hint(size_t value) const { m_frame_usage_hint = value; }
auto frame_usage_hint() const { return m_frame_usage_hint; }
mutable CompiledInstructions compiled_instructions;
private:
Vector<Instruction> m_instructions;
mutable Optional<size_t> m_stack_usage_hint;
mutable Optional<size_t> m_frame_usage_hint;
};
class GlobalSection {
public:
class Global {

View file

@ -740,7 +740,8 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
dbgln("[wasm runtime] Cannot stub tag import {}::{}: type is not a function", entry.module, entry.name);
return;
}
address = *machine.store().allocate(type.function(), tag_type.flags());
// The module is not yet validated here, so its canonical types may not be known.
address = *machine.store().allocate(type.function(), nullptr, tag_type.flags());
});
if (address.has_value())