LibWasm: Correctly handle memory64's addr types in the interpreter

This commit is contained in:
Ali Mohammad Pur 2026-06-10 12:23:41 +02:00 committed by Andreas Kling
parent f81717d4c8
commit c414f31ecf
5 changed files with 109 additions and 65 deletions

View file

@ -631,6 +631,8 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
for (auto& segment : module.element_section().segments()) {
auto current_index = index;
++index;
if (current_index >= main_module_instance.elements().size())
return InstantiationError { "Invalid element referenced by active element segment" };
auto active_ptr = segment.mode.get_pointer<ElementSection::Active>();
auto elem_instance = m_store.get(main_module_instance.elements()[current_index]);
if (!active_ptr) {
@ -638,6 +640,8 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
*elem_instance = ElementInstance(elem_instance->type(), {});
continue;
}
if (active_ptr->index.value() >= main_module_instance.tables().size())
return InstantiationError { "Invalid table referenced by active element segment" };
Configuration config { m_store };
if (m_should_limit_instruction_count)
config.enable_instruction_count_limit();
@ -649,14 +653,15 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
auto result = config.execute(interpreter);
if (result.is_trap())
return InstantiationError { "Element section initialisation trapped", move(result.trap()) };
auto d = result.values().first().to<i32>();
auto table_instance = m_store.get(main_module_instance.tables()[active_ptr->index.value()]);
if (current_index >= main_module_instance.elements().size())
return InstantiationError { "Invalid element referenced by active element segment" };
if (!table_instance || !elem_instance)
return InstantiationError { "Invalid element referenced by active element segment" };
auto total_size = saturating_add(elem_instance->references().size(), static_cast<size_t>(d));
auto d = result.values().first().to<u64>();
if (table_instance->type().limits().address_type() == AddressType::I32)
d = static_cast<u32>(d);
auto total_size = saturating_add<u64>(elem_instance->references().size(), d);
if (total_size > table_instance->elements().size())
return InstantiationError { "Table instantiation out of bounds" };
@ -686,7 +691,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
auto result = config.execute(interpreter);
if (result.is_trap())
return InstantiationError { "Data section initialisation trapped", move(result.trap()) };
size_t offset = result.values().first().to<u64>();
if (main_module_instance.memories().size() <= data.index.value()) {
return InstantiationError {
ByteString::formatted("Data segment referenced out-of-bounds memory ({}) of max {} entries",
@ -701,6 +705,9 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
auto address = main_module_instance.memories()[data.index.value()];
auto instance = m_store.get(address);
u64 offset = result.values().first().to<u64>();
if (instance->type().limits().address_type() == AddressType::I32)
offset = static_cast<u32>(offset);
Checked<size_t> checked_offset = data.init.size();
checked_offset += offset;
if (checked_offset.has_overflow() || checked_offset > instance->size()) {
@ -753,20 +760,18 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
module_instance.functions().extend(own_functions);
// FIXME: What if this fails?
for (auto& table : module.table_section().tables()) {
auto table_address = m_store.allocate(table.type());
if (table_address.has_value()) {
module_instance.tables().append(*table_address);
}
if (!table_address.has_value())
return InstantiationError { "Failed to allocate a table instance" };
module_instance.tables().append(*table_address);
}
for (auto& memory : module.memory_section().memories()) {
auto memory_address = m_store.allocate(memory.type());
if (memory_address.has_value()) {
module_instance.memories().append(*memory_address);
}
if (!memory_address.has_value())
return InstantiationError { "Failed to allocate a memory instance" };
module_instance.memories().append(*memory_address);
}
size_t index = 0;

View file

@ -198,9 +198,7 @@ public:
return bit_cast<f64>(m_value.low());
}
if constexpr (IsSame<T, Reference>) {
switch (m_value.high() & 3) {
case 0:
return Reference { Reference::Func { bit_cast<FunctionAddress>(m_value.low()), bit_cast<Wasm::Module*>(m_value.high()) } };
switch (m_value.high()) {
case 1:
return Reference { Reference::Extern { bit_cast<ExternAddress>(m_value.low()) } };
case 2:
@ -211,6 +209,8 @@ public:
return Reference { Reference::Null { ValueType(ValueType::Kind::ExceptionReference) } };
case 5:
return Reference { Reference::Exception { bit_cast<ExceptionAddress>(m_value.low()) } };
default:
return Reference { Reference::Func { bit_cast<FunctionAddress>(m_value.low()), bit_cast<Wasm::Module*>(m_value.high()) } };
}
}
VERIFY_NOT_REACHED();

View file

@ -338,6 +338,16 @@ struct ConvertToRaw<double> {
u64 operator()(double value) const { return bit_cast<LittleEndian<u64>>(value); }
};
// Memory address operands are typed as the memory's address type (proposal 'memory64').
// 32-bit addresses are stored sign-extended in the Value, so they must be truncated back to u32; 64-bit addresses are used as-is.
static ALWAYS_INLINE u64 memory_base_address(MemoryInstance const& memory, Value const& entry)
{
auto base = entry.to<u64>();
if (memory.type().limits().address_type() == AddressType::I32)
return static_cast<u32>(base);
return base;
}
#define TRAP_IF_NOT(x, ...) \
do { \
if (trap_if_not(x, #x##sv __VA_OPT__(, ) __VA_ARGS__)) { \
@ -2759,7 +2769,10 @@ HANDLE_INSTRUCTION(memory_size)
auto instance = configuration.store().get(address);
auto pages = instance->size() / Constants::page_size;
dbgln_if(WASM_TRACE_DEBUG, "memory.size -> stack({})", pages);
configuration.push_to_destination<source_address_mix>(Value(static_cast<i32>(pages)), addresses.destination);
auto result = instance->type().limits().address_type() == AddressType::I32
? Value(static_cast<i32>(pages))
: Value(static_cast<i64>(pages));
configuration.push_to_destination<source_address_mix>(result, addresses.destination);
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
}
@ -2770,14 +2783,19 @@ HANDLE_INSTRUCTION(memory_grow)
auto& args = instruction->arguments().unsafe_get<Instruction::MemoryIndexArgument>();
auto address = configuration.frame().module().memories().data()[args.memory_index.value()];
auto instance = configuration.store().get(address);
u32 old_pages = instance->size() / Constants::page_size;
u64 old_pages = instance->size() / Constants::page_size;
auto& entry = configuration.source_value<source_address_mix>(0, addresses.sources); // bounds checked by verifier.
auto new_pages = entry.template to<u32>();
bool const is_address32 = instance->type().limits().address_type() == AddressType::I32;
auto new_pages = entry.template to<u64>();
if (is_address32)
new_pages = static_cast<u32>(new_pages);
dbgln_if(WASM_TRACE_DEBUG, "memory.grow({}), previously {} pages...", new_pages, old_pages);
if (instance->grow(static_cast<u64>(new_pages) * Constants::page_size))
entry = Value(static_cast<i32>(old_pages));
Checked<u64> size_to_grow { new_pages };
size_to_grow *= Constants::page_size;
if (!size_to_grow.has_overflow() && instance->grow(size_to_grow.value()))
entry = is_address32 ? Value(static_cast<i32>(old_pages)) : Value(static_cast<i64>(old_pages));
else
entry = Value(static_cast<i32>(-1));
entry = is_address32 ? Value(static_cast<i32>(-1)) : Value(static_cast<i64>(-1));
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
}
@ -2790,9 +2808,9 @@ HANDLE_INSTRUCTION(memory_fill)
auto address = configuration.frame().module().memories().data()[args.memory_index.value()];
auto instance = configuration.store().get(address);
// bounds checked by verifier.
auto const count = configuration.take_source<source_address_mix>(0, addresses.sources).template to<u32>();
auto const count = memory_base_address(*instance, configuration.take_source<source_address_mix>(0, addresses.sources));
auto const value = static_cast<u8>(configuration.take_source<source_address_mix>(1, addresses.sources).template to<u32>());
auto const destination_offset = configuration.take_source<source_address_mix>(2, addresses.sources).template to<u32>();
auto const destination_offset = memory_base_address(*instance, configuration.take_source<source_address_mix>(2, addresses.sources));
Checked<u64> checked_end = destination_offset;
checked_end += count;
@ -2820,13 +2838,13 @@ HANDLE_INSTRUCTION(memory_copy)
auto source_instance = configuration.store().get(source_address);
auto destination_instance = configuration.store().get(destination_address);
// Wasm memory.copy operands are i32 values used as unsigned offsets/counts.
auto count = configuration.take_source<source_address_mix>(0, addresses.sources).template to<u32>();
auto source_offset = configuration.take_source<source_address_mix>(1, addresses.sources).template to<u32>();
auto destination_offset = configuration.take_source<source_address_mix>(2, addresses.sources).template to<u32>();
auto const& count_memory = source_instance->type().limits().address_type() == AddressType::I32 ? *source_instance : *destination_instance;
auto count = memory_base_address(count_memory, configuration.take_source<source_address_mix>(0, addresses.sources));
auto source_offset = memory_base_address(*source_instance, configuration.take_source<source_address_mix>(1, addresses.sources));
auto destination_offset = memory_base_address(*destination_instance, configuration.take_source<source_address_mix>(2, addresses.sources));
auto source_position = saturating_add(static_cast<size_t>(source_offset), static_cast<size_t>(count));
auto destination_position = saturating_add(static_cast<size_t>(destination_offset), static_cast<size_t>(count));
auto source_position = saturating_add<u64>(source_offset, count);
auto destination_position = saturating_add<u64>(destination_offset, count);
TRAP_IN_LOOP_IF_NOT(source_position <= source_instance->data().size());
TRAP_IN_LOOP_IF_NOT(destination_position <= destination_instance->data().size());
@ -2834,15 +2852,15 @@ HANDLE_INSTRUCTION(memory_copy)
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
if (destination_offset <= source_offset) {
for (u32 i = 0; i < count; ++i) {
for (u64 i = 0; i < count; ++i) {
auto value = source_instance->data()[source_offset + i];
if (interpreter.store_to_memory(*destination_instance, static_cast<u64>(destination_offset) + i, value))
if (interpreter.store_to_memory(*destination_instance, destination_offset + i, value))
return Outcome::Return;
}
} else {
for (u32 i = count; i > 0; --i) {
for (u64 i = count; i > 0; --i) {
auto value = source_instance->data()[source_offset + i - 1];
if (interpreter.store_to_memory(*destination_instance, static_cast<u64>(destination_offset) + i - 1, value))
if (interpreter.store_to_memory(*destination_instance, destination_offset + i - 1, value))
return Outcome::Return;
}
}
@ -2860,12 +2878,13 @@ HANDLE_INSTRUCTION(memory_init)
auto memory_address = configuration.frame().module().memories().data()[args.memory_index.value()];
auto memory = configuration.store().unsafe_get(memory_address);
// bounds checked by verifier.
// The count and source offset are always i32; the destination is typed as the memory's address type.
auto count = configuration.take_source<source_address_mix>(0, addresses.sources).template to<u32>();
auto source_offset = configuration.take_source<source_address_mix>(1, addresses.sources).template to<u32>();
auto destination_offset = configuration.take_source<source_address_mix>(2, addresses.sources).template to<u32>();
auto destination_offset = memory_base_address(*memory, configuration.take_source<source_address_mix>(2, addresses.sources));
auto source_position = saturating_add(static_cast<size_t>(source_offset), static_cast<size_t>(count));
auto destination_position = saturating_add(static_cast<size_t>(destination_offset), static_cast<size_t>(count));
auto source_position = saturating_add<u64>(source_offset, count);
auto destination_position = saturating_add<u64>(destination_offset, count);
TRAP_IN_LOOP_IF_NOT(source_position <= data.data().size());
TRAP_IN_LOOP_IF_NOT(destination_position <= memory->data().size());
@ -5593,10 +5612,13 @@ bool BytecodeInterpreter::load_and_push(Configuration& configuration, Instructio
auto& address = configuration.frame().module().memories().data()[arg.memory_index.value()];
auto memory = configuration.store().unsafe_get(address);
auto& entry = configuration.source_value<mix>(0, addresses.sources); // bounds checked by verifier.
auto base = entry.template to<i32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + arg.offset;
auto base = memory_base_address(*memory, entry);
Checked<u64> end_address { base };
end_address += arg.offset;
end_address += sizeof(ReadType);
u64 instance_address = base + arg.offset;
dbgln_if(WASM_TRACE_DEBUG, "load({} : {}) -> stack", instance_address, sizeof(ReadType));
if (instance_address + sizeof(ReadType) > memory->size()) {
if (end_address.has_overflow() || end_address.value() > memory->size()) {
m_trap = Trap::from_string("Memory access out of bounds");
dbgln_if(WASM_TRACE_DEBUG, "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;
@ -5620,10 +5642,13 @@ bool BytecodeInterpreter::load_and_push_mxn(Configuration& configuration, Instru
auto& address = configuration.frame().module().memories().data()[arg.memory_index.value()];
auto memory = configuration.store().unsafe_get(address);
auto& entry = configuration.source_value<SourceAddressMix::Any>(0, addresses.sources); // bounds checked by verifier.
auto base = entry.template to<i32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + arg.offset;
auto base = memory_base_address(*memory, entry);
Checked<u64> end_address { base };
end_address += arg.offset;
end_address += M * N / 8;
u64 instance_address = base + arg.offset;
dbgln_if(WASM_TRACE_DEBUG, "vec-load({} : {}) -> stack", instance_address, M * N / 8);
if (instance_address + M * N / 8 > memory->size()) {
if (end_address.has_overflow() || end_address.value() > memory->size()) {
m_trap = Trap::from_string("Memory access out of bounds");
return true;
}
@ -5650,10 +5675,13 @@ bool BytecodeInterpreter::load_and_push_lane_n(Configuration& configuration, Ins
auto memory = configuration.store().unsafe_get(address);
// bounds checked by verifier.
auto vector = configuration.take_source<SourceAddressMix::Any>(0, addresses.sources).template to<u128>();
auto base = configuration.take_source<SourceAddressMix::Any>(1, addresses.sources).template to<u32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + memarg_and_lane.memory.offset;
auto base = memory_base_address(*memory, configuration.take_source<SourceAddressMix::Any>(1, addresses.sources));
Checked<u64> end_address { base };
end_address += memarg_and_lane.memory.offset;
end_address += N / 8;
u64 instance_address = 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()) {
if (end_address.has_overflow() || end_address.value() > memory->size()) {
m_trap = Trap::from_string("Memory access out of bounds");
return true;
}
@ -5672,10 +5700,13 @@ bool BytecodeInterpreter::load_and_push_zero_n(Configuration& configuration, Ins
auto& address = configuration.frame().module().memories().data()[memarg_and_lane.memory_index.value()];
auto memory = configuration.store().unsafe_get(address);
// bounds checked by verifier.
auto base = configuration.take_source<SourceAddressMix::Any>(0, addresses.sources).template to<u32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + memarg_and_lane.offset;
auto base = memory_base_address(*memory, configuration.take_source<SourceAddressMix::Any>(0, addresses.sources));
Checked<u64> end_address { base };
end_address += memarg_and_lane.offset;
end_address += N / 8;
u64 instance_address = base + memarg_and_lane.offset;
dbgln_if(WASM_TRACE_DEBUG, "load-zero({} : {}) -> stack", instance_address, N / 8);
if (instance_address + N / 8 > memory->size()) {
if (end_address.has_overflow() || end_address.value() > memory->size()) {
m_trap = Trap::from_string("Memory access out of bounds");
return true;
}
@ -5694,10 +5725,13 @@ bool BytecodeInterpreter::load_and_push_m_splat(Configuration& configuration, In
auto& address = configuration.frame().module().memories().data()[arg.memory_index.value()];
auto memory = configuration.store().unsafe_get(address);
auto& entry = configuration.source_value<SourceAddressMix::Any>(0, addresses.sources); // bounds checked by verifier.
auto base = entry.template to<i32>();
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + arg.offset;
auto base = memory_base_address(*memory, entry);
Checked<u64> end_address { base };
end_address += arg.offset;
end_address += M / 8;
u64 instance_address = base + arg.offset;
dbgln_if(WASM_TRACE_DEBUG, "vec-splat({} : {}) -> stack", instance_address, M / 8);
if (instance_address + M / 8 > memory->size()) {
if (end_address.has_overflow() || end_address.value() > memory->size()) {
m_trap = Trap::from_string("Memory access out of bounds");
return true;
}
@ -5896,7 +5930,7 @@ bool BytecodeInterpreter::store_value(Configuration& configuration, Instruction
{
auto& memarg = instruction.arguments().unsafe_get<Instruction::MemoryArgument>();
dbgln_if(WASM_TRACE_DEBUG, "stack({}) -> temporary({}b)", value, sizeof(StoreT));
auto base = configuration.take_source<SourceAddressMix::Any>(address_source, addresses.sources).template to<i32>();
auto base = configuration.take_source<SourceAddressMix::Any>(address_source, addresses.sources);
return store_to_memory(configuration, memarg, { &value, sizeof(StoreT) }, base);
}
@ -5907,16 +5941,21 @@ bool BytecodeInterpreter::pop_and_store_lane_n(Configuration& configuration, Ins
// bounds checked by verifier.
auto vector = configuration.take_source<SourceAddressMix::Any>(0, addresses.sources).template to<u128>();
auto src = bit_cast<u8*>(&vector) + memarg_and_lane.lane * N / 8;
auto base = configuration.take_source<SourceAddressMix::Any>(1, addresses.sources).template to<u32>();
auto base = configuration.take_source<SourceAddressMix::Any>(1, addresses.sources);
return store_to_memory(configuration, memarg_and_lane.memory, { src, N / 8 }, base);
}
bool BytecodeInterpreter::store_to_memory(Configuration& configuration, Instruction::MemoryArgument const& arg, ReadonlyBytes data, u32 base)
bool BytecodeInterpreter::store_to_memory(Configuration& configuration, Instruction::MemoryArgument const& arg, ReadonlyBytes data, Value const& base_value)
{
auto const& address = configuration.frame().module().memories().data()[arg.memory_index.value()];
auto memory = configuration.store().unsafe_get(address);
u64 instance_address = static_cast<u64>(base) + arg.offset;
return store_to_memory(*memory, instance_address, data);
Checked<u64> instance_address { memory_base_address(*memory, base_value) };
instance_address += arg.offset;
if (instance_address.has_overflow()) [[unlikely]] {
m_trap = Trap::from_string("Memory access out of bounds");
return true;
}
return store_to_memory(*memory, instance_address.value(), data);
}
template<typename T>

View file

@ -94,7 +94,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter {
void pop_and_push_m_splat(Configuration&, Instruction const&, SourcesAndDestination const&);
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);
bool store_to_memory(Configuration&, Instruction::MemoryArgument const&, ReadonlyBytes data, Value const& base);
Outcome call_address(Configuration&, FunctionAddress, SourcesAndDestination const&, CallAddressSource = CallAddressSource::DirectCall, CallType = CallType::UsingStack);
Outcome run_compiled_function_direct(Configuration&);
Outcome run_native_entry(Configuration&);

View file

@ -447,7 +447,7 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
case Instructions::memory_size.value():
case Instructions::memory_grow.value(): {
// op [multi-memory: memindex]|0x00
auto memory_index = TRY_READ(stream, u8, ParseError::ExpectedKindTag);
u32 memory_index = TRY_READ(stream, LEB128<u32>, ParseError::ExpectedKindTag);
return Instruction { opcode, MemoryIndexArgument { MemoryIndex(memory_index) } };
}
@ -656,8 +656,8 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
case Instructions::memory_init.value(): {
auto index = TRY(GenericIndexParser<DataIndex>::parse(stream));
// Proposal "multi-memory", literal 0x00 is replaced with a memory index.
auto memory_index = TRY_READ(stream, u8, ParseError::InvalidInput);
// Proposal "multi-memory", literal 0x00 is replaced with a memory index (LEB-128).
u32 memory_index = TRY_READ(stream, LEB128<u32>, ParseError::InvalidInput);
return Instruction { full_opcode, MemoryInitArgs { index, MemoryIndex(memory_index) } };
}
@ -666,18 +666,18 @@ ParseResult<Instruction> Instruction::parse(ConstrainedStream& stream)
return Instruction { full_opcode, index };
}
case Instructions::memory_copy.value(): {
// Proposal "multi-memory", literal 0x00 is replaced with two memory indices, destination and source, respectively.
// Proposal "multi-memory", literal 0x00 is replaced with two memory indices (LEB-128), destination and source, respectively.
MemoryIndex indices[] = { 0, 0 };
for (size_t i = 0; i < 2; ++i) {
auto memory_index = TRY_READ(stream, u8, ParseError::InvalidInput);
u32 memory_index = TRY_READ(stream, LEB128<u32>, ParseError::InvalidInput);
indices[i] = memory_index;
}
return Instruction { full_opcode, MemoryCopyArgs { indices[1], indices[0] } };
}
case Instructions::memory_fill.value(): {
// Proposal "multi-memory", literal 0x00 is replaced with a memory index.
auto memory_index = TRY_READ(stream, u8, ParseError::InvalidInput);
// Proposal "multi-memory", literal 0x00 is replaced with a memory index (LEB-128).
u32 memory_index = TRY_READ(stream, LEB128<u32>, ParseError::InvalidInput);
return Instruction { full_opcode, MemoryIndexArgument { MemoryIndex { memory_index } } };
}
case Instructions::table_init.value(): {