From be9d8288ef027d49e8703facf7da8524c58930e6 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Fri, 7 Nov 2025 22:24:07 +0100 Subject: [PATCH] LibWasm: Take call arguments and results on registers if possible --- Libraries/LibTest/JavaScriptTestRunner.h | 49 +++++ Libraries/LibTest/TestRunner.h | 2 + .../AbstractMachine/BytecodeInterpreter.cpp | 194 ++++++++---------- .../AbstractMachine/BytecodeInterpreter.h | 7 +- Tests/LibJS/Runtime/test-common.js | 16 ++ Utilities/wasm.cpp | 6 +- 6 files changed, 160 insertions(+), 114 deletions(-) diff --git a/Libraries/LibTest/JavaScriptTestRunner.h b/Libraries/LibTest/JavaScriptTestRunner.h index 69d4072717..d13c3d0890 100644 --- a/Libraries/LibTest/JavaScriptTestRunner.h +++ b/Libraries/LibTest/JavaScriptTestRunner.h @@ -177,6 +177,10 @@ public: } virtual void initialize(JS::Realm&) override; virtual ~TestRunnerGlobalObject() override = default; + + JS_DECLARE_NATIVE_FUNCTION(report_test); + + Function on_test_reported; }; inline void TestRunnerGlobalObject::initialize(JS::Realm& realm) @@ -184,6 +188,7 @@ inline void TestRunnerGlobalObject::initialize(JS::Realm& realm) Base::initialize(realm); define_direct_property("global"_utf16_fly_string, this, JS::Attribute::Enumerable); + define_native_function(realm, "__reportTest__"_utf16, report_test, 2, JS::default_attributes); for (auto& entry : s_exposed_global_functions) { define_native_function( realm, @@ -195,6 +200,19 @@ inline void TestRunnerGlobalObject::initialize(JS::Realm& realm) } } +inline JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::report_test) +{ + auto const& self = as(vm.get_global_object()); + if (!self.on_test_reported) + return JS::js_undefined(); + + auto test_name_value = vm.argument(0); + auto test_name = TRY(test_name_value.to_string(vm)); + auto state_value = vm.argument(1); + self.on_test_reported(test_name, state_value); + return JS::js_undefined(); +} + inline ByteBuffer load_entire_file(StringView path) { auto try_load_entire_file = [](StringView const& path) -> ErrorOr { @@ -271,6 +289,34 @@ inline Vector TestRunner::get_test_paths() const return paths; } +inline void print_test_timings(String test_name, JS::Value state_value) +{ + if (state_value.is_string()) { + auto state_string = state_value.as_string().utf8_string(); + if (state_string == "pass"sv) { + print_modifiers({ FG_BOLD }); + out("Finished: "); + print_modifiers({ CLEAR }); + outln("{} (PASS)", test_name); + } else if (state_string == "fail"sv) { + print_modifiers({ FG_RED, FG_BOLD }); + out("Finished: "); + print_modifiers({ CLEAR }); + outln("{} (FAIL)", test_name); + } else if (state_string == "xfail"sv) { + print_modifiers({ FG_ORANGE, FG_BOLD }); + out("Finished: "); + print_modifiers({ CLEAR }); + outln("{} (XFAIL)", test_name); + } else if (state_string == "start"sv) { + print_modifiers({ BG_GREEN, FG_ORANGE }); + out("Running: "); + print_modifiers({ CLEAR }); + outln("{}", test_name); + } + } +} + inline JSFileResult TestRunner::run_file_test(ByteString const& test_path) { g_currently_running_test = test_path; @@ -284,6 +330,9 @@ inline JSFileResult TestRunner::run_file_test(ByteString const& test_path) [&](JS::Realm& realm_) -> JS::GlobalObject* { realm = &realm_; global_object = realm->create(*realm); + if (this->needs_timings()) { + global_object->on_test_reported = print_test_timings; + } return global_object; }, nullptr)); diff --git a/Libraries/LibTest/TestRunner.h b/Libraries/LibTest/TestRunner.h index 03748484b1..ce0407bf70 100644 --- a/Libraries/LibTest/TestRunner.h +++ b/Libraries/LibTest/TestRunner.h @@ -51,6 +51,8 @@ public: bool needs_detailed_suites() const { return m_detailed_json; } Vector const& suites() const { return *m_suites; } + bool needs_timings() const { return m_print_times; } + Vector& ensure_suites() { return m_suites.ensure([] { return Vector {}; }); diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index 871e6378e1..b606f71036 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -1079,97 +1080,81 @@ HANDLE_INSTRUCTION(synthetic_local_seti32_const) HANDLE_INSTRUCTION(synthetic_call_00) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_00(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } HANDLE_INSTRUCTION(synthetic_call_01) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_01(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } HANDLE_INSTRUCTION(synthetic_call_10) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_10(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } HANDLE_INSTRUCTION(synthetic_call_11) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_11(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } HANDLE_INSTRUCTION(synthetic_call_20) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_20(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } HANDLE_INSTRUCTION(synthetic_call_21) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_21(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } HANDLE_INSTRUCTION(synthetic_call_30) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_30(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } HANDLE_INSTRUCTION(synthetic_call_31) { - auto regs_copy = configuration.regs; auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_31(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) return Outcome::Return; - configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } @@ -1333,7 +1318,7 @@ HANDLE_INSTRUCTION(call) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "call({})", address.value()); - if (interpreter.call_address(configuration, address) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses) == Outcome::Return) return Outcome::Return; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } @@ -1344,7 +1329,7 @@ HANDLE_INSTRUCTION(return_call) auto address = configuration.frame().module().functions()[index.value()]; configuration.label_stack().shrink(configuration.frame().label_index() + 1, true); dbgln_if(WASM_TRACE_DEBUG, "tail call({})", address.value()); - switch (auto const outcome = interpreter.call_address(configuration, address, BytecodeInterpreter::CallAddressSource::DirectTailCall)) { + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectTailCall)) { default: // Some IP we have to continue from. current_ip_value = to_underlying(outcome) - 1; @@ -1378,7 +1363,7 @@ HANDLE_INSTRUCTION(call_indirect) TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results()); dbgln_if(WASM_TRACE_DEBUG, "call_indirect({} -> {})", index, address.value()); - if (interpreter.call_address(configuration, address, BytecodeInterpreter::CallAddressSource::IndirectCall) == Outcome::Return) + if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectCall) == Outcome::Return) return Outcome::Return; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } @@ -1403,7 +1388,7 @@ HANDLE_INSTRUCTION(return_call_indirect) TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results()); dbgln_if(WASM_TRACE_DEBUG, "tail call_indirect({} -> {})", index, address.value()); - switch (auto const outcome = interpreter.call_address(configuration, address, BytecodeInterpreter::CallAddressSource::IndirectTailCall)) { + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectTailCall)) { default: // Some IP we have to continue from. current_ip_value = to_underlying(outcome) - 1; @@ -3884,14 +3869,15 @@ bool BytecodeInterpreter::load_and_push(Configuration& configuration, Instructio auto& entry = configuration.source_value(0, addresses.sources); // bounds checked by verifier. auto base = entry.to(); u64 instance_address = static_cast(bit_cast(base)) + arg.offset; + dbgln_if(WASM_TRACE_DEBUG, "load({} : {}) -> stack", instance_address, sizeof(ReadType)); if (instance_address + sizeof(ReadType) > memory->size()) { m_trap = Trap::from_string("Memory access out of bounds"); dbgln("LibWasm: load_and_push - Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + sizeof(ReadType), memory->size()); return true; } - dbgln_if(WASM_TRACE_DEBUG, "load({} : {}) -> stack", instance_address, sizeof(ReadType)); auto slice = memory->data().bytes().slice(instance_address, sizeof(ReadType)); entry = Value(static_cast(read_value(slice))); + dbgln_if(WASM_TRACE_DEBUG, " loaded value: {}", entry.value()); return false; } @@ -3910,12 +3896,12 @@ bool BytecodeInterpreter::load_and_push_mxn(Configuration& configuration, Instru auto& entry = configuration.source_value(0, addresses.sources); // bounds checked by verifier. auto base = entry.to(); u64 instance_address = static_cast(bit_cast(base)) + arg.offset; + dbgln_if(WASM_TRACE_DEBUG, "vec-load({} : {}) -> stack", instance_address, M * N / 8); if (instance_address + M * N / 8 > memory->size()) { m_trap = Trap::from_string("Memory access out of bounds"); dbgln("LibWasm: load_and_push_mxn - Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + M * N / 8, memory->size()); return true; } - dbgln_if(WASM_TRACE_DEBUG, "vec-load({} : {}) -> stack", instance_address, M * N / 8); auto slice = memory->data().bytes().slice(instance_address, M * N / 8); using V64 = NativeVectorType; using V128 = NativeVectorType; @@ -3927,6 +3913,7 @@ bool BytecodeInterpreter::load_and_push_mxn(Configuration& configuration, Instru ByteReader::load(slice.data(), bytes); entry = Value(bit_cast(convert_vector(bytes))); + dbgln_if(WASM_TRACE_DEBUG, " loaded value: {}", entry.value()); return false; } @@ -3940,6 +3927,7 @@ bool BytecodeInterpreter::load_and_push_lane_n(Configuration& configuration, Ins auto vector = configuration.take_source(0, addresses.sources).to(); auto base = configuration.take_source(1, addresses.sources).to(); u64 instance_address = static_cast(bit_cast(base)) + memarg_and_lane.memory.offset; + dbgln_if(WASM_TRACE_DEBUG, "load-lane({} : {}, lane {}) -> stack", instance_address, N / 8, memarg_and_lane.lane); if (instance_address + N / 8 > memory->size()) { m_trap = Trap::from_string("Memory access out of bounds"); dbgln("LibWasm: load_and_push_lane_n - Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + N / 8, memory->size()); @@ -3948,6 +3936,7 @@ bool BytecodeInterpreter::load_and_push_lane_n(Configuration& configuration, Ins auto slice = memory->data().bytes().slice(instance_address, N / 8); auto dst = bit_cast(&vector) + memarg_and_lane.lane * N / 8; memcpy(dst, slice.data(), N / 8); + dbgln_if(WASM_TRACE_DEBUG, " loaded value: {}", vector); configuration.push_to_destination(Value(vector), addresses.destination); return false; } @@ -3961,6 +3950,7 @@ bool BytecodeInterpreter::load_and_push_zero_n(Configuration& configuration, Ins // bounds checked by verifier. auto base = configuration.take_source(0, addresses.sources).to(); u64 instance_address = static_cast(bit_cast(base)) + memarg_and_lane.offset; + dbgln_if(WASM_TRACE_DEBUG, "load-zero({} : {}) -> stack", instance_address, N / 8); if (instance_address + N / 8 > memory->size()) { m_trap = Trap::from_string("Memory access out of bounds"); dbgln("LibWasm: load_and_push_zero_n - Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + N / 8, memory->size()); @@ -3969,6 +3959,7 @@ bool BytecodeInterpreter::load_and_push_zero_n(Configuration& configuration, Ins auto slice = memory->data().bytes().slice(instance_address, N / 8); u128 vector = 0; memcpy(&vector, slice.data(), N / 8); + dbgln_if(WASM_TRACE_DEBUG, " loaded value: {}", vector); configuration.push_to_destination(Value(vector), addresses.destination); return false; } @@ -3982,14 +3973,15 @@ bool BytecodeInterpreter::load_and_push_m_splat(Configuration& configuration, In auto& entry = configuration.source_value(0, addresses.sources); // bounds checked by verifier. auto base = entry.to(); u64 instance_address = static_cast(bit_cast(base)) + arg.offset; + dbgln_if(WASM_TRACE_DEBUG, "vec-splat({} : {}) -> stack", instance_address, M / 8); if (instance_address + M / 8 > memory->size()) { m_trap = Trap::from_string("Memory access out of bounds"); dbgln("LibWasm: load_and_push_m_splat - Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + M / 8, memory->size()); return true; } - dbgln_if(WASM_TRACE_DEBUG, "vec-splat({} : {}) -> stack", instance_address, M / 8); auto slice = memory->data().bytes().slice(instance_address, M / 8); auto value = read_value>(slice); + dbgln_if(WASM_TRACE_DEBUG, " loaded value: {}", value); set_top_m_splat(configuration, value, addresses); return false; } @@ -4040,61 +4032,76 @@ VectorType BytecodeInterpreter::pop_vector(Configuration& configuration, size_t return bit_cast(configuration.take_source(source, addresses.sources).to()); } -Outcome BytecodeInterpreter::call_address(Configuration& configuration, FunctionAddress address, CallAddressSource source) +Outcome BytecodeInterpreter::call_address(Configuration& configuration, FunctionAddress address, SourcesAndDestination const& addresses, CallAddressSource source, CallType call_type) { TRAP_IF_NOT(m_stack_info.size_free() >= Constants::minimum_stack_space_to_keep_free, "{}: {}", Constants::stack_exhaustion_message); - - auto instance = configuration.store().get(address); - FunctionType const* type { nullptr }; - instance->visit([&](auto const& function) { type = &function.type(); }); - if (source == CallAddressSource::IndirectCall || source == CallAddressSource::IndirectTailCall) { - TRAP_IF_NOT(type->parameters().size() <= configuration.value_stack().size()); - } - Vector args; - if (!type->parameters().is_empty()) { - args.ensure_capacity(type->parameters().size()); - auto span = configuration.value_stack().span().slice_from_end(type->parameters().size()); - for (auto& value : span) - args.unchecked_append(value); - - configuration.value_stack().remove(configuration.value_stack().size() - span.size(), span.size()); - } - Result result { Trap::from_string("") }; Outcome final_outcome = Outcome::Continue; + { + Optional> regs_rollback; + if (call_type == CallType::UsingRegisters) + regs_rollback = ScopedValueRollback { configuration.regs }; - if (source == CallAddressSource::DirectTailCall || source == CallAddressSource::IndirectTailCall) { - auto prep_outcome = configuration.prepare_call(address, args, true); - if (prep_outcome.is_error()) { - m_trap = prep_outcome.release_error(); + auto instance = configuration.store().get(address); + FunctionType const* type { nullptr }; + instance->visit([&](auto const& function) { type = &function.type(); }); + if (source == CallAddressSource::IndirectCall || source == CallAddressSource::IndirectTailCall) { + TRAP_IF_NOT(type->parameters().size() <= configuration.value_stack().size()); + } + Vector args; + 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); + + configuration.value_stack().remove(configuration.value_stack().size() - span.size(), span.size()); + } + } + + if (source == CallAddressSource::DirectTailCall || source == CallAddressSource::IndirectTailCall) { + auto prep_outcome = configuration.prepare_call(address, args, true); + if (prep_outcome.is_error()) { + m_trap = prep_outcome.release_error(); + return Outcome::Return; + } + + final_outcome = Outcome::Return; // At this point we can only ever return (unless we succeed in tail-calling). + if (prep_outcome.value().has_value()) { + result = prep_outcome.value()->function()(configuration, args); + } else { + configuration.ip() = 0; + return static_cast(0); // Continue from IP 0 in the new frame. + } + } else { + if (instance->has()) { + CallFrameHandle handle { *this, configuration }; + result = configuration.call(*this, address, move(args)); + } else { + result = configuration.call(*this, address, move(args)); + } + } + + if (result.is_trap()) { + m_trap = move(result.trap()); return Outcome::Return; } - - final_outcome = Outcome::Return; // At this point we can only ever return (unless we succeed in tail-calling). - if (prep_outcome.value().has_value()) { - result = prep_outcome.value()->function()(configuration, args); - } else { - configuration.ip() = 0; - return static_cast(0); // Continue from IP 0 in the new frame. - } - } else { - if (instance->has()) { - CallFrameHandle handle { *this, configuration }; - result = configuration.call(*this, address, move(args)); - } else { - result = configuration.call(*this, address, move(args)); - } - } - - if (result.is_trap()) { - m_trap = move(result.trap()); - return Outcome::Return; } if (!result.values().is_empty()) { - configuration.value_stack().ensure_capacity(configuration.value_stack().size() + result.values().size()); - for (auto& entry : result.values().in_reverse()) - configuration.value_stack().unchecked_append(entry); + if (call_type == CallType::UsingRegisters) { + configuration.push_to_destination(result.values().take_first(), addresses.destination); + } else { + configuration.value_stack().ensure_capacity(configuration.value_stack().size() + result.values().size()); + for (auto& entry : result.values().in_reverse()) + configuration.value_stack().unchecked_append(entry); + } } return final_outcome; @@ -4590,10 +4597,6 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span Vector dependent_ids; bool variadic_or_unknown = false; - auto const is_known_call = opcode == Instructions::synthetic_call_00 || opcode == Instructions::synthetic_call_01 - || opcode == Instructions::synthetic_call_10 || opcode == Instructions::synthetic_call_11 - || opcode == Instructions::synthetic_call_20 || opcode == Instructions::synthetic_call_21 - || opcode == Instructions::synthetic_call_30 || opcode == Instructions::synthetic_call_31; switch (opcode.value()) { #define M(name, _, ins, outs) \ @@ -4651,9 +4654,6 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span auto& value = values.get(input_value).value(); value.uses.append(i); value.last_use = max(value.last_use, i); - - if (is_known_call) - forced_stack_values.append(input_value); } instr_to_input_values.set(i, input_ids); instr_to_dependent_values.set(i, dependent_ids); @@ -4666,9 +4666,6 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span instr_to_output_value.set(i, id); output_id = id; ensure_id_space(id); - - if (is_known_call) - forced_stack_values.append(id); } // Alias the output with the last input, if one exists. @@ -4697,31 +4694,6 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span for (size_t i = 0; i < final_roots.size(); ++i) final_roots[i] = find_root(i); - // One more pass to ensure that all inputs and outputs of known calls are forced to the stack after aliases are resolved. - for (size_t i = 0; i < result.dispatches.size(); ++i) { - auto const opcode = result.dispatches[i].instruction->opcode(); - auto const is_known_call = opcode == Instructions::synthetic_call_00 || opcode == Instructions::synthetic_call_01 - || opcode == Instructions::synthetic_call_10 || opcode == Instructions::synthetic_call_11 - || opcode == Instructions::synthetic_call_20 || opcode == Instructions::synthetic_call_21 - || opcode == Instructions::synthetic_call_30 || opcode == Instructions::synthetic_call_31; - - if (is_known_call) { - if (auto input_ids = instr_to_input_values.get(i); input_ids.has_value()) { - for (auto input_id : *input_ids) { - if (input_id.value() < final_roots.size()) { - stack_forced_roots.set(final_roots[input_id.value()]); - } - } - } - - if (auto output_id = instr_to_output_value.get(i); output_id.has_value()) { - if (output_id->value() < final_roots.size()) { - stack_forced_roots.set(final_roots[output_id->value()]); - } - } - } - } - struct LiveInterval { ValueID value_id; IP start; diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h index 82e4c3f38d..e9ece89932 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h @@ -69,6 +69,11 @@ struct WASM_API BytecodeInterpreter final : public Interpreter { IndirectTailCall, }; + enum class CallType { + UsingRegisters, + UsingStack, + }; + template void interpret_impl(Configuration&, Expression const&); @@ -96,7 +101,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter { template typename SetSign, typename VectorType = Native128ByteVectorOf> VectorType pop_vector(Configuration&, size_t source, SourcesAndDestination const&); bool store_to_memory(Configuration&, Instruction::MemoryArgument const&, ReadonlyBytes data, u32 base); - Outcome call_address(Configuration&, FunctionAddress, CallAddressSource = CallAddressSource::DirectCall); + Outcome call_address(Configuration&, FunctionAddress, SourcesAndDestination const&, CallAddressSource = CallAddressSource::DirectCall, CallType = CallType::UsingStack); template bool store_to_memory(MemoryInstance&, u64 address, T value); diff --git a/Tests/LibJS/Runtime/test-common.js b/Tests/LibJS/Runtime/test-common.js index 85cdf37ccb..ccf2d98bc3 100644 --- a/Tests/LibJS/Runtime/test-common.js +++ b/Tests/LibJS/Runtime/test-common.js @@ -560,6 +560,8 @@ class ExpectationError extends Error { }; test = (message, callback) => { + __reportTest__(message, "start"); + if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {}; const suite = __TestResults__[suiteMessage]; @@ -569,6 +571,7 @@ class ExpectationError extends Error { details: "Another test with the same message did already run", duration: 0, }; + __reportTest__(message, "fail"); return; } @@ -581,16 +584,19 @@ class ExpectationError extends Error { result: "pass", duration: time_ms(), }; + __reportTest__(message, "pass"); } catch (e) { suite[message] = { result: "fail", details: String(e), duration: time_ms(), }; + __reportTest__(message, "fail"); } }; asyncTest = async (message, callback) => { + __reportTest__(message, "start"); if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {}; const suite = __TestResults__[suiteMessage]; @@ -600,6 +606,7 @@ class ExpectationError extends Error { details: "Another test with the same message did already run", duration: 0, }; + __reportTest__(message, "fail"); return; } @@ -612,16 +619,19 @@ class ExpectationError extends Error { result: "pass", duration: time_ms(), }; + __reportTest__(message, "pass"); } catch (e) { suite[message] = { result: "fail", details: String(e), duration: time_ms(), }; + __reportTest__(message, "fail"); } }; test.skip = (message, callback) => { + __reportTest__(message, "start"); if (typeof callback !== "function") throw new Error("test.skip has invalid second argument (must be a function)"); @@ -634,6 +644,7 @@ class ExpectationError extends Error { details: "Another test with the same message did already run", duration: 0, }; + __reportTest__(message, "fail"); return; } @@ -641,9 +652,11 @@ class ExpectationError extends Error { result: "skip", duration: 0, }; + __reportTest__(message, "skip"); }; test.xfail = (message, callback) => { + __reportTest__(message, "start"); if (!__TestResults__[suiteMessage]) __TestResults__[suiteMessage] = {}; const suite = __TestResults__[suiteMessage]; @@ -653,6 +666,7 @@ class ExpectationError extends Error { details: "Another test with the same message did already run", duration: 0, }; + __reportTest__(message, "fail"); return; } @@ -666,11 +680,13 @@ class ExpectationError extends Error { details: "Expected test to fail, but it passed", duration: time_ms(), }; + __reportTest__(message, "fail"); } catch (e) { suite[message] = { result: "xfail", duration: time_ms(), }; + __reportTest__(message, "xfail"); } }; diff --git a/Utilities/wasm.cpp b/Utilities/wasm.cpp index d35d6e6c80..cdb3238c43 100644 --- a/Utilities/wasm.cpp +++ b/Utilities/wasm.cpp @@ -782,8 +782,10 @@ ErrorOr ladybird_main(Main::Arguments arguments) } } - if (!regs.is_empty()) - regs = ByteString::formatted(" {{{:<33} }}", regs); + if (regs.is_empty()) + regs = ByteString::formatted(" {{{:-<34}}}", regs); + else + regs = ByteString::formatted(" {{{: <33} }}", regs); TRY(g_stdout->write_until_depleted(ByteString::formatted(" [{:>03}]", ip))); TRY(g_stdout->write_until_depleted(regs.bytes()));