From 921373a045a706b72ab71c68b2b8fa7d6d0eec77 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Fri, 23 Jan 2026 12:30:50 +0100 Subject: [PATCH] LibWasm: Implement call argument forwarding using call records --- .../AbstractMachine/AbstractMachine.cpp | 7 +- .../LibWasm/AbstractMachine/AbstractMachine.h | 24 +- .../AbstractMachine/BytecodeInterpreter.cpp | 452 ++++++++++++++++-- .../AbstractMachine/BytecodeInterpreter.h | 1 + .../LibWasm/AbstractMachine/Configuration.cpp | 11 +- .../LibWasm/AbstractMachine/Configuration.h | 86 +++- .../LibWasm/AbstractMachine/Validator.cpp | 18 + Libraries/LibWasm/AbstractMachine/Validator.h | 5 - Libraries/LibWasm/Opcode.h | 6 +- Libraries/LibWasm/Parser/Parser.cpp | 16 - Libraries/LibWasm/Printer/Printer.cpp | 30 +- Libraries/LibWasm/Types.h | 8 + Utilities/wasm.cpp | 18 +- 13 files changed, 546 insertions(+), 136 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index d6d491c322..add88f132e 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -188,6 +188,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector(); + main_module_instance_pointer->cached_minimum_call_record_allocation_size = module.minimum_call_record_allocation_size(); auto& main_module_instance = *main_module_instance_pointer; main_module_instance.types() = module.type_section().types(); @@ -196,6 +197,8 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector> elements; ModuleInstance auxiliary_instance; + auxiliary_instance.cached_minimum_call_record_allocation_size = module.minimum_call_record_allocation_size(); + for (auto [i, import_] : enumerate(module.import_section().imports())) { auto extern_ = externs.at(i); auto invalid = import_.description().visit( @@ -294,7 +297,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector {}, - Vector {}, entry.expression(), 1); auto result = config.execute(interpreter); @@ -316,7 +318,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector {}, - Vector {}, entry, entry.instructions().size() - 1); auto result = config.execute(interpreter); @@ -351,7 +352,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector {}, - Vector {}, active_ptr->expression, 1); auto result = config.execute(interpreter); @@ -386,7 +386,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector {}, - Vector {}, data.offset, 1); auto result = config.execute(interpreter); diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 8184725949..cb144326e4 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -18,7 +18,7 @@ namespace Wasm { -constexpr inline size_t ArgumentsStaticSize = 8; +constexpr inline size_t ArgumentsStaticSize = 3; class Configuration; class Result; @@ -320,8 +320,9 @@ private: class ModuleInstance { public: explicit ModuleInstance( - Vector types, Vector function_addresses, Vector table_addresses, Vector memory_addresses, Vector global_addresses, Vector data_addresses, Vector tag_addresses, Vector tag_types, Vector exports) - : m_types(move(types)) + Vector types, Vector function_addresses, Vector table_addresses, Vector memory_addresses, Vector global_addresses, Vector data_addresses, Vector tag_addresses, Vector tag_types, Vector exports, size_t minimum_call_record_allocation_size) + : cached_minimum_call_record_allocation_size(minimum_call_record_allocation_size) + , m_types(move(types)) , m_tag_types(move(tag_types)) , m_functions(move(function_addresses)) , m_tables(move(table_addresses)) @@ -357,6 +358,8 @@ public: auto& tags() { return m_tags; } auto& tag_types() { return m_tag_types; } + size_t cached_minimum_call_record_allocation_size { 0 }; + private: Vector m_types; Vector m_tag_types; @@ -674,9 +677,8 @@ private: class Frame { public: - explicit Frame(ModuleInstance const& module, Vector arguments, Vector locals, Expression const& expression, size_t arity) + explicit Frame(ModuleInstance const& module, Vector locals, Expression const& expression, size_t arity) : m_module(module) - , m_arguments(move(arguments)) , m_locals(move(locals)) , m_expression(expression) , m_arity(arity) @@ -686,24 +688,14 @@ public: auto& module() const { return m_module; } auto& locals() const { return m_locals; } auto& locals() { return m_locals; } - auto& arguments() const { return m_arguments; } - auto& arguments() { return m_arguments; } auto& expression() const { return m_expression; } auto arity() const { return m_arity; } auto label_index() const { return m_label_index; } auto& label_index() { return m_label_index; } - Value& local_or_argument(LocalIndex index) - { - if (index.value() & LocalArgumentMarker) - return m_arguments[index.value() & ~LocalArgumentMarker]; - return m_locals[index.value()]; - } - private: ModuleInstance const& m_module; - Vector m_arguments; - Vector m_locals; + Vector m_locals; Expression const& m_expression; size_t m_arity { 0 }; size_t m_label_index { 0 }; diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index 69f2455f6a..bdb91f7a46 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -172,12 +173,12 @@ static_assert(sizeof(ShortenedIPAndAddresses) == sizeof(u64)); static Outcome operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS)); \ }; \ template \ - Outcome InstructionHandler::operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS)) + FLATTEN Outcome InstructionHandler::operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS)) #define ALIAS_INSTRUCTION(new_name, existing_name) \ template<> \ struct InstructionHandler { \ template \ - static Outcome operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS)) \ + FLATTEN static Outcome operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS)) \ { \ TAILCALL return InstructionHandler::operator()( \ HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); \ @@ -1475,7 +1476,7 @@ HANDLE_INSTRUCTION(local_set) { LOG_INSN; // bounds checked by verifier. - configuration.local_or_argument(instruction->local_index()) = configuration.take_source(0, ip_and_addresses.addresses.sources); + configuration.local(instruction->local_index()) = configuration.take_source(0, ip_and_addresses.addresses.sources); TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } @@ -1639,6 +1640,28 @@ HANDLE_INSTRUCTION(call) TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } +HANDLE_INSTRUCTION(synthetic_call_with_record_0) +{ + LOG_INSN; + auto index = instruction->arguments().get(); + auto address = configuration.frame().module().functions()[index.value()]; + dbgln_if(WASM_TRACE_DEBUG, "call.with_record.0({})", address.value()); + if (interpreter.call_address(configuration, address, ip_and_addresses.addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingCallRecord) == Outcome::Return) + return Outcome::Return; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +HANDLE_INSTRUCTION(synthetic_call_with_record_1) +{ + LOG_INSN; + auto index = instruction->arguments().get(); + auto address = configuration.frame().module().functions()[index.value()]; + dbgln_if(WASM_TRACE_DEBUG, "call.with_record.1({})", address.value()); + if (interpreter.call_address(configuration, address, ip_and_addresses.addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingCallRecord) == Outcome::Return) + return Outcome::Return; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + HANDLE_INSTRUCTION(return_call) { LOG_INSN; @@ -1913,7 +1936,17 @@ HANDLE_INSTRUCTION(local_tee) auto value = configuration.source_value(0, ip_and_addresses.addresses.sources); // bounds checked by verifier. auto local_index = instruction->local_index(); dbgln_if(WASM_TRACE_DEBUG, "stack:peek -> locals({})", local_index.value()); - configuration.frame().local_or_argument(local_index) = value; + configuration.local(local_index) = value; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +HANDLE_INSTRUCTION(synthetic_argument_tee) +{ + LOG_INSN; + auto value = configuration.source_value(0, ip_and_addresses.addresses.sources); // bounds checked by verifier. + auto local_index = instruction->local_index(); + dbgln_if(WASM_TRACE_DEBUG, "stack:peek -> locals({})", local_index.value()); + configuration.local(local_index) = value; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } @@ -4668,7 +4701,7 @@ Outcome BytecodeInterpreter::call_address(Configuration& configuration, Function { Optional> regs_rollback; - if (call_type == CallType::UsingRegisters) + if (call_type == CallType::UsingRegisters || call_type == CallType::UsingCallRecord) regs_rollback = ScopedValueRollback { configuration.regs }; auto instance = configuration.store().get(address); @@ -4678,22 +4711,28 @@ Outcome BytecodeInterpreter::call_address(Configuration& configuration, Function TRAP_IF_NOT(type->parameters().size() <= configuration.value_stack().size()); } Vector args; - configuration.get_arguments_allocation_if_possible(args, type->parameters().size()); - { - auto param_count = type->parameters().size(); - if (param_count) { - args.ensure_capacity(param_count); - if (call_type == CallType::UsingRegisters) { - args.resize_with_default_value(param_count, Value(0)); - for (size_t i = 0; i < param_count; ++i) - args[param_count - i - 1] = configuration.take_source(i, addresses.sources); - } else { - auto span = configuration.value_stack().span().slice_from_end(param_count); - for (auto& value : span) - args.unchecked_append(value); + if (call_type == CallType::UsingCallRecord) { + configuration.take_call_record(args); + args.shrink(type->parameters().size(), true); + } else { + configuration.get_arguments_allocation_if_possible(args, type->parameters().size()); - configuration.value_stack().remove(configuration.value_stack().size() - span.size(), span.size()); + { + auto param_count = type->parameters().size(); + if (param_count) { + args.ensure_capacity(param_count); + if (call_type == CallType::UsingRegisters) { + args.resize_and_keep_capacity(param_count); + for (size_t i = 0; i < param_count; ++i) + args[param_count - i - 1] = configuration.take_source(i, addresses.sources); + } else { + auto span = configuration.value_stack().span().slice_from_end(param_count); + for (auto& value : span) + args.unchecked_append(value); + + configuration.value_stack().remove(configuration.value_stack().size() - span.size(), span.size()); + } } } } @@ -4730,7 +4769,7 @@ Outcome BytecodeInterpreter::call_address(Configuration& configuration, Function } if (!result.values().is_empty()) { - if (call_type == CallType::UsingRegisters) { + if (call_type == CallType::UsingRegisters || call_type == CallType::UsingCallRecord || result.values().size() == 1) { configuration.push_to_destination(result.values().take_first(), addresses.destination); } else { configuration.value_stack().ensure_capacity(configuration.value_stack().size() + result.values().size()); @@ -4888,6 +4927,8 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span } pattern_state { InsnPatternState::Nothing }; static Instruction nop { Instructions::nop }; + size_t calls_in_expression = 0; + auto const set_default_dispatch = [&result](Instruction const& instruction, size_t index = NumericLimits::max()) { if (index < result.dispatches.size()) { result.dispatches[index] = { { .instruction_opcode = instruction.opcode() }, &instruction }; @@ -4910,6 +4951,8 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span set_default_dispatch(result.extra_instruction_storage.unsafe_last()); continue; } + + calls_in_expression++; } switch (pattern_state) { @@ -5133,7 +5176,7 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span result.dispatches.remove_all(nops_to_remove, [](auto const& it) { return it.key(); }); result.src_dst_mappings.remove_all(nops_to_remove, [](auto const& it) { return it.key(); }); - // Rewrite local.get of arguments to argument.get to keep local.get for locals only. + // Rewrite local.* of arguments to argument.* to keep local.* for locals only. for (size_t i = 0; i < result.dispatches.size(); ++i) { auto& dispatch = result.dispatches[i]; if (dispatch.instruction->opcode() == Instructions::local_get) { @@ -5145,6 +5188,24 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span result.dispatches[i].instruction = &result.extra_instruction_storage.unsafe_last(); result.dispatches[i].instruction_opcode = result.dispatches[i].instruction->opcode(); } + } else if (dispatch.instruction->opcode() == Instructions::local_set) { + auto local_index = dispatch.instruction->local_index(); + if (local_index.value() & LocalArgumentMarker) { + result.extra_instruction_storage.unchecked_append(Instruction( + Instructions::synthetic_argument_set, + local_index)); + result.dispatches[i].instruction = &result.extra_instruction_storage.unsafe_last(); + result.dispatches[i].instruction_opcode = result.dispatches[i].instruction->opcode(); + } + } else if (dispatch.instruction->opcode() == Instructions::local_tee) { + auto local_index = dispatch.instruction->local_index(); + if (local_index.value() & LocalArgumentMarker) { + result.extra_instruction_storage.unchecked_append(Instruction( + Instructions::synthetic_argument_tee, + local_index)); + result.dispatches[i].instruction = &result.extra_instruction_storage.unsafe_last(); + result.dispatches[i].instruction_opcode = result.dispatches[i].instruction->opcode(); + } } } @@ -5161,6 +5222,7 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span IP definition_index; Vector uses; IP last_use = 0; + bool was_created_as_a_result_of_polymorphic_stack = false; }; struct ActiveReg { @@ -5230,6 +5292,20 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span Vector> live_at_instr; live_at_instr.resize(result.dispatches.size()); + // Track call record constraints + HashMap value_to_callrec_slot; + + struct CallInfo { + size_t call_index; + size_t param_count; + size_t result_count; + size_t earliest_arg_index; + Vector arg_values; + }; + Vector eligible_calls; + + eligible_calls.ensure_capacity(calls_in_expression); + for (size_t i = 0; i < result.dispatches.size(); ++i) { auto& dispatch = result.dispatches[i]; auto opcode = dispatch.instruction->opcode(); @@ -5238,16 +5314,16 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span Vector dependent_ids; bool variadic_or_unknown = false; + bool requires_aliased_destination = true; switch (opcode.value()) { #define M(name, _, ins, outs) \ case Instructions::name.value(): \ if constexpr (ins == -1 || outs == -1) { \ variadic_or_unknown = true; \ - } else { \ - inputs = ins; \ - outputs = outs; \ } \ + inputs = max(ins, 0); \ + outputs = max(outs, 0); \ break; ENUMERATE_WASM_OPCODES(M) #undef M @@ -5255,6 +5331,113 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span Vector input_ids; + if (opcode == Instructions::call) { + auto& type = functions[dispatch.instruction->arguments().get().value()]; + + if (type.parameters().size() <= (Dispatch::LastCallRecord - Dispatch::CallRecord + 1) + && type.results().size() <= 1 + && type.parameters().size() <= value_stack.size()) { + + inputs = type.parameters().size(); + outputs = type.results().size(); + variadic_or_unknown = false; + requires_aliased_destination = false; + + auto value_stack_copy = value_stack; + + for (size_t j = 0; j < inputs; ++j) { + auto input_value = value_stack.take_last(); + auto& value = values.get(input_value).value(); + + // if this value was created as a result of a polymorphic stack, + // we can't actually go and force it to a call record again, so disqualify this call. + if (value.was_created_as_a_result_of_polymorphic_stack) { + inputs = 0; + outputs = 0; + variadic_or_unknown = true; + value_stack = move(value_stack_copy); + goto avoid_optimizing_this_call; + } + + input_ids.append(input_value); + dependent_ids.append(input_value); + value.uses.append(i); + value.last_use = max(value.last_use, i); + forced_stack_values.append(input_value); + } + instr_to_input_values.set(i, input_ids); + instr_to_dependent_values.set(i, dependent_ids); + + for (size_t j = 0; j < outputs; ++j) { + auto id = next_value_id++; + values.set(id, Value { id, i, {}, i }); + value_stack.append(id); + instr_to_output_value.set(i, id); + ensure_id_space(id); + } + + size_t earliest = i; + ValueID earliest_arg_value = NumericLimits::max(); + for (auto value_id : input_ids) { + auto& value = values.get(value_id).value(); + if (earliest > value.definition_index.value()) { + earliest = value.definition_index.value(); + earliest_arg_value = value_id; + } + } + + // Reverse the input_ids to match stack order + Vector reversed_args; + for (size_t j = 0; j < inputs; ++j) { + reversed_args.append(input_ids[inputs - 1 - j]); + } + + // Follow the alias root of the earliest arg value to find the first instruction that produced it. + auto new_earliest = earliest; + while (true) { + auto maybe_inputs = instr_to_input_values.get(new_earliest); + if (!maybe_inputs.has_value()) + break; + bool found_earliest = false; + for (auto val : maybe_inputs.value()) { + auto root = find_root(val); + if (root == find_root(earliest_arg_value)) { + auto& value = values.get(val).value(); + if (value.definition_index.value() < new_earliest) { + new_earliest = value.definition_index.value(); + found_earliest = true; + break; + } + } + } + if (!found_earliest) + break; + } + + eligible_calls.append({ .call_index = i, + .param_count = inputs, + .result_count = outputs, + .earliest_arg_index = new_earliest, + .arg_values = reversed_args }); + + continue; + } + } + avoid_optimizing_this_call:; + + // Handle the inputs we actually know about. + size_t j = 0; + for (; j < inputs && !value_stack.is_empty(); ++j) { + auto input_value = value_stack.take_last(); + input_ids.append(input_value); + dependent_ids.append(input_value); + auto& value = values.get(input_value).value(); + value.uses.append(i); + value.last_use = max(value.last_use, i); + } + + inputs -= j; + if (variadic_or_unknown) { for (auto val : value_stack) { auto& value = values.get(val).value(); @@ -5267,7 +5450,7 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span value_stack.clear_with_capacity(); } - if (!variadic_or_unknown && value_stack.size() < inputs) { + if (value_stack.size() < inputs) { size_t j = 0; for (; j < inputs && !value_stack.is_empty(); ++j) { auto input_value = value_stack.take_last(); @@ -5280,7 +5463,7 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span for (; j < inputs; ++j) { auto val_id = next_value_id++; - values.set(val_id, Value { val_id, i, {}, i }); + values.set(val_id, Value { val_id, i, {}, i, true }); input_ids.append(val_id); forced_stack_values.append(val_id); ensure_id_space(val_id); @@ -5311,7 +5494,7 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span } // Alias the output with the last input, if one exists. - if (outputs > 0) { + if (outputs > 0 && requires_aliased_destination) { auto maybe_input_ids = instr_to_input_values.get(i); if (maybe_input_ids.has_value() && !maybe_input_ids->is_empty()) { auto last_input_id = maybe_input_ids->last(); @@ -5319,6 +5502,12 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span auto alias_root = find_root(last_input_id); + // If the last input was created as a result of polymorphic stack, propagate that to the output (as they're aliased). + auto& output_value = values.get(output_id).value(); + auto const& input_value = values.get(last_input_id).value(); + if (input_value.was_created_as_a_result_of_polymorphic_stack) + output_value.was_created_as_a_result_of_polymorphic_stack = true; + // If any *other* input is forced to alias the output, we have no choice but to place all three on the stack. for (size_t j = 0; j < maybe_input_ids->size() - 1; ++j) { auto input_root = find_root((*maybe_input_ids)[j]); @@ -5333,9 +5522,104 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span forced_stack_values.extend(value_stack); + // Build conflict graph and select maximum set of non-conflicting calls + // Prefer calls with more arguments, and among those with equal args, prefer shorter spans + + struct CallScore { + size_t index; + size_t param_count; + size_t span; + }; + + Vector scored_calls; + for (size_t i = 0; i < eligible_calls.size(); ++i) { + auto& call = eligible_calls[i]; + size_t span = call.call_index - call.earliest_arg_index; + scored_calls.append({ i, call.param_count, span }); + } + + // Sort by: more params first, then shorter span + quick_sort(scored_calls, [](auto const& a, auto const& b) { + if (a.param_count != b.param_count) + return a.param_count > b.param_count; + return a.span < b.span; + }); + + // Greedily select non-conflicting calls in priority order + Vector valid_calls; + HashTable selected_indices; + size_t max_call_record_size = 0; + + for (auto const& score : scored_calls) { + auto& call_info = eligible_calls[score.index]; + size_t call_start = call_info.earliest_arg_index; + size_t call_end = call_info.call_index; + + bool conflicts = false; + for (auto* other_call : valid_calls) { + size_t other_start = other_call->earliest_arg_index; + size_t other_end = other_call->call_index; + + // Check if the ranges overlap + // Two ranges [a,b] and [c,d] overlap if: NOT (b < c OR d < a) + if (!(call_end < other_start || other_end < call_start)) { + conflicts = true; + break; + } + } + + if (!conflicts) { + valid_calls.append(&call_info); + selected_indices.set(score.index); + max_call_record_size = max(max_call_record_size, call_info.param_count); + } + } + + // Only apply call record optimization to non-conflicting calls + HashTable calls_with_records; + for (auto* call_info : valid_calls) { + calls_with_records.set(call_info->call_index); + + // Mark values for call record slots + for (size_t j = 0; j < call_info->param_count; ++j) { + value_to_callrec_slot.set(call_info->arg_values[j], Dispatch::CallRecord + j); + } + + auto new_call_opcode = call_info->result_count == 0 + ? Instructions::synthetic_call_with_record_0 + : Instructions::synthetic_call_with_record_1; + + auto new_call_insn = Instruction( + new_call_opcode, + result.dispatches[call_info->call_index].instruction->arguments()); + + result.extra_instruction_storage.unchecked_append(new_call_insn); + result.dispatches[call_info->call_index].instruction = &result.extra_instruction_storage.unsafe_last(); + result.dispatches[call_info->call_index].instruction_opcode = new_call_opcode; + } + + result.max_call_rec_size = max_call_record_size; + for (size_t i = 0; i < final_roots.size(); ++i) final_roots[i] = find_root(i); + HashMap root_to_callrec_slot; + for (auto const& [value_id, slot] : value_to_callrec_slot) { + auto root = final_roots[value_id.value()]; + if (auto existing = root_to_callrec_slot.get(root); existing.has_value()) { + VERIFY(*existing == slot); + } + root_to_callrec_slot.set(root, slot); + } + + value_to_callrec_slot.clear_with_capacity(); + for (size_t i = 0; i < final_roots.size(); ++i) { + auto root = final_roots[i]; + if (auto slot = root_to_callrec_slot.get(root); slot.has_value()) { + value_to_callrec_slot.set(ValueID { i }, *slot); + } + } + struct LiveInterval { ValueID value_id; IP start; @@ -5417,6 +5701,36 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span } for (auto& [key, group] : alias_groups) { + // Check if any value in this group needs a call record slot + Dispatch::RegisterOrStack forced_slot = Dispatch::RegisterOrStack::Stack; + bool has_callrec_constraint = false; + + for (auto* interval : group) { + if (auto slot = value_to_callrec_slot.get(interval->value_id); slot.has_value()) { + forced_slot = static_cast(*slot); + has_callrec_constraint = true; + break; + } + } + + if (has_callrec_constraint) { + // Force all values in this alias group to use the call record slot + for (auto* interval : group) { + value_alloc.set(interval->value_id, forced_slot); + } + continue; + } + + auto has_fixed_allocation = false; + for (auto* interval : group) { + if (value_alloc.contains(interval->value_id)) { + has_fixed_allocation = true; + break; + } + } + if (has_fixed_allocation) + continue; + IP group_start = NumericLimits::max(); IP group_end = 0; auto group_forced_to_stack = false; @@ -5507,17 +5821,30 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span return true; }; + constexpr auto all_sources_are_callrec = [](SourcesAndDestination const& addrs, ssize_t expected_source_count, ssize_t expected_dest_count) -> bool { + if (expected_source_count < 0 || expected_dest_count > 1 || expected_dest_count < 0) + return false; + for (ssize_t i = 0; i < expected_source_count; ++i) { + if (addrs.sources[i] < Dispatch::CallRecord) + return false; + } + if (expected_dest_count == 1 && addrs.destination < Dispatch::CallRecord) + return false; + return true; + }; for (size_t i = 0; i < result.dispatches.size(); ++i) { auto& dispatch = result.dispatches[i]; auto& addrs = result.src_dst_mappings[i]; -#define CASE(name, _, inputs, outputs) \ - case Instructions::name.value(): \ - if (all_sources_are_registers(addrs, inputs, outputs)) \ - dispatch.handler_ptr = bit_cast(&InstructionHandler::template operator()); \ - else \ - dispatch.handler_ptr = bit_cast(&InstructionHandler::template operator()); \ +#define CASE(name, _, inputs, outputs) \ + case Instructions::name.value(): \ + if (all_sources_are_registers(addrs, inputs, outputs)) \ + dispatch.handler_ptr = bit_cast(&InstructionHandler::template operator()); \ + else if (all_sources_are_callrec(addrs, inputs, outputs)) \ + dispatch.handler_ptr = bit_cast(&InstructionHandler::template operator()); \ + else \ + dispatch.handler_ptr = bit_cast(&InstructionHandler::template operator()); \ break; switch (dispatch.instruction->opcode().value()) { @@ -5561,32 +5888,29 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span ENUMERATE_WASM_OPCODES(XM) } for (ssize_t i = 0; i < in_count; ++i) { - if (addresses.sources[i] < Dispatch::Stack) { - warnln(" arg{} [reg{}]", i, to_underlying(addresses.sources[i])); - } else { - warnln(" arg{} [stack]", i); - } + warnln(" arg{} [{}]", i, regname(addresses.sources[i])); } if (out_count == 1) { auto dest = addresses.destination; - if (dest < Dispatch::Stack) { - warnln(" dest [reg{}]", to_underlying(dest)); - } else { - warnln(" dest [stack]"); - } + warnln(" dest [{}]", regname(dest)); } else if (out_count > 1) { warnln(" dest [multiple outputs]"); + } else if (instruction->opcode() == Instructions::call || instruction->opcode() == Instructions::call_indirect) { + if (addresses.destination != Dispatch::Stack) + warnln(" dest [{}]", regname(addresses.destination)); } } }; if (start_ish > end_ish) swap(start_ish, end_ish); - auto start_ip = start_ish >= 5 ? start_ish - 5 : 0; + auto start_ip = start_ish >= 40 ? start_ish - 40 : 0; auto end_ip = min(result.dispatches.size(), end_ish + 10); auto skip_start = Optional {}; for (auto ip = start_ip; ip < end_ip; ip += 5) { size_t chunk_end = min(end_ip, ip + 5); + print_range(ip, chunk_end); + continue; bool has_mark = false; for (auto const& mark : { marks... }) { if (mark.ip >= ip && mark.ip < chunk_end) { @@ -5606,6 +5930,7 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span } }; + bool used[256] = { false }; for (size_t i = 0; i < result.dispatches.size(); ++i) { auto& dispatch = result.dispatches[i]; if (dispatch.instruction->opcode() == Instructions::if_) { @@ -5628,6 +5953,45 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span VERIFY_NOT_REACHED(); } } + // If the instruction is a call with a callrec, clear used[] for the callrec registers. + if (dispatch.instruction->opcode() == Instructions::synthetic_call_with_record_0 || dispatch.instruction->opcode() == Instructions::synthetic_call_with_record_1) { + for (size_t j = to_underlying(Dispatch::CallRecord); j <= to_underlying(Dispatch::LastCallRecord); ++j) + used[j] = false; + } + + auto& addr = result.src_dst_mappings[i]; + + // for each input, ensure it's not reading from a register that is not marked as used (unless stack). + ssize_t in_count = 0; + ssize_t out_count = 0; + switch (dispatch.instruction->opcode().value()) { + ENUMERATE_WASM_OPCODES(XM) + } + for (ssize_t j = 0; j < in_count; ++j) { + auto src = addr.sources[j]; + if (src == Dispatch::Stack) + continue; + if (!used[to_underlying(src)]) { + dbgln("Instruction {} reads from register {} which is not populated", i, to_underlying(src)); + dbgln("Instructions around the invalid read:"); + print_instructions_around(i, i, Mark { i, "invalid read here"sv }); + VERIFY_NOT_REACHED(); + } + used[to_underlying(src)] = false; + } + // if the instruction has an output, ensure it's not writing to a register that is marked used. + if (out_count == 1 || dispatch.instruction->opcode() == Instructions::call || dispatch.instruction->opcode() == Instructions::call_indirect) { + auto dest = addr.destination; + if (dest != Dispatch::Stack) { + if (used[to_underlying(dest)]) { + dbgln("Instruction {} writes to register {} which is already populated", i, to_underlying(dest)); + dbgln("Instructions around the invalid write:"); + print_instructions_around(i, i, Mark { i, "invalid write here"sv }); + VERIFY_NOT_REACHED(); + } + used[to_underlying(dest)] = true; + } + } } return result; diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h index 676edcb9fe..223e13286e 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h @@ -63,6 +63,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter { enum class CallType { UsingRegisters, + UsingCallRecord, UsingStack, }; diff --git a/Libraries/LibWasm/AbstractMachine/Configuration.cpp b/Libraries/LibWasm/AbstractMachine/Configuration.cpp index 63034767bb..19482089ab 100644 --- a/Libraries/LibWasm/AbstractMachine/Configuration.cpp +++ b/Libraries/LibWasm/AbstractMachine/Configuration.cpp @@ -13,13 +13,10 @@ namespace Wasm { void Configuration::unwind_impl() { - release_arguments_allocation(m_frame_stack.last().arguments()); - m_frame_stack.last().locals().clear(); - auto last_frame = m_frame_stack.take_last(); m_depth--; m_locals_base = m_frame_stack.is_empty() ? nullptr : m_frame_stack.unchecked_last().locals().data(); - m_arguments_base = m_frame_stack.is_empty() ? nullptr : m_frame_stack.unchecked_last().arguments().data(); + release_arguments_allocation(last_frame.locals()); } Result Configuration::call(Interpreter& interpreter, FunctionAddress address, Vector& arguments) @@ -39,18 +36,16 @@ ErrorOr, Trap> Configuration::prepare_call(FunctionAddre if (auto* wasm_function = function->get_pointer()) { if (is_tailcall) unwind_impl(); // Unwind the current frame, the "return" in the tail-called function will unwind the frame we're gonna push now. - Vector locals; - locals.ensure_capacity(wasm_function->code().func().total_local_count()); + arguments.ensure_capacity(arguments.size() + wasm_function->code().func().total_local_count()); for (auto& local : wasm_function->code().func().locals()) { for (size_t i = 0; i < local.n(); ++i) - locals.unchecked_append(Value(local.type())); + arguments.unchecked_append(Value(local.type())); } set_frame( is_tailcall ? IsTailcall::Yes : IsTailcall::No, wasm_function->module(), move(arguments), - move(locals), wasm_function->code().func().body(), wasm_function->type().results().size()); return OptionalNone {}; diff --git a/Libraries/LibWasm/AbstractMachine/Configuration.h b/Libraries/LibWasm/AbstractMachine/Configuration.h index 163049ae2c..9dbbe33c8a 100644 --- a/Libraries/LibWasm/AbstractMachine/Configuration.h +++ b/Libraries/LibWasm/AbstractMachine/Configuration.h @@ -14,6 +14,7 @@ namespace Wasm { enum class SourceAddressMix { AllRegisters, + AllCallRecords, Any, }; @@ -36,7 +37,6 @@ public: auto& frame = m_frame_stack.unchecked_last(); m_locals_base = frame.locals().data(); - m_arguments_base = frame.arguments().data(); auto continuation = frame.expression().instructions().size() - 1; if (auto size = frame.expression().compiled_instructions.dispatches.size(); size > 0) @@ -50,9 +50,14 @@ public: m_label_stack.ensure_capacity(*hint + m_label_stack.size()); } m_label_stack.append(label); - if (auto max_count = frame.expression().compiled_instructions.max_call_arg_count; max_count > ArgumentsStaticSize) { - if (!m_call_argument_freelist.is_empty() && !any_of(m_call_argument_freelist, [&](auto const& entry) { return entry.capacity() >= frame.expression().compiled_instructions.max_call_arg_count; })) - m_call_argument_freelist.last().ensure_capacity(max_count); + + auto max_call_rec_size = frame.expression().compiled_instructions.max_call_rec_size; + if (max_call_rec_size > 0) { + get_arguments_allocation_if_possible(m_current_call_record, max_call_rec_size); + m_current_call_record.resize_and_keep_capacity(max_call_rec_size); + m_call_record_base = m_current_call_record.data(); + } else { + m_call_record_base = nullptr; } } ALWAYS_INLINE auto& frame() const { return m_frame_stack.unchecked_last(); } @@ -68,15 +73,6 @@ public: ALWAYS_INLINE auto& store() const { return m_store; } ALWAYS_INLINE auto& store() { return m_store; } - ALWAYS_INLINE Value& local_or_argument(LocalIndex index) - { - if (index.value() & LocalArgumentMarker) - return m_arguments_base[index.value() & ~LocalArgumentMarker]; - return m_locals_base[index.value()]; - } - ALWAYS_INLINE Value const& argument(LocalIndex index) const { return m_arguments_base[index.value() & ~LocalArgumentMarker]; } - ALWAYS_INLINE Value& argument(LocalIndex index) { return m_arguments_base[index.value() & ~LocalArgumentMarker]; } - ALWAYS_INLINE Value const& local(LocalIndex index) const { return m_locals_base[index.value()]; } ALWAYS_INLINE Value& local(LocalIndex index) { return m_locals_base[index.value()]; } @@ -84,15 +80,25 @@ public: explicit CallFrameHandle(Configuration& configuration) : configuration(configuration) { + if (configuration.m_call_record_base) + moved_call_record = move(configuration.m_current_call_record); configuration.depth()++; + configuration.m_call_record_base = nullptr; } ~CallFrameHandle() { + if (moved_call_record.has_value()) { + configuration.m_current_call_record = moved_call_record.release_value(); + configuration.m_call_record_base = configuration.m_current_call_record.data(); + } else { + configuration.m_call_record_base = nullptr; + } configuration.unwind({}, *this); } Configuration& configuration; + Optional> moved_call_record; }; void unwind(Badge, CallFrameHandle const&) { unwind_impl(); } @@ -110,24 +116,49 @@ public: if (arguments.capacity() != ArgumentsStaticSize || max_size <= ArgumentsStaticSize) return; // Already heap allocated, or we just don't need to allocate anything. - // _arguments_ is still in static storage, pull something from the freelist if possible; otherwise allocate a new one. + // _arguments_ is still in static storage, pull something from the freelist if it fits. if (auto index = m_call_argument_freelist.find_first_index_if([&](auto& entry) { return entry.capacity() >= max_size; }); index.has_value()) { arguments = m_call_argument_freelist.take(*index); return; } - arguments.ensure_capacity(max_size); + if (!m_call_argument_freelist.is_empty()) + arguments = m_call_argument_freelist.take_last(); + + arguments.ensure_capacity(max(max_size, frame().module().cached_minimum_call_record_allocation_size)); } void release_arguments_allocation(Vector& arguments) { arguments.clear_with_capacity(); // Clear to avoid copying, but keep capacity for reuse. - if (arguments.capacity() != ArgumentsStaticSize) { - if (m_call_argument_freelist.size() >= 16) // Don't grow to heap. - return; + auto size = frame().expression().compiled_instructions.max_call_rec_size; - m_call_argument_freelist.append(move(arguments)); + if (size > 0) { + // If we need a call record, keep this as the current one. + if (!m_call_record_base) { + m_current_call_record = move(arguments); + m_current_call_record.resize_and_keep_capacity(size); + m_call_record_base = m_current_call_record.data(); + return; + } + + VERIFY(m_current_call_record.size() >= size); } + + if (arguments.capacity() != ArgumentsStaticSize) { + if (m_call_argument_freelist.size() >= 16) { + // Don't grow to heap. + return; + } + + m_call_argument_freelist.unchecked_append(move(arguments)); + } + } + + void take_call_record(Vector& call_record) + { + call_record = move(m_current_call_record); + m_call_record_base = nullptr; } template @@ -136,6 +167,9 @@ public: if constexpr (mix == SourceAddressMix::AllRegisters) { regs.data()[to_underlying(destination)] = value; return; + } else if constexpr (mix == SourceAddressMix::AllCallRecords) { + m_call_record_base[to_underlying(destination) - Dispatch::RegisterOrStack::CallRecord] = value; + return; } else if constexpr (mix == SourceAddressMix::Any) { if (!(destination & ~(Dispatch::Stack - 1))) [[likely]] { regs.data()[to_underlying(destination)] = value; @@ -148,6 +182,9 @@ public: value_stack().unchecked_append(value); return; } + + m_call_record_base[to_underlying(destination) - Dispatch::RegisterOrStack::CallRecord] = value; + return; } VERIFY_NOT_REACHED(); @@ -161,6 +198,8 @@ public: if constexpr (mix == SourceAddressMix::AllRegisters) { return regs.data()[to_underlying(source)]; + } else if constexpr (mix == SourceAddressMix::AllCallRecords) { + return m_call_record_base[to_underlying(source) - Dispatch::RegisterOrStack::CallRecord]; } else if constexpr (mix == SourceAddressMix::Any) { if (!(source & ~(Dispatch::Stack - 1))) [[likely]] return regs.data()[to_underlying(source)]; @@ -169,6 +208,8 @@ public: if constexpr (mix == SourceAddressMix::Any) { if (source == Dispatch::RegisterOrStack::Stack) [[unlikely]] return value_stack().unsafe_last(); + + return m_call_record_base[to_underlying(source) - Dispatch::RegisterOrStack::CallRecord]; } VERIFY_NOT_REACHED(); @@ -180,6 +221,8 @@ public: auto const source = sources[index]; if constexpr (mix == SourceAddressMix::AllRegisters) { return regs.data()[to_underlying(source)]; + } else if constexpr (mix == SourceAddressMix::AllCallRecords) { + return m_call_record_base[to_underlying(source) - Dispatch::RegisterOrStack::CallRecord]; } else if constexpr (mix == SourceAddressMix::Any) { if (!(source & ~(Dispatch::Stack - 1))) [[likely]] return regs.data()[to_underlying(source)]; @@ -188,6 +231,8 @@ public: if constexpr (mix == SourceAddressMix::Any) { if (source == Dispatch::RegisterOrStack::Stack) [[unlikely]] return value_stack().unsafe_take_last(); + + return m_call_record_base[to_underlying(source) - Dispatch::RegisterOrStack::CallRecord]; } VERIFY_NOT_REACHED(); @@ -211,12 +256,13 @@ private: Vector m_value_stack; Vector m_label_stack; DoublyLinkedList m_frame_stack; + Vector m_current_call_record; Vector, 16, FastLastAccess::Yes> m_call_argument_freelist; size_t m_depth { 0 }; u64 m_ip { 0 }; bool m_should_limit_instruction_count { false }; Value* m_locals_base { nullptr }; - Value* m_arguments_base { nullptr }; + Value* m_call_record_base { nullptr }; }; } diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index 09f756cb0c..3b7a5e3733 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -97,6 +97,8 @@ ErrorOr Validator::validate(Module& module) for (auto& tag : module.tag_section().tags()) m_context.tags.append(TagType(tag.type(), tag.flags())); + m_context.current_module = &module; + // We need to build the set of declared functions to check that `ref.func` uses a specific set of predetermined functions, found in: // - Element initializer expressions // - Global initializer expressions @@ -133,6 +135,9 @@ ErrorOr Validator::validate(Module& module) TRY(validate(module.code_section())); TRY(validate(module.tag_section())); + for (auto& entry : module.code_section().functions()) + module.set_minimum_call_record_allocation_size(max(entry.func().body().compiled_instructions.max_call_rec_size, module.minimum_call_record_allocation_size())); + module.set_validation_status(Module::ValidationStatus::Valid, {}); return {}; } @@ -273,6 +278,19 @@ ErrorOr Validator::validate(CodeSection const& section) auto results = TRY(function_validator.validate(function.body(), function_type.results())); if (results.result_types.size() != function_type.results().size()) return Errors::invalid("function result"sv, function_type.results(), results.result_types); + + if (function.body().compiled_instructions.max_call_rec_size != 0) { + size_t max_callee_locals = 0; + for (auto& insn : function.body().instructions()) { + if (!first_is_one_of(insn.opcode(), Instructions::call, Instructions::synthetic_call_with_record_0, Instructions::synthetic_call_with_record_1)) + continue; + auto callee_index = insn.arguments().template get(); + if (callee_index.value() - m_context.imported_function_count < section.functions().size()) + max_callee_locals = max(max_callee_locals, section.functions()[callee_index.value() - m_context.imported_function_count].func().total_local_count()); + } + + function.body().compiled_instructions.max_call_rec_size += max_callee_locals; + } } return {}; diff --git a/Libraries/LibWasm/AbstractMachine/Validator.h b/Libraries/LibWasm/AbstractMachine/Validator.h index a2a1565ba8..a1b2847a6c 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.h +++ b/Libraries/LibWasm/AbstractMachine/Validator.h @@ -127,11 +127,6 @@ public: ErrorOr validate(LocalIndex index) const { - if (index.value() & LocalArgumentMarker) - index = index.value() & ~LocalArgumentMarker; - else - index = index.value() + m_context.current_function_parameter_count; - if (index.value() < m_context.locals.size()) return index; return Errors::invalid("LocalIndex"sv); diff --git a/Libraries/LibWasm/Opcode.h b/Libraries/LibWasm/Opcode.h index c88a7a1748..00ba7f98de 100644 --- a/Libraries/LibWasm/Opcode.h +++ b/Libraries/LibWasm/Opcode.h @@ -500,7 +500,9 @@ namespace Instructions { M(synthetic_call_30, 0xfe0000000000000cull, 3, 0) \ M(synthetic_call_31, 0xfe0000000000000dull, 3, 1) \ M(synthetic_end_expression, 0xfe0000000000000eull, 0, 0) \ - M(synthetic_argument_get, 0xfe0000000000000full, 0, 1) + M(synthetic_argument_get, 0xfe0000000000000full, 0, 1) \ + M(synthetic_call_with_record_0, 0xfe00000000000012ull, 0, 0) \ + M(synthetic_call_with_record_1, 0xfe00000000000013ull, 0, 1) #define ENUMERATE_WASM_OPCODES(M) \ ENUMERATE_SINGLE_BYTE_WASM_OPCODES(M) \ @@ -511,7 +513,7 @@ ENUMERATE_WASM_OPCODES(M) #undef M static constexpr inline OpCode SyntheticInstructionBase = 0xfe00000000000000ull; -static constexpr inline size_t SyntheticInstructionCount = 16; +static constexpr inline size_t SyntheticInstructionCount = 18; } diff --git a/Libraries/LibWasm/Parser/Parser.cpp b/Libraries/LibWasm/Parser/Parser.cpp index 3d5117280f..e9bc71bc80 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -1480,22 +1480,6 @@ ParseResult> Module::parse(Stream& stream) void Module::preprocess() { - for (auto const [i, type_id] : enumerate(function_section().types())) { - if (type_id.value() >= type_section().types().size() || i >= code_section().functions().size()) { - dbgln("WASM Module preprocessing: skipping function {} with invalid type id {}", i, type_id.value()); - continue; - } - - auto& function = code_section().functions()[i]; - auto const parameter_count = type_section().types()[type_id.value()].parameters().size(); - for (auto& instruction : function.func().body().instructions()) { - auto& mutable_instruction = const_cast(instruction); - if (instruction.local_index() < parameter_count) - mutable_instruction.set_local_index({}, instruction.local_index().value() | LocalArgumentMarker); - else - mutable_instruction.set_local_index({}, instruction.local_index().value() - parameter_count); - } - } } ByteString parse_error_to_byte_string(ParseError error) diff --git a/Libraries/LibWasm/Printer/Printer.cpp b/Libraries/LibWasm/Printer/Printer.cpp index e4e50683a3..756987f1d3 100644 --- a/Libraries/LibWasm/Printer/Printer.cpp +++ b/Libraries/LibWasm/Printer/Printer.cpp @@ -475,27 +475,20 @@ void Printer::print(Wasm::Instruction const& instruction) print("({}", instruction_name(instruction.opcode())); if (instruction.arguments().has()) { if (first_is_one_of(instruction.opcode(), Instructions::local_get, Instructions::local_set, Instructions::local_tee, Instructions::synthetic_argument_get)) { - if (instruction.local_index().value() & LocalArgumentMarker) - print(" (argument index {})", instruction.local_index().value() & ~LocalArgumentMarker); - else - print(" (local index {})", instruction.local_index().value()); + print(" (local index {})", instruction.local_index().value()); } print(")\n"); } else { print(" "); instruction.arguments().visit( + [&](Instruction::BranchArgs const& branch) { print("(label index {})", branch.label.value()); }, [&](BlockType const& type) { print(type); }, [&](DataIndex const& index) { print("(data index {})", index.value()); }, [&](ElementIndex const& index) { print("(element index {})", index.value()); }, [&](FunctionIndex const& index) { print("(function index {})", index.value()); }, [&](GlobalIndex const& index) { print("(global index {})", index.value()); }, [&](LabelIndex const& index) { print("(label index {})", index.value()); }, - [&](LocalIndex const& index) { - if (index.value() & LocalArgumentMarker) - print("(argument index {})", index.value() & ~LocalArgumentMarker); - else - print("(local index {})", index.value()); - }, + [&](LocalIndex const& index) { print("(local index {})", index.value()); }, [&](TableIndex const& index) { print("(table index {})", index.value()); }, [&](Instruction::IndirectCallArgs const& args) { print("(indirect (type index {}) (table index {}))", args.type.value(), args.table.value()); }, [&](Instruction::MemoryArgument const& args) { print("(memory index {} (align {}) (offset {}))", args.memory_index.value(), args.align, args.offset); }, @@ -516,7 +509,12 @@ void Printer::print(Wasm::Instruction const& instruction) TemporaryChange change { m_indent, m_indent + 1 }; print(args.block_type); print_indent(); - print("(else {}) (end {}))", args.else_ip.has_value() ? ByteString::number(args.else_ip->value()) : "(none)", args.end_ip.value()); + print("(else {}) (end {})", args.else_ip.has_value() ? ByteString::number(args.else_ip->value()) : "(none)", args.end_ip.value()); + if (args.meta.has_value()) + print(" (meta arity {} params {})", args.meta->arity, args.meta->parameter_count); + else + print(" (meta none)"); + print(")"); }, [&](Instruction::TryTableArgs const& args) { print("(try_table "); @@ -540,12 +538,8 @@ void Printer::print(Wasm::Instruction const& instruction) [&](Vector const&) { print("(types...)"); }, [&](auto const& value) { print("(const {})", value); }); - if (first_is_one_of(instruction.opcode(), Instructions::local_get, Instructions::local_set, Instructions::local_tee, Instructions::synthetic_argument_get, Instructions::synthetic_local_seti32_const, Instructions::synthetic_i32_storelocal)) { - if (instruction.local_index().value() & LocalArgumentMarker) - print(" (argument index {})", instruction.local_index().value() & ~LocalArgumentMarker); - else - print(" (local index {})", instruction.local_index().value()); - } + if (first_is_one_of(instruction.opcode(), Instructions::local_get, Instructions::local_set, Instructions::local_tee, Instructions::synthetic_argument_get, Instructions::synthetic_local_seti32_const, Instructions::synthetic_i32_storelocal)) + print(" (local index {})", instruction.local_index().value()); print(")\n"); } @@ -1274,5 +1268,7 @@ HashMap Wasm::Names::instruction_names { { Instructions::synthetic_call_31, "synthetic:call.31" }, { Instructions::synthetic_end_expression, "synthetic:expression.end" }, { Instructions::synthetic_argument_get, "synthetic:argument.get" }, + { Instructions::synthetic_call_with_record_0, "synthetic:call.with_record.0" }, + { Instructions::synthetic_call_with_record_1, "synthetic:call.with_record.1" }, }; HashMap Wasm::Names::instructions_by_name; diff --git a/Libraries/LibWasm/Types.h b/Libraries/LibWasm/Types.h index 3073f09404..71cbf522b9 100644 --- a/Libraries/LibWasm/Types.h +++ b/Libraries/LibWasm/Types.h @@ -620,6 +620,8 @@ struct Dispatch { R7, CountRegisters, Stack = CountRegisters, + CallRecord, + LastCallRecord = NumericLimits::max(), }; static_assert(is_power_of_two(to_underlying(Stack)), "Stack marker must be a single bit"); @@ -645,6 +647,7 @@ struct CompiledInstructions { Vector extra_instruction_storage; bool direct = false; // true if all dispatches contain handler_ptr, otherwise false and all contain instruction_opcode. size_t max_call_arg_count = 0; + size_t max_call_rec_size = 0; }; template @@ -1251,6 +1254,9 @@ public: static ParseResult> parse(Stream& stream); + size_t minimum_call_record_allocation_size() const { return m_minimum_call_record_allocation_size; } + void set_minimum_call_record_allocation_size(size_t size) { m_minimum_call_record_allocation_size = size; } + private: void set_validation_status(ValidationStatus status) { m_validation_status = status; } void preprocess(); @@ -1272,6 +1278,8 @@ private: ValidationStatus m_validation_status { ValidationStatus::Unchecked }; Optional m_validation_error; + + size_t m_minimum_call_record_allocation_size { 0 }; }; CompiledInstructions try_compile_instructions(Expression const&, Span functions); diff --git a/Utilities/wasm.cpp b/Utilities/wasm.cpp index 9f90494c46..4043732301 100644 --- a/Utilities/wasm.cpp +++ b/Utilities/wasm.cpp @@ -747,11 +747,11 @@ ErrorOr ladybird_main(Main::Arguments arguments) ByteString regs; auto first = true; ssize_t in_count = 0; - bool has_out = false; + ssize_t out_count = 0; #define M(name, _, ins, outs) \ case Wasm::Instructions::name.value(): \ in_count = ins; \ - has_out = outs != 0; \ + out_count = outs; \ break; switch (dispatch.instruction->opcode().value()) { ENUMERATE_WASM_OPCODES(M) @@ -760,6 +760,8 @@ ErrorOr ladybird_main(Main::Arguments arguments) constexpr auto reg_name = [](Wasm::Dispatch::RegisterOrStack reg) -> ByteString { if (reg == Wasm::Dispatch::RegisterOrStack::Stack) return "stack"sv; + if (reg >= Wasm::Dispatch::RegisterOrStack::CallRecord) + return ByteString::formatted("cr{}", to_underlying(reg) - to_underlying(Wasm::Dispatch::RegisterOrStack::CallRecord)); return ByteString::formatted("reg{}", to_underlying(reg)); }; if (in_count > -1) { @@ -770,17 +772,25 @@ ErrorOr ladybird_main(Main::Arguments arguments) regs = ByteString::formatted("{}, {}", regs, reg_name(addresses.sources[index])); first = false; } - if (has_out) { + if (out_count > 0) { if (first) regs = ByteString::formatted(" () -> {}", reg_name(addresses.destination)); else regs = ByteString::formatted("{}) -> {}", regs, reg_name(addresses.destination)); - } else { + } else if (out_count == 0) { if (first) regs = ByteString::formatted(" () -x"); else regs = ByteString::formatted("{}) -x", regs); + } else { + if (first) + regs = ByteString::formatted(" () -?"); + else + regs = ByteString::formatted("{}) -?", regs); } + } else if (dispatch.instruction->opcode() == Wasm::Instructions::call || dispatch.instruction->opcode() == Wasm::Instructions::call_indirect) { + if (addresses.destination != Wasm::Dispatch::RegisterOrStack::Stack) + regs = ByteString::formatted("(?) -> {}", reg_name(addresses.destination)); } if (regs.is_empty())