mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
LibGC: Add a ForeignCell class for ownership of non-C++ objects
This will allow us to use the GC to manage the lifetime of objects that are not C++ objects, such as Swift objects. In the future we could expand this cursed FFI to other languages as well.
This commit is contained in:
parent
726f2cfb11
commit
829391e714
Notes:
github-actions[bot]
2024-11-19 21:54:22 +00:00
Author: https://github.com/ADKaster Commit: https://github.com/LadybirdBrowser/ladybird/commit/829391e7144 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2386 Reviewed-by: https://github.com/alimpfard
11 changed files with 281 additions and 0 deletions
|
@ -3,6 +3,7 @@ set(SOURCES
|
|||
Cell.cpp
|
||||
CellAllocator.cpp
|
||||
ConservativeVector.cpp
|
||||
ForeignCell.cpp
|
||||
Root.cpp
|
||||
Heap.cpp
|
||||
HeapBlock.cpp
|
||||
|
|
61
Libraries/LibGC/ForeignCell.cpp
Normal file
61
Libraries/LibGC/ForeignCell.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/DeferGC.h>
|
||||
#include <LibGC/ForeignCell.h>
|
||||
#include <LibGC/Heap.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
void* ForeignCell::foreign_data()
|
||||
{
|
||||
// !!!
|
||||
auto offset = round_up_to_power_of_two(sizeof(ForeignCell), m_vtable.alignment);
|
||||
return static_cast<void*>(reinterpret_cast<u8*>(this) + offset);
|
||||
}
|
||||
|
||||
ForeignCell::ForeignCell(ForeignCell::Vtable vtable)
|
||||
: m_vtable(move(vtable))
|
||||
{
|
||||
if (m_vtable.initialize)
|
||||
m_vtable.initialize(foreign_data(), m_vtable.class_metadata_pointer, *this);
|
||||
}
|
||||
|
||||
ForeignCell::~ForeignCell()
|
||||
{
|
||||
if (m_vtable.destroy)
|
||||
m_vtable.destroy(foreign_data(), m_vtable.class_metadata_pointer);
|
||||
}
|
||||
|
||||
Ref<ForeignCell> ForeignCell::create(Heap& heap, size_t size, ForeignCell::Vtable vtable)
|
||||
{
|
||||
// NOTE: GC must be deferred so that a collection during allocation doesn't get tripped
|
||||
// up looking for the Cell pointer on the stack or in a register when it might only exist in the heap.
|
||||
// We can't guarantee that the ForeignCell will be stashed in a proper ForeignRef/ForeignPtr or similar
|
||||
// foreign type until after all the dust has settled on both sides of the FFI boundary.
|
||||
VERIFY(heap.is_gc_deferred());
|
||||
VERIFY(is_power_of_two(vtable.alignment));
|
||||
auto& allocator = heap.allocator_for_size(sizeof(ForeignCell) + round_up_to_power_of_two(size, vtable.alignment));
|
||||
auto* memory = allocator.allocate_cell(heap);
|
||||
auto* foreign_cell = new (memory) ForeignCell(move(vtable));
|
||||
return *foreign_cell;
|
||||
}
|
||||
|
||||
void ForeignCell::finalize()
|
||||
{
|
||||
Base::finalize();
|
||||
if (m_vtable.finalize)
|
||||
m_vtable.finalize(foreign_data(), m_vtable.class_metadata_pointer);
|
||||
}
|
||||
|
||||
void ForeignCell::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
if (m_vtable.visit_edges)
|
||||
m_vtable.visit_edges(foreign_data(), m_vtable.class_metadata_pointer, visitor);
|
||||
}
|
||||
|
||||
}
|
180
Libraries/LibGC/ForeignCell.h
Normal file
180
Libraries/LibGC/ForeignCell.h
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Swift.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibGC/Cell.h>
|
||||
#include <LibGC/DeferGC.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
template<typename T>
|
||||
struct ForeignRef;
|
||||
|
||||
template<typename T>
|
||||
struct ForeignPtr;
|
||||
|
||||
#define FOREIGN_CELL(class_, base_class) \
|
||||
using Base = base_class; \
|
||||
friend class GC::Heap;
|
||||
|
||||
class ForeignCell : public Cell {
|
||||
FOREIGN_CELL(ForeignCell, Cell);
|
||||
|
||||
public:
|
||||
struct Vtable {
|
||||
// Holds a pointer to the foreign vtable information such as
|
||||
// a jclass in Java, or a Swift type metadata pointer
|
||||
void* class_metadata_pointer = nullptr;
|
||||
|
||||
// FIXME: FlyString? The class name must be owned by the ForeignCell so it can vend StringViews
|
||||
// We should properly cache the name and class info pointer to avoid string churn
|
||||
String class_name;
|
||||
|
||||
size_t alignment { 1 };
|
||||
|
||||
void (*initialize)(void* thiz, void* clazz, Ref<Cell>);
|
||||
void (*destroy)(void* thiz, void* clazz);
|
||||
void (*finalize)(void* thiz, void* clazz);
|
||||
void (*visit_edges)(void* thiz, void* clazz, Cell::Visitor&);
|
||||
};
|
||||
static Ref<ForeignCell> create(Heap&, size_t size, Vtable);
|
||||
|
||||
void* foreign_data() SWIFT_RETURNS_INDEPENDENT_VALUE; // technically lying to swift, but it's fiiiiine
|
||||
|
||||
// ^Cell
|
||||
virtual void finalize() override;
|
||||
virtual void visit_edges(Cell::Visitor& visitor) override;
|
||||
virtual StringView class_name() const override { return m_vtable.class_name; }
|
||||
|
||||
~ForeignCell();
|
||||
|
||||
private:
|
||||
ForeignCell(Vtable vtable);
|
||||
|
||||
Vtable m_vtable;
|
||||
} SWIFT_IMMORTAL_REFERENCE;
|
||||
|
||||
template<typename T>
|
||||
struct ForeignRef {
|
||||
friend struct ForeignPtr<T>;
|
||||
|
||||
template<typename... Args>
|
||||
static ForeignRef allocate(Heap& heap, Args... args)
|
||||
{
|
||||
DeferGC const defer_gc(heap);
|
||||
auto* cell = T::create(&heap, forward<Args>(args)...);
|
||||
if constexpr (IsSame<decltype(cell), Cell*>) {
|
||||
return ForeignRef(*verify_cast<ForeignCell>(cell));
|
||||
} else {
|
||||
static_assert(IsSame<decltype(cell), void*>);
|
||||
auto* cast_cell = static_cast<Cell*>(cell);
|
||||
return ForeignRef(*verify_cast<ForeignCell>(cast_cell));
|
||||
}
|
||||
}
|
||||
|
||||
ForeignRef() = delete;
|
||||
|
||||
// This constructor should only be called directly after allocating a foreign cell by calling an FFI create method
|
||||
ForeignRef(ForeignCell& cell)
|
||||
: m_cell(cell)
|
||||
{
|
||||
// FIXME: This is super dangerous. How can we assert that the cell is actually a T?
|
||||
m_data = static_cast<T*>(m_cell->foreign_data());
|
||||
}
|
||||
|
||||
~ForeignRef() = default;
|
||||
ForeignRef(ForeignRef const& other) = default;
|
||||
ForeignRef& operator=(ForeignRef const& other) = default;
|
||||
|
||||
RETURNS_NONNULL T* operator->() const { return m_data; }
|
||||
[[nodiscard]] T& operator*() const { return *m_data; }
|
||||
|
||||
RETURNS_NONNULL T* ptr() const { return m_data; }
|
||||
RETURNS_NONNULL operator T*() const { return m_data; }
|
||||
|
||||
operator T&() const { return *m_data; }
|
||||
|
||||
Ref<ForeignCell> cell() const { return m_cell; }
|
||||
|
||||
void visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
visitor.visit(m_cell);
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<ForeignCell> m_cell;
|
||||
T* m_data { nullptr };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ForeignPtr {
|
||||
constexpr ForeignPtr() = default;
|
||||
|
||||
// This constructor should only be called directly after allocating a foreign cell by calling an FFI create method
|
||||
ForeignPtr(ForeignCell& cell)
|
||||
: m_cell(&cell)
|
||||
{
|
||||
// FIXME: This is super dangerous. How can we assert that the cell is actually a T?
|
||||
m_data = static_cast<T*>(m_cell->foreign_data());
|
||||
}
|
||||
|
||||
// This constructor should only be called directly after allocating a foreign cell by calling an FFI create method
|
||||
ForeignPtr(ForeignCell* cell)
|
||||
: m_cell(cell)
|
||||
{
|
||||
// FIXME: This is super dangerous. How can we assert that the cell is actually a T?
|
||||
m_data = m_cell ? static_cast<T*>(m_cell->foreign_data()) : nullptr;
|
||||
}
|
||||
|
||||
ForeignPtr(ForeignRef<T> const& other)
|
||||
: m_cell(other.m_cell)
|
||||
, m_data(other.m_data)
|
||||
{
|
||||
}
|
||||
|
||||
ForeignPtr(nullptr_t)
|
||||
: m_cell(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ForeignPtr(ForeignPtr const& other) = default;
|
||||
ForeignPtr& operator=(ForeignPtr const& other) = default;
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
ASSERT(m_cell && m_data);
|
||||
return m_data;
|
||||
}
|
||||
|
||||
[[nodiscard]] T& operator*() const
|
||||
{
|
||||
ASSERT(m_cell && m_data);
|
||||
return *m_data;
|
||||
}
|
||||
|
||||
operator T*() const { return m_data; }
|
||||
T* ptr() const { return m_data; }
|
||||
|
||||
explicit operator bool() const { return !!m_cell; }
|
||||
bool operator!() const { return !m_cell; }
|
||||
|
||||
Ptr<ForeignCell> cell() const { return m_cell; }
|
||||
|
||||
void visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
visitor.visit(m_cell);
|
||||
}
|
||||
|
||||
private:
|
||||
Ptr<ForeignCell> m_cell;
|
||||
T* m_data { nullptr };
|
||||
};
|
||||
|
||||
}
|
|
@ -13,6 +13,7 @@ namespace GC {
|
|||
class Cell;
|
||||
class CellAllocator;
|
||||
class DeferGC;
|
||||
class ForeignCell;
|
||||
class RootImpl;
|
||||
class Heap;
|
||||
class HeapBlock;
|
||||
|
|
|
@ -79,6 +79,7 @@ private:
|
|||
friend class MarkingVisitor;
|
||||
friend class GraphConstructorVisitor;
|
||||
friend class DeferGC;
|
||||
friend class ForeignCell;
|
||||
|
||||
void defer_gc();
|
||||
void undefer_gc();
|
||||
|
|
|
@ -314,6 +314,9 @@ static std::optional<CellTypeWithOrigin> find_cell_type_with_origin(clang::CXXRe
|
|||
if (base_name == "GC::Cell")
|
||||
return CellTypeWithOrigin { *base_record, LibJSCellMacro::Type::GCCell };
|
||||
|
||||
if (base_name == "GC::ForeignCell")
|
||||
return CellTypeWithOrigin { *base_record, LibJSCellMacro::Type::ForeignCell };
|
||||
|
||||
if (base_name == "JS::Object")
|
||||
return CellTypeWithOrigin { *base_record, LibJSCellMacro::Type::JSObject };
|
||||
|
||||
|
@ -336,6 +339,9 @@ static std::optional<CellTypeWithOrigin> find_cell_type_with_origin(clang::CXXRe
|
|||
|
||||
LibJSGCVisitor::CellMacroExpectation LibJSGCVisitor::get_record_cell_macro_expectation(clang::CXXRecordDecl const& record)
|
||||
{
|
||||
if (record.getQualifiedNameAsString() == "GC::ForeignCell")
|
||||
return { LibJSCellMacro::Type::ForeignCell, "Cell" };
|
||||
|
||||
auto origin = find_cell_type_with_origin(record);
|
||||
assert(origin.has_value());
|
||||
|
||||
|
@ -471,6 +477,8 @@ char const* LibJSCellMacro::type_name(Type type)
|
|||
switch (type) {
|
||||
case Type::GCCell:
|
||||
return "GC_CELL";
|
||||
case Type::ForeignCell:
|
||||
return "FOREIGN_CELL";
|
||||
case Type::JSObject:
|
||||
return "JS_OBJECT";
|
||||
case Type::JSEnvironment:
|
||||
|
@ -499,6 +507,7 @@ void LibJSPPCallbacks::MacroExpands(clang::Token const& name_token, clang::Macro
|
|||
if (auto* ident_info = name_token.getIdentifierInfo()) {
|
||||
static llvm::StringMap<LibJSCellMacro::Type> libjs_macro_types {
|
||||
{ "GC_CELL", LibJSCellMacro::Type::GCCell },
|
||||
{ "FOREIGN_CELL", LibJSCellMacro::Type::ForeignCell },
|
||||
{ "JS_OBJECT", LibJSCellMacro::Type::JSObject },
|
||||
{ "JS_ENVIRONMENT", LibJSCellMacro::Type::JSEnvironment },
|
||||
{ "JS_PROTOTYPE_OBJECT", LibJSCellMacro::Type::JSPrototypeObject },
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
struct LibJSCellMacro {
|
||||
enum class Type {
|
||||
GCCell,
|
||||
ForeignCell,
|
||||
JSObject,
|
||||
JSEnvironment,
|
||||
JSPrototypeObject,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
|
||||
|
||||
#include <LibGC/ForeignCell.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
|
||||
|
@ -13,6 +14,10 @@
|
|||
class TestCellClass : JS::Cell {
|
||||
};
|
||||
|
||||
// expected-error@+1 {{Expected record to have a FOREIGN_CELL macro invocation}}
|
||||
class TestForeignCellClass : GC::ForeignCell {
|
||||
};
|
||||
|
||||
// expected-error@+1 {{Expected record to have a JS_OBJECT macro invocation}}
|
||||
class TestObjectClass : JS::Object {
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
|
||||
// expected-no-diagnostics
|
||||
|
||||
#include <LibGC/ForeignCell.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
|
||||
|
@ -14,6 +15,10 @@ class TestCellClass : JS::Cell {
|
|||
GC_CELL(TestCellClass, JS::Cell);
|
||||
};
|
||||
|
||||
class TestForeignCellClass : GC::ForeignCell {
|
||||
FOREIGN_CELL(TestForeignCellClass, GC::ForeignCell);
|
||||
};
|
||||
|
||||
class TestObjectClass : JS::Object {
|
||||
JS_OBJECT(TestObjectClass, JS::Object);
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
|
||||
|
||||
#include <LibGC/ForeignCell.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
|
||||
|
@ -34,6 +35,16 @@ class ObjectWithEnvironmentMacro : JS::Object {
|
|||
JS_ENVIRONMENT(ObjectWithEnvironmentMacro, JS::Object);
|
||||
};
|
||||
|
||||
class CellWithForeignCellMacro : GC::Cell {
|
||||
// expected-error@+1 {{Invalid GC-CELL-like macro invocation; expected GC_CELL}}
|
||||
FOREIGN_CELL(CellWithForeignCellMacro, GC::Cell);
|
||||
};
|
||||
|
||||
class ObjectWithForeignCellMacro : JS::Object {
|
||||
// expected-error@+1 {{Invalid GC-CELL-like macro invocation; expected JS_OBJECT}}
|
||||
FOREIGN_CELL(ObjectWithForeignCellMacro, JS::Object);
|
||||
};
|
||||
|
||||
// JS_PROTOTYPE_OBJECT can only be used in the JS namespace
|
||||
namespace JS {
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
|
||||
|
||||
#include <LibGC/ForeignCell.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
|
||||
|
@ -16,6 +17,11 @@ class TestCellClass : JS::Cell {
|
|||
GC_CELL(bad, JS::Cell);
|
||||
};
|
||||
|
||||
class TestForeignCellClass : GC::ForeignCell {
|
||||
// expected-error@+1 {{Expected first argument of FOREIGN_CELL macro invocation to be TestForeignCellClass}}
|
||||
FOREIGN_CELL(bad, GC::ForeignCell);
|
||||
};
|
||||
|
||||
class TestObjectClass : JS::Object {
|
||||
// expected-error@+1 {{Expected first argument of JS_OBJECT macro invocation to be TestObjectClass}}
|
||||
JS_OBJECT(bad, JS::Object);
|
||||
|
|
Loading…
Reference in a new issue