LibWasm: Improve table support

Implements `table.get`, `table.set`, `elem.drop`, `table.size`,
and `table.grow`. Also fixes a few issues when generating ref-related
spectests. Also changes the `TableInstance` type to use
`Vector<Reference>` instead of `Vector<Optional<Reference>>`, because
the ability to be null is already encoded in the `Reference` type.
This commit is contained in:
Diego 2024-05-31 17:14:49 -07:00 committed by Ali Mohammad Pur
parent 3ca6dfba48
commit d906255cbb
Notes: sideshowbarker 2024-07-18 03:35:30 +09:00
6 changed files with 82 additions and 29 deletions

View file

@ -62,6 +62,9 @@ def parse_typed_value(ast):
'i64.const': 'i64', 'i64.const': 'i64',
'f32.const': 'float', 'f32.const': 'float',
'f64.const': 'double', 'f64.const': 'double',
'ref.null': 'null',
'ref.extern': 'i32',
'ref.func': 'i32',
'v128.const': 'bigint', 'v128.const': 'bigint',
} }
@ -331,9 +334,9 @@ def generate(ast):
"function": { "function": {
"module": module, "module": module,
"name": name, "name": name,
"args": list(parse_typed_value(x) for x in entry[1][arg + 2:]) "args": [parse_typed_value(entry[2])] if len(entry) == 3 else []
}, },
"result": parse_typed_value(entry[2]) if len(entry) == 3 + arg else None "result": None
}) })
elif len(entry) > 1 and entry[0][0] == 'register': elif len(entry) > 1 and entry[0][0] == 'register':
if len(entry) == 3: if len(entry) == 3:
@ -370,6 +373,8 @@ def genarg(spec):
x = spec['value'] x = spec['value']
if spec['type'] == 'bigint': if spec['type'] == 'bigint':
return f"0x{x}n" return f"0x{x}n"
if spec['type'] == 'null':
return 'null'
if spec['type'] in ('i32', 'i64'): if spec['type'] in ('i32', 'i64'):
if x.startswith('0x'): if x.startswith('0x'):

View file

@ -242,10 +242,18 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
break; break;
} }
case Wasm::ValueType::Kind::FunctionReference: case Wasm::ValueType::Kind::FunctionReference:
if (argument.is_null()) {
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } }));
break;
}
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast<u64>(double_value) } })); arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast<u64>(double_value) } }));
break; break;
case Wasm::ValueType::Kind::ExternReference: case Wasm::ValueType::Kind::ExternReference:
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast<u64>(double_value) } })); if (argument.is_null()) {
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExternReference) } }));
break;
}
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Extern { static_cast<u64>(double_value) } }));
break; break;
case Wasm::ValueType::Kind::NullFunctionReference: case Wasm::ValueType::Kind::NullFunctionReference:
arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } })); arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } }));

View file

