LibWasm: Back wasm-gc aggregate instances with the LibGC heap

This commit is contained in:
Ali Mohammad Pur 2026-06-13 13:21:06 +02:00 committed by Ali Mohammad Pur
parent 76f17f7703
commit 87961e3c92
6 changed files with 239 additions and 10 deletions

View file

@ -8,6 +8,7 @@
#include <AK/NeverDestroyed.h>
#include <AK/SaturatingMath.h>
#include <LibCore/System.h>
#include <LibGC/Heap.h>
#include <LibSync/MutexProtected.h>
#include <LibWasm/AbstractMachine/AbstractMachine.h>
#include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
@ -92,6 +93,76 @@ void dump_module_stats()
});
}
GC_DEFINE_ALLOCATOR(StructInstance);
GC_DEFINE_ALLOCATOR(ArrayInstance);
void StructInstance::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto& field : m_fields) {
if (auto* cell = field.gc_cell())
visitor.visit(cell);
}
}
void ArrayInstance::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto& element : m_elements) {
if (auto* cell = element.gc_cell())
visitor.visit(cell);
}
}
void AbstractMachine::adopt_heap(GC::Heap& heap)
{
VERIFY(!m_heap);
m_heap = &heap;
m_store.set_heap(heap);
m_roots_provider = make<RootsProvider>(heap, m_store);
}
void AbstractMachine::create_own_heap()
{
m_owned_heap = make<GC::Heap>([](auto&) { }, GC::Heap::BecomeProcessDefault::No);
adopt_heap(*m_owned_heap);
}
void AbstractMachine::RootsProvider::for_each_conservative_range(AK::Function<void(ReadonlySpan<FlatPtr>)> const& callback) const
{
static_assert(sizeof(Value) % sizeof(FlatPtr) == 0);
auto report_values = [&](Value const* data, size_t count) {
if (count == 0)
return;
callback({ reinterpret_cast<FlatPtr const*>(data), count * (sizeof(Value) / sizeof(FlatPtr)) });
};
auto report_references = [&](ReadonlySpan<Reference> references) {
if (references.is_empty())
return;
callback({ reinterpret_cast<FlatPtr const*>(references.data()), references.size() * (sizeof(Reference) / sizeof(FlatPtr)) });
};
for (auto* configuration : m_store.active_configurations()) {
// Scan up to capacity, not just size.
report_values(configuration->value_stack().data(), configuration->value_stack().capacity());
report_values(configuration->regs.data(), configuration->regs.size());
report_values(configuration->m_current_call_record.data(), configuration->m_current_call_record.size());
for (auto& arguments : configuration->m_call_argument_freelist)
report_values(arguments.data(), arguments.capacity());
for (auto& frame : configuration->m_frame_stack) {
if (frame.owns_locals())
report_values(frame.locals_data(), frame.owned_locals().size());
}
}
for (auto& table : m_store.tables())
report_references(table.elements().span());
for (auto& global : m_store.globals())
report_values(&global.value(), 1);
for (auto& exception : m_store.exceptions())
report_values(exception.params().data(), exception.params().size());
}
MemoryBuffer::~MemoryBuffer()
{
clear();
@ -475,6 +546,9 @@ ErrorOr<void, ValidationError> AbstractMachine::validate(Module& module, Optiona
}
InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<ExternValue> externs)
{
// FIXME: Only create a heap if we actually have GC used in the module.
heap();
if (auto result = validate(const_cast<Module&>(module)); result.is_error())
return InstantiationError { ByteString::formatted("Validation failed: {}", result.error()) };

View file

@ -14,6 +14,10 @@
#include <AK/StackInfo.h>
#include <AK/UFixedBigInt.h>
#include <AK/Weakable.h>
#include <LibGC/Cell.h>
#include <LibGC/CellAllocator.h>
#include <LibGC/ConservativeRangeProvider.h>
#include <LibGC/Heap.h>
#include <LibWasm/Export.h>
#include <LibWasm/TypeSystem.h>
#include <LibWasm/Types.h>
@ -62,8 +66,17 @@ public:
struct Exception {
ExceptionAddress address;
};
// https://webassembly.github.io/spec/core/exec/runtime.html#values
// ref.i31 i31: an unboxed 31-bit scalar reference.
struct I31 {
u32 value; // using only low 31 bits
};
// A reference to a structure or array instance (a GC::Cell, see StructInstance and ArrayInstance below).
struct GcObject {
GC::Ptr<GC::Cell> ptr;
};
using RefType = Variant<Null, Func, Extern, Exception>;
using RefType = Variant<Null, Func, Extern, Exception, I31, GcObject>;
explicit Reference(RefType ref)
: m_ref(move(ref))
{
@ -117,7 +130,7 @@ public:
case ValueType::ArrayReference:
case ValueType::NoneReference:
case ValueType::TypeUseReference:
m_value = u128(0, 5);
m_value = u128(0, 8);
break;
}
}
@ -158,21 +171,51 @@ public:
{
}
explicit Value(Reference ref)
explicit Value(Reference const& ref)
{
// Reference variant is encoded in the high storage of the u128:
// 0: funcref
// 1: externref
// 2: null funcref
// 3: null externref
// 4: null exnref
// 5: exnref
// 6: a gc object
// 7: an i31 reference
// 8: a null reference in the any hierarchy
// anything else: funcref, where high is the defining Module* (null for host functions)
ref.ref().visit(
[&](Reference::Func const& func) { m_value = u128(bit_cast<u64>(func.address), bit_cast<u64>(func.source_module.ptr())); },
[&](Reference::Extern const& func) { m_value = u128(bit_cast<u64>(func.address), 1); },
[&](Reference::Null const& null) { m_value = u128(0, null.type.kind() == ValueType::Kind::FunctionReference ? 2 : null.type.kind() == ValueType::Kind::ExceptionReference ? 4
: 3); },
[&](Reference::Exception const& exn) { m_value = u128(bit_cast<u64>(exn.address), 5); });
[&](Reference::Null const& null) {
switch (null.type.kind()) {
case ValueType::Kind::FunctionReference:
case ValueType::Kind::NoFunctionReference:
m_value = u128(0, 2);
break;
case ValueType::Kind::ExternReference:
case ValueType::Kind::NoExternReference:
m_value = u128(0, 3);
break;
case ValueType::Kind::ExceptionReference:
case ValueType::Kind::NoExceptionReference:
m_value = u128(0, 4);
break;
default:
m_value = u128(0, 8);
break;
}
},
[&](Reference::Exception const& exn) { m_value = u128(bit_cast<u64>(exn.address), 5); },
[&](Reference::I31 const& i31) { m_value = u128(static_cast<u64>(i31.value & 0x7fffffff), 7); },
[&](Reference::GcObject const& object) { m_value = u128(bit_cast<u64>(object.ptr.ptr()), 6); });
}
// The gc cell behind this value if it holds a gc object reference, otherwise null.
GC::Cell* gc_cell() const
{
if (m_value.high() == 6)
return bit_cast<GC::Cell*>(m_value.low());
return nullptr;
}
template<SameAs<u128> T>
@ -219,6 +262,12 @@ public:
return Reference { Reference::Null { ValueType(ValueType::Kind::ExceptionReference) } };
case 5:
return Reference { Reference::Exception { bit_cast<ExceptionAddress>(m_value.low()) } };
case 6:
return Reference { Reference::GcObject { bit_cast<GC::Cell*>(m_value.low()) } };
case 7:
return Reference { Reference::I31 { static_cast<u32>(m_value.low()) } };
case 8:
return Reference { Reference::Null { ValueType(ValueType::Kind::AnyReference) } };
default:
return Reference { Reference::Func { bit_cast<FunctionAddress>(m_value.low()), bit_cast<Wasm::Module*>(m_value.high()) } };
}
@ -687,6 +736,51 @@ private:
Vector<Value> m_params;
};
// https://webassembly.github.io/spec/core/exec/runtime.html#aggregate-instances
class WASM_API StructInstance final : public GC::Cell {
GC_CELL(StructInstance, GC::Cell);
GC_DECLARE_ALLOCATOR(StructInstance);
public:
DefinedType const& type() const { return *m_type; }
ReadonlySpan<Value> fields() const { return m_fields; }
Span<Value> fields() { return m_fields; }
private:
StructInstance(DefinedType const& type, Vector<Value> fields)
: m_type(&type)
, m_fields(move(fields))
{
}
virtual void visit_edges(Visitor&) override;
DefinedType const* m_type { nullptr };
Vector<Value> m_fields;
};
class WASM_API ArrayInstance final : public GC::Cell {
GC_CELL(ArrayInstance, GC::Cell);
GC_DECLARE_ALLOCATOR(ArrayInstance);
public:
DefinedType const& type() const { return *m_type; }
ReadonlySpan<Value> elements() const { return m_elements; }
Span<Value> elements() { return m_elements; }
private:
ArrayInstance(DefinedType const& type, Vector<Value> elements)
: m_type(&type)
, m_elements(move(elements))
{
}
virtual void visit_edges(Visitor&) override;
DefinedType const* m_type { nullptr };
Vector<Value> m_elements;
};
class WASM_API Store {
public:
Store() = default;
@ -715,6 +809,17 @@ public:
ALWAYS_INLINE FunctionInstance* unsafe_get(FunctionAddress address) { return &m_functions.data()[address.value()]; }
ALWAYS_INLINE MemoryInstance* unsafe_get(MemoryAddress address) { return m_memories.data()[address.value()].ptr(); }
GC::Heap& heap() { return *m_heap; }
void set_heap(GC::Heap& heap) { m_heap = &heap; }
void register_configuration(Badge<Configuration>, Configuration& configuration) { m_active_configurations.set(&configuration); }
void unregister_configuration(Badge<Configuration>, Configuration& configuration) { m_active_configurations.remove(&configuration); }
auto& active_configurations() const { return m_active_configurations; }
auto& tables() const { return m_tables; }
auto& globals() const { return m_globals; }
auto& exceptions() const { return m_exceptions; }
private:
Vector<FunctionInstance> m_functions;
Vector<TableInstance> m_tables;
@ -724,6 +829,9 @@ private:
Vector<DataInstance> m_datas;
Vector<TagInstance> m_tags;
Vector<ExceptionInstance> m_exceptions;
GC::Heap* m_heap { nullptr };
HashTable<Configuration*> m_active_configurations;
};
class Label {
@ -815,7 +923,18 @@ struct HostVisitOps {
class WASM_API AbstractMachine {
public:
explicit AbstractMachine() = default;
explicit AbstractMachine(GC::Heap* heap = nullptr)
{
if (heap)
adopt_heap(*heap);
}
GC::Heap& heap()
{
if (!m_heap) [[unlikely]]
create_own_heap();
return *m_heap;
}
// Validate a module; permanently sets the module's validity status.
ErrorOr<void, ValidationError> validate(Module&, Optional<CompileCacheConfig> cache_config = {}, CompileToNative = CompileToNative::Yes);
@ -858,7 +977,28 @@ private:
Optional<InstantiationError> allocate_all_initial_phase(Module const&, ModuleInstance&, Vector<ExternValue>&, Vector<Value>& global_values, Vector<Value>& table_initial_values, Vector<FunctionAddress>& own_functions);
Optional<InstantiationError> allocate_all_final_phase(Module const&, ModuleInstance&, Vector<Vector<Reference>>& elements);
void adopt_heap(GC::Heap&);
void create_own_heap();
class RootsProvider final : public GC::ConservativeRangeProvider {
public:
RootsProvider(GC::Heap& heap, Store& store)
: GC::ConservativeRangeProvider(heap)
, m_store(store)
{
}
private:
virtual void for_each_conservative_range(AK::Function<void(ReadonlySpan<FlatPtr>)> const&) const override;
Store& m_store;
};
Store m_store;
OwnPtr<GC::Heap> m_owned_heap;
GC::Heap* m_heap { nullptr };
OwnPtr<RootsProvider> m_roots_provider;
StackInfo m_stack_info;
HashTable<Interpreter*> m_active_interpreters;
bool m_should_limit_instruction_count { false };

View file

@ -25,10 +25,19 @@ enum class IsTailcall {
};
class Configuration {
AK_MAKE_NONCOPYABLE(Configuration);
AK_MAKE_NONMOVABLE(Configuration);
public:
explicit Configuration(Store& store)
: m_store(store)
{
m_store.register_configuration({}, *this);
}
~Configuration()
{
m_store.unregister_configuration({}, *this);
}
template<typename... Args>

View file

@ -39,7 +39,7 @@ else()
endif()
ladybird_lib(LibWasm wasm EXPLICIT_SYMBOL_EXPORT)
target_link_libraries(LibWasm PRIVATE LibCore PUBLIC LibSync)
target_link_libraries(LibWasm PRIVATE LibCore PUBLIC LibSync LibGC)
target_compile_definitions(LibWasm PRIVATE
WASM_COMPILED_FAULT_RECOVERY_SUPPORTED=${WASM_COMPILED_FAULT_RECOVERY_SUPPORTED}

View file

@ -839,6 +839,8 @@ void Printer::print(Wasm::Value const& value, Wasm::ValueType const& type)
value.to<Reference>().ref().visit(
[](Wasm::Reference::Null const&) { return ByteString("null"); },
[](Wasm::Reference::Exception const&) { return ByteString("exception"); },
[](Wasm::Reference::I31 const& ref) { return ByteString::formatted("i31({})", ref.value); },
[](Wasm::Reference::GcObject const& ref) { return ByteString::formatted("gc-object({:p})", ref.ptr); },
[](auto const& ref) { return ByteString::number(ref.address.value()); }));
break;
case ValueType::TypeUseReference:
@ -863,6 +865,8 @@ void Printer::print(Wasm::Reference const& value)
value.ref().visit(
[](Wasm::Reference::Null const&) { return ByteString("null"); },
[](Wasm::Reference::Exception const&) { return ByteString("exception"); },
[](Wasm::Reference::I31 const& ref) { return ByteString::formatted("i31({})", ref.value); },
[](Wasm::Reference::GcObject const& ref) { return ByteString::formatted("gc-object({:p})", ref.ptr); },
[](auto const& ref) { return ByteString::number(ref.address.value()); }));
}

View file

@ -361,6 +361,8 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export)
return ref.ref().visit(
[&](Wasm::Reference::Null const&) -> JS::Value { return JS::js_null(); },
[](Wasm::Reference::Exception const&) -> JS::Value { return JS::js_undefined(); },
[](Wasm::Reference::I31 const& ref) -> JS::Value { return JS::Value(static_cast<double>(ref.value)); },
[](Wasm::Reference::GcObject const&) -> JS::Value { return JS::js_undefined(); },
[&](auto const& ref) -> JS::Value { return JS::Value(static_cast<double>(ref.address.value())); });
}
case Wasm::ValueType::I8:
@ -517,7 +519,7 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
case Wasm::ValueType::StructReference:
case Wasm::ValueType::ArrayReference:
case Wasm::ValueType::NoneReference:
return (value.to<Wasm::Reference>()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast<double>(ref.address.value())); });
return (value.to<Wasm::Reference>()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::Value(); }, [&](Wasm::Reference::I31 const& ref) { return JS::Value(static_cast<double>(ref.value)); }, [&](Wasm::Reference::GcObject const&) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast<double>(ref.address.value())); });
case Wasm::ValueType::ExceptionReference:
case Wasm::ValueType::NoExceptionReference:
return JS::js_null();