mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
LibGC: Add Swift bindings to the GC heap
This includes a protocol for creating LibGC Heap allocated Swift objects. Pay no attention to the Unmanaged shenanigans, they are all behind the curtain.
This commit is contained in:
parent
829391e714
commit
fca6fd0b85
Notes:
github-actions[bot]
2024-11-19 21:54:12 +00:00
Author: https://github.com/ADKaster Commit: https://github.com/LadybirdBrowser/ladybird/commit/fca6fd0b859 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2386 Reviewed-by: https://github.com/alimpfard
11 changed files with 258 additions and 1 deletions
|
@ -13,3 +13,12 @@ set(SOURCES
|
||||||
|
|
||||||
serenity_lib(LibGC gc)
|
serenity_lib(LibGC gc)
|
||||||
target_link_libraries(LibGC PRIVATE LibCore)
|
target_link_libraries(LibGC PRIVATE LibCore)
|
||||||
|
|
||||||
|
if (ENABLE_SWIFT)
|
||||||
|
generate_clang_module_map(LibGC)
|
||||||
|
target_sources(LibGC PRIVATE
|
||||||
|
Heap+Swift.swift
|
||||||
|
)
|
||||||
|
target_link_libraries(LibGC PRIVATE AK)
|
||||||
|
add_swift_target_properties(LibGC LAGOM_LIBRARIES AK)
|
||||||
|
endif()
|
||||||
|
|
105
Libraries/LibGC/Heap+Swift.swift
Normal file
105
Libraries/LibGC/Heap+Swift.swift
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
import AK
|
||||||
|
@_exported import GCCxx
|
||||||
|
|
||||||
|
extension GC.Heap {
|
||||||
|
public func withDeferredGC<R, E>(_ body: () throws(E) -> R) throws(E) -> R {
|
||||||
|
let deferredRAII = GC.DeferGC(self)
|
||||||
|
_ = deferredRAII
|
||||||
|
return try body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Cell and Cell::Visitor are not imported properly, so we have to treat them as OpaquePointer
|
||||||
|
public protocol HeapAllocatable {
|
||||||
|
static func allocate(on heap: GC.Heap) -> UnsafeMutablePointer<Self>
|
||||||
|
|
||||||
|
init(cell: OpaquePointer)
|
||||||
|
|
||||||
|
func finalize()
|
||||||
|
func visitEdges(_ visitor: OpaquePointer)
|
||||||
|
|
||||||
|
var cell: OpaquePointer { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Figure out why other modules can't conform to HeapAllocatable
|
||||||
|
public struct HeapString: HeapAllocatable {
|
||||||
|
public var string: Swift.String
|
||||||
|
|
||||||
|
public init(cell: OpaquePointer) {
|
||||||
|
self.cell = cell
|
||||||
|
self.string = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: HeapAllocatable cannot be exposed to C++ yet, so we're off to void* paradise
|
||||||
|
public static func create(on heap: GC.Heap, string: Swift.String) -> OpaquePointer {
|
||||||
|
// 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
|
||||||
|
precondition(heap.is_gc_deferred())
|
||||||
|
let heapString = allocate(on: heap)
|
||||||
|
heapString.pointee.string = string
|
||||||
|
return heapString.pointee.cell
|
||||||
|
}
|
||||||
|
|
||||||
|
public var cell: OpaquePointer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here be dragons
|
||||||
|
|
||||||
|
func asTypeMetadataPointer(_ type: Any.Type) -> UnsafeMutableRawPointer {
|
||||||
|
unsafeBitCast(type, to: UnsafeMutableRawPointer.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asHeapAllocatableType(_ typeMetadata: UnsafeMutableRawPointer) -> any HeapAllocatable.Type {
|
||||||
|
let typeObject = unsafeBitCast(typeMetadata, to: Any.Type.self)
|
||||||
|
guard let type = typeObject as? any HeapAllocatable.Type else {
|
||||||
|
fatalError("Passed foreign class but it wasn't a Swift type!")
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HeapAllocatable {
|
||||||
|
fileprivate static func initializeFromFFI(at this: UnsafeMutableRawPointer, cell: OpaquePointer) {
|
||||||
|
this.assumingMemoryBound(to: Self.self).initialize(to: Self.self.init(cell: cell))
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate static func destroyFromFFI(at this: UnsafeMutableRawPointer) {
|
||||||
|
this.assumingMemoryBound(to: Self.self).deinitialize(count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate static func finalizeFromFFI(at this: UnsafeMutableRawPointer) {
|
||||||
|
this.assumingMemoryBound(to: Self.self).pointee.finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate static func visitEdgesFromFFI(at this: UnsafeMutableRawPointer, visitor: OpaquePointer) {
|
||||||
|
this.assumingMemoryBound(to: Self.self).pointee.visitEdges(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func allocate(on heap: GC.Heap) -> UnsafeMutablePointer<Self> {
|
||||||
|
let vtable = GC.ForeignCell.Vtable(
|
||||||
|
class_metadata_pointer: asTypeMetadataPointer(Self.self),
|
||||||
|
class_name: AK.String(swiftString: Swift.String(describing: Self.self)),
|
||||||
|
alignment: MemoryLayout<Self>.alignment,
|
||||||
|
initialize: { this, typeMetadata, cell in
|
||||||
|
asHeapAllocatableType(typeMetadata!).initializeFromFFI(at: this!, cell: cell.ptr())
|
||||||
|
},
|
||||||
|
destroy: { this, typeMetadata in
|
||||||
|
asHeapAllocatableType(typeMetadata!).destroyFromFFI(at: this!)
|
||||||
|
},
|
||||||
|
finalize: { this, typeMetadata in
|
||||||
|
asHeapAllocatableType(typeMetadata!).finalizeFromFFI(at: this!)
|
||||||
|
},
|
||||||
|
visit_edges: nil
|
||||||
|
)
|
||||||
|
let cell = GC.ForeignCell.create(heap, MemoryLayout<Self>.stride, vtable)
|
||||||
|
return cell.pointee.foreign_data().assumingMemoryBound(to: Self.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func finalize() {}
|
||||||
|
public func visitEdges(_ visitor: OpaquePointer) {}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
#include <AK/Noncopyable.h>
|
#include <AK/Noncopyable.h>
|
||||||
#include <AK/NonnullOwnPtr.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
#include <AK/StackInfo.h>
|
#include <AK/StackInfo.h>
|
||||||
|
#include <AK/Swift.h>
|
||||||
#include <AK/Types.h>
|
#include <AK/Types.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibCore/Forward.h>
|
#include <LibCore/Forward.h>
|
||||||
|
@ -150,7 +151,7 @@ private:
|
||||||
bool m_collecting_garbage { false };
|
bool m_collecting_garbage { false };
|
||||||
StackInfo m_stack_info;
|
StackInfo m_stack_info;
|
||||||
AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> m_gather_embedder_roots;
|
AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> m_gather_embedder_roots;
|
||||||
};
|
} SWIFT_IMMORTAL_REFERENCE;
|
||||||
|
|
||||||
inline void Heap::did_create_root(Badge<RootImpl>, RootImpl& impl)
|
inline void Heap::did_create_root(Badge<RootImpl>, RootImpl& impl)
|
||||||
{
|
{
|
||||||
|
|
|
@ -501,6 +501,7 @@ if (BUILD_TESTING)
|
||||||
AK
|
AK
|
||||||
LibCrypto
|
LibCrypto
|
||||||
LibCompress
|
LibCompress
|
||||||
|
LibGC
|
||||||
LibTest
|
LibTest
|
||||||
LibTextCodec
|
LibTextCodec
|
||||||
LibThreading
|
LibThreading
|
||||||
|
|
17
Tests/LibGC/CMakeLists.txt
Normal file
17
Tests/LibGC/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
if (ENABLE_SWIFT)
|
||||||
|
find_package(SwiftTesting REQUIRED)
|
||||||
|
|
||||||
|
add_executable(TestGCSwift
|
||||||
|
TestGCBindings.swift
|
||||||
|
TestHeap.cpp
|
||||||
|
TestInterop.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# FIXME: Swift doesn't seem to like object libraries for @main
|
||||||
|
target_sources(TestGCSwift PRIVATE ../Resources/SwiftTestMain.swift)
|
||||||
|
|
||||||
|
set_target_properties(TestGCSwift PROPERTIES SUFFIX .swift-testing)
|
||||||
|
target_include_directories(TestGCSwift PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(TestGCSwift PRIVATE AK LibGC SwiftTesting::SwiftTesting)
|
||||||
|
add_test(NAME TestGCSwift COMMAND TestGCSwift)
|
||||||
|
endif()
|
39
Tests/LibGC/TestGCBindings.swift
Normal file
39
Tests/LibGC/TestGCBindings.swift
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
import AK
|
||||||
|
import GC
|
||||||
|
import GCTesting
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
// FIXME: We want a type declared *here* for HeapString, but it gives a compiler warning:
|
||||||
|
// error: type 'GCString' cannot conform to protocol 'HeapAllocatable' because it has requirements that cannot be satisfied
|
||||||
|
// Even using the same exact code from LibGC/Heap+Swift.swift
|
||||||
|
// This is likely because one of the required types for HeapAllocatable is not fully imported from C++ and thus can't
|
||||||
|
// be re-exported by the GC module.
|
||||||
|
|
||||||
|
@Suite(.serialized)
|
||||||
|
struct TestGCSwiftBindings {
|
||||||
|
|
||||||
|
@Test func createBoundString() {
|
||||||
|
let heap = test_gc_heap()
|
||||||
|
let string = heap.withDeferredGC {
|
||||||
|
return HeapString.allocate(on: heap)
|
||||||
|
}
|
||||||
|
#expect(string.pointee.string == "")
|
||||||
|
heap.collect_garbage(GC.Heap.CollectionType.CollectGarbage)
|
||||||
|
|
||||||
|
string.pointee.string = "Hello, World!"
|
||||||
|
heap.collect_garbage(GC.Heap.CollectionType.CollectGarbage)
|
||||||
|
#expect(string.pointee.string == "Hello, World!")
|
||||||
|
|
||||||
|
heap.collect_garbage(GC.Heap.CollectionType.CollectEverything)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func testInterop() {
|
||||||
|
test_interop()
|
||||||
|
}
|
||||||
|
}
|
15
Tests/LibGC/TestHeap.cpp
Normal file
15
Tests/LibGC/TestHeap.cpp
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TestHeap.h"
|
||||||
|
#include <LibGC/Heap.h>
|
||||||
|
|
||||||
|
GC::Heap& test_gc_heap()
|
||||||
|
{
|
||||||
|
// FIXME: The GC heap should become thread aware!
|
||||||
|
thread_local GC::Heap heap(nullptr, [](auto&) {});
|
||||||
|
return heap;
|
||||||
|
}
|
11
Tests/LibGC/TestHeap.h
Normal file
11
Tests/LibGC/TestHeap.h
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGC/Forward.h>
|
||||||
|
|
||||||
|
GC::Heap& test_gc_heap();
|
44
Tests/LibGC/TestInterop.cpp
Normal file
44
Tests/LibGC/TestInterop.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TestInterop.h"
|
||||||
|
#include "TestHeap.h"
|
||||||
|
#include <AK/TypeCasts.h>
|
||||||
|
#include <LibGC-Swift.h>
|
||||||
|
#include <LibGC/ForeignCell.h>
|
||||||
|
#include <LibGC/Heap.h>
|
||||||
|
|
||||||
|
#define COLLECT heap.collect_garbage(GC::Heap::CollectionType::CollectGarbage)
|
||||||
|
#define COLLECT_ALL heap.collect_garbage(GC::Heap::CollectionType::CollectEverything)
|
||||||
|
|
||||||
|
void test_interop()
|
||||||
|
{
|
||||||
|
auto& heap = test_gc_heap();
|
||||||
|
|
||||||
|
COLLECT_ALL;
|
||||||
|
|
||||||
|
auto string = GC::ForeignRef<GC::HeapString>::allocate(heap, "Hello, World!");
|
||||||
|
|
||||||
|
COLLECT;
|
||||||
|
|
||||||
|
auto strings_string = std::string(string->getString());
|
||||||
|
VERIFY(strings_string == "Hello, World!");
|
||||||
|
|
||||||
|
COLLECT;
|
||||||
|
|
||||||
|
auto* cell = string->getCell();
|
||||||
|
VERIFY(cell == static_cast<GC::Cell*>(string.cell()));
|
||||||
|
|
||||||
|
COLLECT;
|
||||||
|
|
||||||
|
strings_string = std::string(string->getString());
|
||||||
|
|
||||||
|
COLLECT;
|
||||||
|
|
||||||
|
VERIFY(strings_string == "Hello, World!");
|
||||||
|
|
||||||
|
COLLECT_ALL;
|
||||||
|
}
|
9
Tests/LibGC/TestInterop.h
Normal file
9
Tests/LibGC/TestInterop.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void test_interop();
|
6
Tests/LibGC/module.modulemap
Normal file
6
Tests/LibGC/module.modulemap
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module GCTesting {
|
||||||
|
header "TestHeap.h"
|
||||||
|
header "TestInterop.h"
|
||||||
|
requires cplusplus
|
||||||
|
export *
|
||||||
|
}
|
Loading…
Reference in a new issue