From 62cb073ada4757654a331feeb3ffd9b711049bfc Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Thu, 11 Jun 2026 07:15:32 +0200 Subject: [PATCH] LibWasm: Validate the wasm-gc and function-references instructions Actual implementations are still trap-on-exec. --- .../AbstractMachine/AbstractMachine.cpp | 32 +- .../LibWasm/AbstractMachine/AbstractMachine.h | 2 +- .../AbstractMachine/BytecodeInterpreter.cpp | 47 ++ .../LibWasm/AbstractMachine/Validator.cpp | 595 +++++++++++++++++- Libraries/LibWasm/AbstractMachine/Validator.h | 41 +- Libraries/LibWasm/Opcode.h | 37 +- Libraries/LibWasm/Parser/Parser.cpp | 161 ++++- Libraries/LibWasm/Printer/Printer.cpp | 46 ++ Libraries/LibWasm/TypeSystem.cpp | 2 +- Libraries/LibWasm/TypeSystem.h | 1 + Libraries/LibWasm/Types.h | 91 ++- Utilities/wasm.cpp | 3 +- 12 files changed, 1003 insertions(+), 55 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 0330d6ded3..4358156c17 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -606,7 +606,23 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector 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 {}, + 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 AbstractMachine::allocate_all_initial_phase(Module const& module, ModuleInstance& module_instance, Vector& externs, Vector& global_values, Vector& own_functions) +Optional AbstractMachine::allocate_all_initial_phase(Module const& module, ModuleInstance& module_instance, Vector& externs, Vector& global_values, Vector& table_initial_values, Vector& own_functions) { Optional result; @@ -771,11 +787,21 @@ Optional 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(); + if (!reference.ref().has()) { + auto* table_instance = m_store.get(*table_address); + RefPtr anchor; + if (reference.ref().has()) + 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()) { diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index ba8570f6d6..22b2d2a3fe 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -856,7 +856,7 @@ private: return InterpreterHandle(*this, interpreter); } - Optional allocate_all_initial_phase(Module const&, ModuleInstance&, Vector&, Vector& global_values, Vector& own_functions); + Optional allocate_all_initial_phase(Module const&, ModuleInstance&, Vector&, Vector& global_values, Vector& table_initial_values, Vector& own_functions); Optional allocate_all_final_phase(Module const&, ModuleInstance&, Vector>& elements); Store m_store; StackInfo m_stack_info; diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index 2b20b7b9b2..7e641c2737 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -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); diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index 0963058cfd..6d41e9c134 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -332,9 +332,10 @@ ErrorOr Validator::validate(ElementSection const& section [](ElementSection::Declarative const&) -> ErrorOr { return {}; }, [](ElementSection::Passive const&) -> ErrorOr { return {}; }, [&](ElementSection::Active const& active) -> ErrorOr { + // 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 Validator::validate(ElementSection const& section ErrorOr 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 Validator::validate(MemorySection const& section) return {}; } +// https://webassembly.github.io/spec/core/valid/modules.html#tables ErrorOr 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 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); } +// 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(); + 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(); + 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(); + 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(); + 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 Validator::validate_struct_get(Stack& stack, Instruction const& instruction, bool requires_packed) +{ + auto& args = instruction.arguments().get(); + 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(); + 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 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(); + auto field = TRY(array_field_type(type_index, "array.new type"sv, false)); + + TRY(stack.take()); + 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(); + 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()); + 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(); + 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(); + 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())); + 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(); + 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())); + stack.append(ValueType(ValueType::TypeUseReference, args.type_index, false)); + return {}; +} + +// https://webassembly.github.io/spec/core/valid/instructions.html#aggregate-reference-instructions +ErrorOr Validator::validate_array_get(Stack& stack, Instruction const& instruction, bool requires_packed) +{ + auto type_index = instruction.arguments().get(); + 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()); + 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(); + auto field = TRY(array_field_type(type_index, "array.set type"sv, true)); + + TRY(stack.take(field.type().unpacked())); + TRY(stack.take()); + 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(); + auto field = TRY(array_field_type(type_index, "array.fill type"sv, true)); + + TRY(stack.take()); + TRY(stack.take(field.type().unpacked())); + TRY(stack.take()); + 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(); + 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())); + TRY(stack.take(ValueType(ValueType::TypeUseReference, args.source_type_index))); + TRY(stack.take()); + 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(); + 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())); + 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(); + 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())); + TRY(stack.take(ValueType(ValueType::TypeUseReference, args.type_index))); + return {}; +} + +// https://webassembly.github.io/spec/core/valid/instructions.html#reference-instructions +ErrorOr Validator::validate_ref_test_or_cast(Stack& stack, Instruction const& instruction) +{ + auto type = instruction.arguments().get(); + 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()); + return {}; +} + +VALIDATE_INSTRUCTION(ref_cast_null) +{ + TRY(validate_ref_test_or_cast(stack, instruction)); + stack.append(instruction.arguments().get()); + return {}; +} + +// https://webassembly.github.io/spec/core/valid/instructions.html#control-instructions +ErrorOr Validator::validate_br_on_cast(Stack& stack, Instruction const& instruction, bool branch_on_failure) +{ + auto& args = instruction.arguments().get(); + 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()); + 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::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()) { diff --git a/Libraries/LibWasm/AbstractMachine/Validator.h b/Libraries/LibWasm/AbstractMachine/Validator.h index 5c217c7a4b..d7f398888a 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.h +++ b/Libraries/LibWasm/AbstractMachine/Validator.h @@ -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 const& labels() const { @@ -241,13 +244,16 @@ public: friend struct AK::Formatter; public: - explicit Stack(Vector&&) = delete; + explicit Stack(Vector&&, TypeContext const&) = delete; - explicit Stack(Vector const& frames) + explicit Stack(Vector 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 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 m_entries; Vector const& m_frames; + TypeContext m_type_context; size_t m_max_known_size { 0 }; }; @@ -353,6 +360,12 @@ private: { } + ErrorOr validate_struct_get(Stack&, Instruction const&, bool requires_packed); + ErrorOr array_field_type(TypeIndex, StringView instruction_name, bool requires_mutable); + ErrorOr validate_array_get(Stack&, Instruction const&, bool requires_packed); + ErrorOr validate_ref_test_or_cast(Stack&, Instruction const&); + ErrorOr 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 m_frames; + Vector m_local_initialized; + Vector m_local_init_log; size_t m_max_frame_size { 0 }; COWVector m_globals_without_internal_globals; }; diff --git a/Libraries/LibWasm/Opcode.h b/Libraries/LibWasm/Opcode.h index 8b708fa5c5..8af411f4d3 100644 --- a/Libraries/LibWasm/Opcode.h +++ b/Libraries/LibWasm/Opcode.h @@ -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) diff --git a/Libraries/LibWasm/Parser/Parser.cpp b/Libraries/LibWasm/Parser/Parser.cpp index af7339804a..538748c65c 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -454,6 +454,14 @@ ParseResult Instruction::parse(ConstrainedStream& stream) auto index = TRY(GenericIndexParser::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::parse(stream)); + return Instruction { opcode, BranchArgs { index } }; + } case Instructions::br_table.value(): { // br_table label* label auto labels = TRY(parse_vector>(stream)); @@ -590,6 +598,11 @@ ParseResult 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::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::parse(ConstrainedStream& stream) auto index = TRY(GenericIndexParser::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::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::parse(stream)); + u32 field_index = TRY_READ(stream, LEB128, 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::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::parse(stream)); + u32 count = TRY_READ(stream, LEB128, 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::parse(stream)); + auto data_index = TRY(GenericIndexParser::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::parse(stream)); + auto element_index = TRY(GenericIndexParser::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::parse(stream)); + auto source_type_index = TRY(GenericIndexParser::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::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::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::parse(ConstrainedStream& stream) { ScopeLogger 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 instructions { + Instruction(Instructions::ref_null, null_element), + Instruction(Instructions::synthetic_end_expression), + }; + return Table { type, Expression { move(instructions) } }; } ParseResult TableSection::parse(ConstrainedStream& stream) { ScopeLogger logger("TableSection"sv); auto tables = TRY(parse_vector(stream)); - return TableSection { tables }; + return TableSection { move(tables) }; } ParseResult MemorySection::Memory::parse(ConstrainedStream& stream) diff --git a/Libraries/LibWasm/Printer/Printer.cpp b/Libraries/LibWasm/Printer/Printer.cpp index 8bc4d9e432..a6c9957cdc 100644 --- a/Libraries/LibWasm/Printer/Printer.cpp +++ b/Libraries/LibWasm/Printer/Printer.cpp @@ -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 const&) { print("(types...)"); }, [&](auto const& value) { print("(const {})", value); }); @@ -1046,6 +1057,10 @@ HashMap& 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::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" }, diff --git a/Libraries/LibWasm/TypeSystem.cpp b/Libraries/LibWasm/TypeSystem.cpp index 52afccb403..6a0cceaea7 100644 --- a/Libraries/LibWasm/TypeSystem.cpp +++ b/Libraries/LibWasm/TypeSystem.cpp @@ -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: diff --git a/Libraries/LibWasm/TypeSystem.h b/Libraries/LibWasm/TypeSystem.h index 270f4afec8..670117d277 100644 --- a/Libraries/LibWasm/TypeSystem.h +++ b/Libraries/LibWasm/TypeSystem.h @@ -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&); diff --git a/Libraries/LibWasm/Types.h b/Libraries/LibWasm/Types.h index f57b34d0a2..b8f90039c4 100644 --- a/Libraries/LibWasm/Types.h +++ b/Libraries/LibWasm/Types.h @@ -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>> { using Base = StructuredInstructionArgsBase>>; @@ -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 m_types; }; +class Expression { +public: + explicit Expression(Vector instructions) + : m_instructions(move(instructions)) + { + } + + auto& instructions() const { return m_instructions; } + + static ParseResult parse(ConstrainedStream& stream, Optional 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 m_instructions; + mutable Optional m_stack_usage_hint; + mutable Optional 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
parse(ConstrainedStream& stream); private: TableType m_type; + Expression m_initializer; }; public: @@ -1278,30 +1343,6 @@ private: Vector m_memories; }; -class Expression { -public: - explicit Expression(Vector instructions) - : m_instructions(move(instructions)) - { - } - - auto& instructions() const { return m_instructions; } - - static ParseResult parse(ConstrainedStream& stream, Optional 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 m_instructions; - mutable Optional m_stack_usage_hint; - mutable Optional m_frame_usage_hint; -}; - class GlobalSection { public: class Global { diff --git a/Utilities/wasm.cpp b/Utilities/wasm.cpp index 519e4b865d..95e14ffbb1 100644 --- a/Utilities/wasm.cpp +++ b/Utilities/wasm.cpp @@ -740,7 +740,8 @@ ErrorOr 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())