LibWasm+LibWeb: Properly track module lifetime with function refs

This commit is contained in:
Ali Mohammad Pur 2026-04-14 01:36:28 +02:00 committed by Ali Mohammad Pur
parent a33e148339
commit a36f6abedb
10 changed files with 103 additions and 36 deletions

View file

@ -248,7 +248,12 @@ FunctionInstance* Store::get(FunctionAddress address)
auto value = address.value();
if (m_functions.size() <= value)
return nullptr;
return &m_functions[value];
auto& instance = m_functions[value];
if (auto const* wasm = instance.get_pointer<WasmFunction>()) {
if (!wasm->try_module())
return nullptr;
}
return &instance;
}
Module const* Store::get_module_for(Wasm::FunctionAddress address)
@ -259,6 +264,14 @@ Module const* Store::get_module_for(Wasm::FunctionAddress address)
return function->get<WasmFunction>().module_ref().ptr();
}
RefPtr<ModuleInstance const> Store::get_module_instance_for(FunctionAddress address)
{
auto* function = get(address);
if (!function || function->has<HostFunction>())
return nullptr;
return function->get<WasmFunction>().try_module();
}
TableInstance* Store::get(TableAddress address)
{
auto value = address.value();
@ -337,7 +350,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
if (auto result = validate(const_cast<Module&>(module)); result.is_error())
return InstantiationError { ByteString::formatted("Validation failed: {}", result.error()) };
auto main_module_instance_pointer = make<ModuleInstance>();
auto main_module_instance_pointer = adopt_ref(*new ModuleInstance);
main_module_instance_pointer->cached_minimum_call_record_allocation_size = module.minimum_call_record_allocation_size();
auto& main_module_instance = *main_module_instance_pointer;
@ -345,7 +358,8 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
Vector<Value> global_values;
Vector<Vector<Reference>> elements;
ModuleInstance auxiliary_instance;
auto auxiliary_instance_ptr = adopt_ref(*new ModuleInstance);
auto& auxiliary_instance = *auxiliary_instance_ptr;
auxiliary_instance.cached_minimum_call_record_allocation_size = module.minimum_call_record_allocation_size();
@ -453,7 +467,8 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
if (result.is_trap())
return InstantiationError { "Global instantiation trapped", move(result.trap()) };
global_values.append(result.values().first());
auxiliary_instance.globals().append(m_store.allocate(entry.type(), result.values().first()).release_value());
auto addr = m_store.allocate(entry.type(), result.values().first()).release_value();
auxiliary_instance.globals().append(addr);
}
if (auto result = allocate_all_initial_phase(module, main_module_instance, externs, global_values, module_functions); result.has_value())
@ -520,8 +535,12 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
return InstantiationError { "Table instantiation out of bounds" };
size_t i = 0;
for (auto it = elem_instance->references().begin(); it < elem_instance->references().end(); ++i, ++it)
table_instance->elements()[i + d] = *it;
for (auto it = elem_instance->references().begin(); it < elem_instance->references().end(); ++i, ++it) {
RefPtr<ModuleInstance const> anchor;
if (auto const* func = it->ref().template get_pointer<Reference::Func>())
anchor = m_store.get_module_instance_for(func->address);
table_instance->set_element(i + d, *it, move(anchor));
}
// Drop element
*m_store.get(main_module_instance.elements()[current_index]) = ElementInstance(elem_instance->type(), {});
}
@ -611,14 +630,16 @@ Optional<InstantiationError> AbstractMachine::allocate_all_initial_phase(Module
for (auto& table : module.table_section().tables()) {
auto table_address = m_store.allocate(table.type());
if (table_address.has_value())
if (table_address.has_value()) {
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())
if (memory_address.has_value()) {
module_instance.memories().append(*memory_address);
}
}
size_t index = 0;

View file

@ -13,6 +13,7 @@
#include <AK/NonnullOwnPtr.h>
#include <AK/StackInfo.h>
#include <AK/UFixedBigInt.h>
#include <AK/Weakable.h>
#include <LibWasm/Export.h>
#include <LibWasm/Types.h>
@ -302,6 +303,8 @@ struct InstantiationError {
using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress, TagAddress>;
class Store;
class ExportInstance {
public:
explicit ExportInstance(ByteString name, ExternValue value)
@ -318,7 +321,8 @@ private:
ExternValue m_value;
};
class ModuleInstance {
class WASM_API ModuleInstance : public RefCounted<ModuleInstance>
, public Weakable<ModuleInstance> {
public:
explicit ModuleInstance(
Vector<TypeSection::Type> types, Vector<FunctionAddress> function_addresses, Vector<TableAddress> table_addresses, Vector<MemoryAddress> memory_addresses, Vector<GlobalAddress> global_addresses, Vector<DataAddress> data_addresses, Vector<TagAddress> tag_addresses, Vector<TagType> tag_types, Vector<ExportInstance> exports, size_t minimum_call_record_allocation_size)
@ -379,21 +383,23 @@ public:
explicit WasmFunction(FunctionType const& type, ModuleInstance const& instance, Module const& module, CodeSection::Code const& code)
: m_type(type)
, m_module(module.make_weak_ptr())
, m_module_instance(instance)
, m_code(code)
, m_module_instance(instance.make_weak_ptr<ModuleInstance const>())
, m_code(&code)
{
}
auto& type() const { return m_type; }
auto& module() const { return m_module_instance; }
auto& code() const { return m_code; }
// Callers must have already verified the module is alive (e.g., via Store::get() returning non-null).
ModuleInstance const& module() const { return *m_module_instance.strong_ref(); }
RefPtr<ModuleInstance const> try_module() const { return m_module_instance.strong_ref(); }
auto& code() const { return *m_code; }
RefPtr<Module const> module_ref() const { return m_module.strong_ref(); }
private:
FunctionType m_type;
WeakPtr<Module const> m_module;
ModuleInstance const& m_module_instance;
CodeSection::Code const& m_code;
WeakPtr<ModuleInstance const> m_module_instance;
CodeSection::Code const* m_code;
};
class HostFunction {
@ -423,13 +429,24 @@ public:
: m_elements(move(elements))
, m_type(type)
{
m_module_anchors.resize(m_elements.size());
}
auto& elements() const { return m_elements; }
auto& elements() { return m_elements; }
auto& type() const { return m_type; }
bool grow(u32 size_to_grow, Reference const& fill_value)
// MUST use this if a function reference can be stored in the table
void set_element(size_t index, Reference ref, RefPtr<ModuleInstance const> module_anchor = {})
{
m_elements[index] = move(ref);
m_module_anchors[index] = move(module_anchor);
}
// Strong ref pinning the element's defining ModuleInstance (null for non-Func).
RefPtr<ModuleInstance const> module_anchor_at(size_t index) const { return m_module_anchors[index]; }
bool grow(u32 size_to_grow, Reference const& fill_value, RefPtr<ModuleInstance const> fill_module_anchor = {})
{
if (size_to_grow == 0)
return true;
@ -444,8 +461,12 @@ public:
auto previous_size = m_elements.size();
if (m_elements.try_resize(new_size).is_error())
return false;
for (size_t i = previous_size; i < m_elements.size(); ++i)
if (m_module_anchors.try_resize(new_size).is_error())
return false;
for (size_t i = previous_size; i < m_elements.size(); ++i) {
m_elements[i] = fill_value;
m_module_anchors[i] = fill_module_anchor;
}
m_type = TableType { m_type.element_type(), Limits(m_type.limits().address_type(), m_type.limits().min() + size_to_grow, m_type.limits().max()) };
@ -454,6 +475,7 @@ public:
private:
Vector<Reference> m_elements;
Vector<RefPtr<ModuleInstance const>> m_module_anchors;
TableType m_type;
};
@ -637,6 +659,7 @@ public:
Optional<ExceptionAddress> allocate(TagInstance const&, Vector<Value>);
Module const* get_module_for(FunctionAddress);
RefPtr<ModuleInstance const> get_module_instance_for(FunctionAddress); // Obtains strong ref for module.
FunctionInstance* get(FunctionAddress);
TableInstance* get(TableAddress);
MemoryInstance* get(MemoryAddress);
@ -746,7 +769,7 @@ private:
bool m_owns_locals { false };
};
using InstantiationResult = AK::ErrorOr<NonnullOwnPtr<ModuleInstance>, InstantiationError>;
using InstantiationResult = AK::ErrorOr<NonnullRefPtr<ModuleInstance>, InstantiationError>;
struct HostVisitOps {
Function<void(ExternallyManagedTrap&)> visit_trap;

View file

@ -2779,8 +2779,13 @@ HANDLE_INSTRUCTION(table_init)
TRAP_IN_LOOP_IF_NOT(!checked_source_offset.has_overflow() && checked_source_offset <= (u32)element->references().size());
TRAP_IN_LOOP_IF_NOT(!checked_destination_offset.has_overflow() && checked_destination_offset <= (u32)table->elements().size());
for (u32 i = 0; i < count; ++i)
table->elements()[destination_offset + i] = element->references()[source_offset + i];
for (u32 i = 0; i < count; ++i) {
auto const& ref = element->references()[source_offset + i];
RefPtr<ModuleInstance const> anchor;
if (auto const* func = ref.ref().template get_pointer<Reference::Func>())
anchor = configuration.store().get_module_instance_for(func->address);
table->set_element(destination_offset + i, ref, move(anchor));
}
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
}
@ -2809,13 +2814,15 @@ HANDLE_INSTRUCTION(table_copy)
if (destination_offset <= source_offset) {
for (u32 i = 0; i < count; ++i) {
auto value = source_instance->elements()[source_offset + i];
destination_instance->elements()[destination_offset + i] = value;
destination_instance->set_element(destination_offset + i,
source_instance->elements()[source_offset + i],
source_instance->module_anchor_at(source_offset + i));
}
} else {
for (u32 i = count - 1; i != NumericLimits<u32>::max(); --i) {
auto value = source_instance->elements()[source_offset + i];
destination_instance->elements()[destination_offset + i] = value;
destination_instance->set_element(destination_offset + i,
source_instance->elements()[source_offset + i],
source_instance->module_anchor_at(source_offset + i));
}
}
@ -2838,8 +2845,15 @@ HANDLE_INSTRUCTION(table_fill)
checked_offset += count;
TRAP_IN_LOOP_IF_NOT(!checked_offset.has_overflow() && checked_offset <= (u32)table->elements().size());
for (u32 i = 0; i < count; ++i)
table->elements()[start + i] = value.template to<Reference>();
// Don't leak the RefPtr to the sibling call.
{
auto ref = value.template to<Reference>();
RefPtr<ModuleInstance const> anchor;
if (auto const* func = ref.ref().template get_pointer<Reference::Func>())
anchor = configuration.store().get_module_instance_for(func->address);
for (u32 i = 0; i < count; ++i)
table->set_element(start + i, ref, anchor);
}
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
}
@ -2854,7 +2868,13 @@ HANDLE_INSTRUCTION(table_set)
auto address = configuration.frame().module().tables()[table_index.value()];
auto table = configuration.store().get(address);
TRAP_IN_LOOP_IF_NOT(index < table->elements().size());
table->elements()[index] = ref.template to<Reference>();
{
auto reference = ref.template to<Reference>();
RefPtr<ModuleInstance const> anchor;
if (auto const* func = reference.ref().template get_pointer<Reference::Func>())
anchor = configuration.store().get_module_instance_for(func->address);
table->set_element(index, reference, move(anchor));
}
TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY));
}