@ -34,7 +34,7 @@ Optional<FunctionAddress> Store::allocate(HostFunction&& function)
Optional<TableAddress> Store::allocate(TableType const& type) Optional<TableAddress> Store::allocate(TableType const& type)
{ {
TableAddress address { m_tables.size() }; TableAddress address { m_tables.size() };
Vector<Optional<Reference>> elements; Vector<Reference> elements;
elements.resize(type.limits().min()); elements.resize(type.limits().min());
m_tables.empend(TableInstance { type, move(elements) }); m_tables.empend(TableInstance { type, move(elements) });
return address; return address;
@ -252,10 +252,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
auto active_ptr = segment.mode.get_pointer<ElementSection::Active>(); auto active_ptr = segment.mode.get_pointer<ElementSection::Active>();
if (!active_ptr) if (!active_ptr)
continue; continue;
if (active_ptr->index.value() != 0) {
instantiation_result = InstantiationError { "Non-zero table referenced by active element segment" };
return IterationDecision::Break;
}
Configuration config { m_store }; Configuration config { m_store };
if (m_should_limit_instruction_count) if (m_should_limit_instruction_count)
config.enable_instruction_count_limit(); config.enable_instruction_count_limit();
@ -275,11 +271,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
instantiation_result = InstantiationError { "Element section initialisation returned invalid table initial offset" }; instantiation_result = InstantiationError { "Element section initialisation returned invalid table initial offset" };
return IterationDecision::Break; return IterationDecision::Break;
} }
if (main_module_instance.tables().size() < 1) { auto table_instance = m_store.get(main_module_instance.tables()[active_ptr->index.value()]);
instantiation_result = InstantiationError { "Element section initialisation references nonexistent table" };
return IterationDecision::Break;
}
auto table_instance = m_store.get(main_module_instance.tables()[0]);
if (current_index >= main_module_instance.elements().size()) { if (current_index >= main_module_instance.elements().size()) {
instantiation_result = InstantiationError { "Invalid element referenced by active element segment" }; instantiation_result = InstantiationError { "Invalid element referenced by active element segment" };
return IterationDecision::Break; return IterationDecision::Break;

View file

@ -61,6 +61,10 @@ public:
: m_ref(move(ref)) : m_ref(move(ref))
{ {
} }
explicit Reference()
: m_ref(Reference::Null { ValueType(ValueType::Kind::FunctionReference) })
{
}
auto& ref() const { return m_ref; } auto& ref() const { return m_ref; }
@ -370,7 +374,7 @@ using FunctionInstance = Variant<WasmFunction, HostFunction>;
class TableInstance { class TableInstance {
public: public:
explicit TableInstance(TableType const& type, Vector<Optional<Reference>> elements) explicit TableInstance(TableType const& type, Vector<Reference> elements)
: m_elements(move(elements)) : m_elements(move(elements))
, m_type(type) , m_type(type)
{ {
@ -380,15 +384,18 @@ public:
auto& elements() { return m_elements; } auto& elements() { return m_elements; }
auto& type() const { return m_type; } auto& type() const { return m_type; }
bool grow(size_t size_to_grow, Reference const& fill_value) bool grow(u32 size_to_grow, Reference const& fill_value)
{ {
if (size_to_grow == 0) if (size_to_grow == 0)
return true; return true;
auto new_size = m_elements.size() + size_to_grow; size_t new_size = m_elements.size() + size_to_grow;
if (auto max = m_type.limits().max(); max.has_value()) { if (auto max = m_type.limits().max(); max.has_value()) {
if (max.value() < new_size) if (max.value() < new_size)
return false; return false;
} }
if (new_size >= NumericLimits<u32>::max()) {
return false;
}
auto previous_size = m_elements.size(); auto previous_size = m_elements.size();
if (m_elements.try_resize(new_size).is_error()) if (m_elements.try_resize(new_size).is_error())
return false; return false;
@ -398,7 +405,7 @@ public:
} }
private: private:
Vector<Optional<Reference>> m_elements; Vector<Reference> m_elements;
TableType m_type; TableType m_type;
}; };

View file

@ -665,9 +665,8 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi
TRAP_IF_NOT(index.value() >= 0); TRAP_IF_NOT(index.value() >= 0);
TRAP_IF_NOT(static_cast<size_t>(index.value()) < table_instance->elements().size()); TRAP_IF_NOT(static_cast<size_t>(index.value()) < table_instance->elements().size());
auto element = table_instance->elements()[index.value()]; auto element = table_instance->elements()[index.value()];
TRAP_IF_NOT(element.has_value()); TRAP_IF_NOT(element.ref().has<Reference::Func>());
TRAP_IF_NOT(element->ref().has<Reference::Func>()); auto address = element.ref().get<Reference::Func>().address;
auto address = element->ref().get<Reference::Func>().address;
dbgln_if(WASM_TRACE_DEBUG, "call_indirect({} -> {})", index.value(), address.value()); dbgln_if(WASM_TRACE_DEBUG, "call_indirect({} -> {})", index.value(), address.value());
call_address(configuration, address); call_address(configuration, address);
return; return;
@ -859,9 +858,55 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi
*configuration.store().get(data_address) = DataInstance({}); *configuration.store().get(data_address) = DataInstance({});
return; return;
} }
case Instructions::table_get.value(): case Instructions::elem_drop.value(): {
case Instructions::table_set.value(): auto elem_index = instruction.arguments().get<ElementIndex>();
goto unimplemented; auto address = configuration.frame().module().elements()[elem_index.value()];
auto elem = configuration.store().get(address);
*configuration.store().get(address) = ElementInstance(elem->type(), {});
return;
}
case Instructions::table_set.value(): {
auto ref = *configuration.stack().pop().get<Value>().to<Reference>();
auto index = (size_t)(*configuration.stack().pop().get<Value>().to<i32>());
auto table_index = instruction.arguments().get<TableIndex>();
auto address = configuration.frame().module().tables()[table_index.value()];
auto table = configuration.store().get(address);
TRAP_IF_NOT(index < table->elements().size());
table->elements()[index] = ref;
return;
}
case Instructions::table_get.value(): {
auto index = (size_t)(*configuration.stack().pop().get<Value>().to<i32>());
auto table_index = instruction.arguments().get<TableIndex>();
auto address = configuration.frame().module().tables()[table_index.value()];
auto table = configuration.store().get(address);
TRAP_IF_NOT(index < table->elements().size());
auto ref = table->elements()[index];
configuration.stack().push(Value(ref));
return;
}
case Instructions::table_grow.value(): {
auto size = *configuration.stack().pop().get<Value>().to<u32>();
auto fill_value = *configuration.stack().pop().get<Value>().to<Reference>();
auto table_index = instruction.arguments().get<TableIndex>();
auto address = configuration.frame().module().tables()[table_index.value()];
auto table = configuration.store().get(address);
auto previous_size = table->elements().size();
auto did_grow = table->grow(size, fill_value);
if (!did_grow) {
configuration.stack().push(Value((i32)-1));
} else {
configuration.stack().push(Value((i32)previous_size));
}
return;
}
case Instructions::table_size.value(): {
auto table_index = instruction.arguments().get<TableIndex>();
auto address = configuration.frame().module().tables()[table_index.value()];
auto table = configuration.store().get(address);
configuration.stack().push(Value((i32)table->elements().size()));
return;
}
case Instructions::ref_null.value(): { case Instructions::ref_null.value(): {
auto type = instruction.arguments().get<ValueType>(); auto type = instruction.arguments().get<ValueType>();
configuration.stack().push(Value(Reference(Reference::Null { type }))); configuration.stack().push(Value(Reference(Reference::Null { type })));
@ -1503,13 +1548,9 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi
case Instructions::f64x2_convert_low_i32x4_s.value(): case Instructions::f64x2_convert_low_i32x4_s.value():
case Instructions::f64x2_convert_low_i32x4_u.value(): case Instructions::f64x2_convert_low_i32x4_u.value():
case Instructions::table_init.value(): case Instructions::table_init.value():
case Instructions::elem_drop.value():
case Instructions::table_copy.value(): case Instructions::table_copy.value():
case Instructions::table_grow.value():
case Instructions::table_size.value():
case Instructions::table_fill.value(): case Instructions::table_fill.value():
default: default:
unimplemented:;
dbgln_if(WASM_TRACE_DEBUG, "Instruction '{}' not implemented", instruction_name(instruction.opcode())); dbgln_if(WASM_TRACE_DEBUG, "Instruction '{}' not implemented", instruction_name(instruction.opcode()));
m_trap = Trap { ByteString::formatted("Unimplemented instruction {}", instruction_name(instruction.opcode())) }; m_trap = Trap { ByteString::formatted("Unimplemented instruction {}", instruction_name(instruction.opcode())) };
return; return;

View file

@ -106,10 +106,10 @@ WebIDL::ExceptionOr<JS::Value> Table::get(u32 index) const
return vm.throw_completion<JS::RangeError>("Table element index out of range"sv); return vm.throw_completion<JS::RangeError>("Table element index out of range"sv);
auto& ref = table->elements()[index]; auto& ref = table->elements()[index];
if (!ref.has_value()) if (!ref.ref().has<Wasm::Reference::Null>())
return JS::js_undefined(); return JS::js_undefined();
Wasm::Value wasm_value { ref.value() }; Wasm::Value wasm_value { ref };
return Detail::to_js_value(vm, wasm_value); return Detail::to_js_value(vm, wasm_value);
} }