diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 4358156c17..6299601022 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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(heap, m_store); +} + +void AbstractMachine::create_own_heap() +{ + m_owned_heap = make([](auto&) { }, GC::Heap::BecomeProcessDefault::No); + adopt_heap(*m_owned_heap); +} + +void AbstractMachine::RootsProvider::for_each_conservative_range(AK::Function)> 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(data), count * (sizeof(Value) / sizeof(FlatPtr)) }); + }; + auto report_references = [&](ReadonlySpan references) { + if (references.is_empty()) + return; + callback({ reinterpret_cast(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 AbstractMachine::validate(Module& module, Optiona } InstantiationResult AbstractMachine::instantiate(Module const& module, Vector externs) { + // FIXME: Only create a heap if we actually have GC used in the module. + heap(); + if (auto result = validate(const_cast(module)); result.is_error()) return InstantiationError { ByteString::formatted("Validation failed: {}", result.error()) }; diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 22b2d2a3fe..32449bf50a 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -14,6 +14,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -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 ptr; + }; - using RefType = Variant; + using RefType = Variant; 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(func.address), bit_cast(func.source_module.ptr())); }, [&](Reference::Extern const& func) { m_value = u128(bit_cast(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(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(exn.address), 5); }, + [&](Reference::I31 const& i31) { m_value = u128(static_cast(i31.value & 0x7fffffff), 7); }, + [&](Reference::GcObject const& object) { m_value = u128(bit_cast(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(m_value.low()); + return nullptr; } template T> @@ -219,6 +262,12 @@ public: return Reference { Reference::Null { ValueType(ValueType::Kind::ExceptionReference) } }; case 5: return Reference { Reference::Exception { bit_cast(m_value.low()) } }; + case 6: + return Reference { Reference::GcObject { bit_cast(m_value.low()) } }; + case 7: + return Reference { Reference::I31 { static_cast(m_value.low()) } }; + case 8: + return Reference { Reference::Null { ValueType(ValueType::Kind::AnyReference) } }; default: return Reference { Reference::Func { bit_cast(m_value.low()), bit_cast(m_value.high()) } }; } @@ -687,6 +736,51 @@ private: Vector 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 fields() const { return m_fields; } + Span fields() { return m_fields; } + +private: + StructInstance(DefinedType const& type, Vector fields) + : m_type(&type) + , m_fields(move(fields)) + { + } + + virtual void visit_edges(Visitor&) override; + + DefinedType const* m_type { nullptr }; + Vector 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 elements() const { return m_elements; } + Span elements() { return m_elements; } + +private: + ArrayInstance(DefinedType const& type, Vector elements) + : m_type(&type) + , m_elements(move(elements)) + { + } + + virtual void visit_edges(Visitor&) override; + + DefinedType const* m_type { nullptr }; + Vector 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) { m_active_configurations.set(&configuration); } + void unregister_configuration(Badge, 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 m_functions; Vector m_tables; @@ -724,6 +829,9 @@ private: Vector m_datas; Vector m_tags; Vector m_exceptions; + + GC::Heap* m_heap { nullptr }; + HashTable 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 validate(Module&, Optional cache_config = {}, CompileToNative = CompileToNative::Yes); @@ -858,7 +977,28 @@ private: Optional allocate_all_initial_phase(Module const&, ModuleInstance&, Vector&, Vector& global_values, Vector& table_initial_values, Vector& own_functions); Optional allocate_all_final_phase(Module const&, ModuleInstance&, Vector>& 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)> const&) const override; + + Store& m_store; + }; + Store m_store; + OwnPtr m_owned_heap; + GC::Heap* m_heap { nullptr }; + OwnPtr m_roots_provider; StackInfo m_stack_info; HashTable m_active_interpreters; bool m_should_limit_instruction_count { false }; diff --git a/Libraries/LibWasm/AbstractMachine/Configuration.h b/Libraries/LibWasm/AbstractMachine/Configuration.h index af91e80ea8..55052aa8b6 100644 --- a/Libraries/LibWasm/AbstractMachine/Configuration.h +++ b/Libraries/LibWasm/AbstractMachine/Configuration.h @@ -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 diff --git a/Libraries/LibWasm/CMakeLists.txt b/Libraries/LibWasm/CMakeLists.txt index de60ed5223..f679c7151f 100644 --- a/Libraries/LibWasm/CMakeLists.txt +++ b/Libraries/LibWasm/CMakeLists.txt @@ -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} diff --git a/Libraries/LibWasm/Printer/Printer.cpp b/Libraries/LibWasm/Printer/Printer.cpp index a6c9957cdc..465da43bc2 100644 --- a/Libraries/LibWasm/Printer/Printer.cpp +++ b/Libraries/LibWasm/Printer/Printer.cpp @@ -839,6 +839,8 @@ void Printer::print(Wasm::Value const& value, Wasm::ValueType const& type) value.to().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()); })); } diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp index bdac1a32e4..ad629a6a5b 100644 --- a/Tests/LibWasm/test-wasm.cpp +++ b/Tests/LibWasm/test-wasm.cpp @@ -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(ref.value)); }, + [](Wasm::Reference::GcObject const&) -> JS::Value { return JS::js_undefined(); }, [&](auto const& ref) -> JS::Value { return JS::Value(static_cast(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()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast(ref.address.value())); }); + return (value.to()).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(ref.value)); }, [&](Wasm::Reference::GcObject const&) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast(ref.address.value())); }); case Wasm::ValueType::ExceptionReference: case Wasm::ValueType::NoExceptionReference: return JS::js_null();