View file

@ -408,7 +408,7 @@ i32 wasm_cl_call_indirect(void* interp_ptr, void* config_ptr, i32 table_idx, i32
auto const& module = config.frame().module();
auto table_address = module.tables()[table_idx];
auto* table_instance = config.store().get(table_address);
if (element_index < 0 || static_cast<size_t>(element_index) >= table_instance->elements().size())
if (!table_instance || element_index < 0 || static_cast<size_t>(element_index) >= table_instance->elements().size())
return interpreter.set_trap(Trap::from_string("Table index out of bounds"));
auto& element = table_instance->elements()[element_index];
@ -416,7 +416,10 @@ i32 wasm_cl_call_indirect(void* interp_ptr, void* config_ptr, i32 table_idx, i32
return interpreter.set_trap(Trap::from_string("Table element is not a function reference"));
auto address = element.ref().get<Reference::Func>().address;
auto const& type_actual = config.store().get(address)->visit([](auto& f) -> decltype(auto) { return f.type(); });
auto* function = config.store().get(address);
if (!function)
return interpreter.set_trap(Trap::from_string("Indirect call to freed function"));
auto const& type_actual = function->visit([](auto& f) -> decltype(auto) { return f.type(); });
auto const& type_expected = module.types()[type_idx].unsafe_function();
if (type_actual.parameters() != type_expected.parameters() || type_actual.results() != type_expected.results())
return interpreter.set_trap(Trap::from_string("Indirect call type mismatch"));

View file

@ -32,7 +32,7 @@ WebIDL::ExceptionOr<GC::Ref<Instance>> Instance::construct_impl(JS::Realm& realm
return realm.create<Instance>(realm, move(module_instance));
}
Instance::Instance(JS::Realm& realm, NonnullOwnPtr<Wasm::ModuleInstance> module_instance)
Instance::Instance(JS::Realm& realm, NonnullRefPtr<Wasm::ModuleInstance> module_instance)
: Bindings::PlatformObject(realm)
, m_exports(Object::create(realm, nullptr))
, m_module_instance(move(module_instance))

View file

@ -29,13 +29,13 @@ public:
Wasm::ModuleInstance const* module_instance() const { return m_module_instance.ptr(); }
private:
Instance(JS::Realm&, NonnullOwnPtr<Wasm::ModuleInstance>);
Instance(JS::Realm&, NonnullRefPtr<Wasm::ModuleInstance>);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override;
GC::Ref<Object> m_exports;
NonnullOwnPtr<Wasm::ModuleInstance> m_module_instance;
NonnullRefPtr<Wasm::ModuleInstance> m_module_instance;
HashMap<Wasm::FunctionAddress, GC::Ptr<JS::FunctionObject>> m_function_instances;
HashMap<Wasm::TableAddress, GC::Ptr<WebAssembly::Table>> m_table_instances;
};

View file

@ -256,7 +256,7 @@ Wasm::HostFunction create_host_function(JS::VM& vm, JS::FunctionObject& function
};
}
JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM& vm, Wasm::Module const& module, GC::Ptr<JS::Object> import_object)
JS::ThrowCompletionOr<NonnullRefPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM& vm, Wasm::Module const& module, GC::Ptr<JS::Object> import_object)
{
Wasm::Linker linker { module };
auto& cache = get_cache(*vm.current_realm());

View file

@ -108,7 +108,7 @@ private:
WebAssemblyCache& get_cache(JS::Realm&);
JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM&, Wasm::Module const&, GC::Ptr<JS::Object> import_object);
JS::ThrowCompletionOr<NonnullRefPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM&, Wasm::Module const&, GC::Ptr<JS::Object> import_object);
JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> compile_a_webassembly_module(JS::VM&, ByteBuffer);
JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress address, Utf16FlyString name, Instance* instance = nullptr);
JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM&, JS::Value value, Wasm::ValueType const& type);

View file

@ -151,7 +151,7 @@ private:
static HashMap<Wasm::Linker::Name, Wasm::ExternValue> s_spec_test_namespace;
static Wasm::AbstractMachine m_machine;
RefPtr<Wasm::Module> m_module;
OwnPtr<Wasm::ModuleInstance> m_module_instance;
RefPtr<Wasm::ModuleInstance> m_module_instance;
};
GC_DEFINE_ALLOCATOR(WebAssemblyModule);

View file

@ -619,7 +619,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
Core::EventLoop main_loop;
// First, resolve the linked modules
Vector<NonnullOwnPtr<Wasm::ModuleInstance>> linked_instances;
Vector<NonnullRefPtr<Wasm::ModuleInstance>> linked_instances;
Vector<NonnullRefPtr<Wasm::Module>> linked_modules;
for (auto& name : modules_to_link_in) {
auto parse_result = parse(name);