LibWasm: Take call arguments and results on registers if possible

This commit is contained in:
Ali Mohammad Pur 2025-11-07 22:24:07 +01:00 committed by Ali Mohammad Pur
parent b89ecfc6bc
commit be9d8288ef
6 changed files with 160 additions and 114 deletions

View file

@ -177,6 +177,10 @@ public:
}
virtual void initialize(JS::Realm&) override;
virtual ~TestRunnerGlobalObject() override = default;
JS_DECLARE_NATIVE_FUNCTION(report_test);
Function<void(String, JS::Value)> 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<TestRunnerGlobalObject>(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<ByteBuffer> {
@ -271,6 +289,34 @@ inline Vector<ByteString> 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<TestRunnerGlobalObject>(*realm);
if (this->needs_timings()) {
global_object->on_test_reported = print_test_timings;
}
return global_object;
},
nullptr));

View file

@ -51,6 +51,8 @@ public:
bool needs_detailed_suites() const { return m_detailed_json; }
Vector<Test::Suite> const& suites() const { return *m_suites; }
bool needs_timings() const { return m_print_times; }
Vector<Test::Suite>& ensure_suites()
{
return m_suites.ensure([] { return Vector<Suite> {}; });

View file

@ -14,6 +14,7 @@
#include <AK/QuickSort.h>
#include <AK/RedBlackTree.h>
#include <AK/SIMDExtras.h>
#include <AK/ScopedValueRollback.h>
#include <AK/Time.h>
#include <LibWasm/AbstractMachine/AbstractMachine.h>
#include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
@ -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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<i32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(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<PushType>(read_value<ReadType>(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<i32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(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<M, N, SetSign>;
using V128 = NativeVectorType<M * 2, N, SetSign>;
@ -3927,6 +3913,7 @@ bool BytecodeInterpreter::load_and_push_mxn(Configuration& configuration, Instru
ByteReader::load(slice.data(), bytes);
entry = Value(bit_cast<u128>(convert_vector<V128>(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<u128>();
auto base = configuration.take_source(1, addresses.sources).to<u32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(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<u8*>(&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<u32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(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<i32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(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<NativeIntegralType<M>>(slice);
dbgln_if(WASM_TRACE_DEBUG, " loaded value: {}", value);
set_top_m_splat<M, NativeIntegralType>(configuration, value, addresses);
return false;
}
@ -4040,61 +4032,76 @@ VectorType BytecodeInterpreter::pop_vector(Configuration& configuration, size_t
return bit_cast<VectorType>(configuration.take_source(source, addresses.sources).to<u128>());
}
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<Value, 8> 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<ScopedValueRollback<decltype(configuration.regs)>> 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<Value, 8> 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<Outcome>(0); // Continue from IP 0 in the new frame.
}
} else {
if (instance->has<WasmFunction>()) {
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<Outcome>(0); // Continue from IP 0 in the new frame.
}
} else {
if (instance->has<WasmFunction>()) {
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<ValueID> 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;

View file

@ -69,6 +69,11 @@ struct WASM_API BytecodeInterpreter final : public Interpreter {
IndirectTailCall,
};
enum class CallType {
UsingRegisters,
UsingStack,
};
template<bool HasCompiledList, bool HasDynamicInsnLimit, bool HaveDirectThreadingInfo>
void interpret_impl(Configuration&, Expression const&);
@ -96,7 +101,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter {
template<typename M, template<typename> typename SetSign, typename VectorType = Native128ByteVectorOf<M, SetSign>>
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<typename T>
bool store_to_memory(MemoryInstance&, u64 address, T value);

View file

@ -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");
}
};

View file

@ -782,8 +782,10 @@ ErrorOr<int> 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()));