LibWasm: Back wasm-gc aggregate instances with the LibGC heap
This commit is contained in:
parent
76f17f7703
commit
87961e3c92
6 changed files with 239 additions and 10 deletions
|
|
@ -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()) };
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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()); }));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue