diff --git a/Package.resolved b/Package.resolved index abab70e4..a792ce23 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "9fc132d8a2914e41367e55e17402381d2909fd00acd94ebc8bdb4ecf8809f175", + "originHash" : "388a5c22c69d82c7f6b3b006c51d450c398ee78df767269226a0294ec043c444", "pins" : [ { "identity" : "darwinprivateframeworks", diff --git a/Package.swift b/Package.swift index 0953d6fd..a1e4c423 100644 --- a/Package.swift +++ b/Package.swift @@ -135,7 +135,7 @@ if libraryEvolutionCondition { // MARK: - Targets -let openGraphTarget = Target.target( +let openAttributeGraphTarget = Target.target( name: "OpenAttributeGraph", dependencies: ["OpenAttributeGraphCxx"], cSettings: sharedCSettings, @@ -144,7 +144,7 @@ let openGraphTarget = Target.target( // FIXME: Merge into one target // OpenAttributeGraph is a C++ & Swift mix target. // The SwiftPM support for such usage is still in progress. -let openGraphSPITarget = Target.target( +let openAttributeGraphSPITarget = Target.target( name: "OpenAttributeGraphCxx", cSettings: sharedCSettings + [ .define("__COREFOUNDATION_FORSWIFTFOUNDATIONONLY__", to: "1", .when(platforms: .nonDarwinPlatforms)), @@ -153,7 +153,7 @@ let openGraphSPITarget = Target.target( .linkedLibrary("z"), ] ) -let openGraphShimsTarget = Target.target( +let openAttributeGraphShimsTarget = Target.target( name: "OpenAttributeGraphShims", cSettings: sharedCSettings, swiftSettings: sharedSwiftSettings @@ -161,7 +161,7 @@ let openGraphShimsTarget = Target.target( // MARK: - Test Targets -let openGraphTestsTarget = Target.testTarget( +let openAttributeGraphTestsTarget = Target.testTarget( name: "OpenAttributeGraphTests", dependencies: [ "OpenAttributeGraph", @@ -170,15 +170,16 @@ let openGraphTestsTarget = Target.testTarget( cSettings: sharedCSettings, swiftSettings: sharedSwiftSettings ) -let openGraphCxxTestsTarget = Target.testTarget( +let openAttributeGraphCxxTestsTarget = Target.testTarget( name: "OpenAttributeGraphCxxTests", dependencies: [ "OpenAttributeGraphCxx", ], exclude: ["README.md"], + cSettings: sharedCSettings + [.define("SWIFT_TESTING")], swiftSettings: sharedSwiftSettings + [.interoperabilityMode(.Cxx)] ) -let openGraphShimsTestsTarget = Target.testTarget( +let openAttributeGraphShimsTestsTarget = Target.testTarget( name: "OpenAttributeGraphShimsTests", dependencies: [ "OpenAttributeGraphShims", @@ -187,7 +188,7 @@ let openGraphShimsTestsTarget = Target.testTarget( cSettings: sharedCSettings, swiftSettings: sharedSwiftSettings ) -let openGraphCompatibilityTestsTarget = Target.testTarget( +let openAttributeGraphCompatibilityTestsTarget = Target.testTarget( name: "OpenAttributeGraphCompatibilityTests", dependencies: [ .product(name: "Numerics", package: "swift-numerics"), @@ -209,14 +210,14 @@ let package = Package( .package(url: "https://github.com/apple/swift-numerics", from: "1.0.2"), ], targets: [ - openGraphTarget, - openGraphSPITarget, - openGraphShimsTarget, - - openGraphTestsTarget, - openGraphCxxTestsTarget, - openGraphShimsTestsTarget, - openGraphCompatibilityTestsTarget, + openAttributeGraphTarget, + openAttributeGraphSPITarget, + openAttributeGraphShimsTarget, + + openAttributeGraphTestsTarget, + openAttributeGraphCxxTestsTarget, + openAttributeGraphShimsTestsTarget, + openAttributeGraphCompatibilityTestsTarget, ], cxxLanguageStandard: .cxx20 ) @@ -253,7 +254,7 @@ if attributeGraphCondition { privateFrameworkRepo = Package.Dependency.package(url: "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", branch: "main") } package.dependencies.append(privateFrameworkRepo) - openGraphShimsTarget.addAGSettings() + openAttributeGraphShimsTarget.addAGSettings() let agVersion = Context.environment["DARWIN_PRIVATE_FRAMEWORKS_TARGET_RELEASE"].flatMap { Int($0) } ?? 2024 package.platforms = switch agVersion { @@ -262,15 +263,15 @@ if attributeGraphCondition { default: nil } } else { - openGraphShimsTarget.dependencies.append("OpenAttributeGraph") + openAttributeGraphShimsTarget.dependencies.append("OpenAttributeGraph") package.platforms = [.iOS(.v13), .macOS(.v10_15), .macCatalyst(.v13), .tvOS(.v13), .watchOS(.v5)] } let compatibilityTestCondition = envEnable("OPENATTRIBUTEGRAPH_COMPATIBILITY_TEST") if compatibilityTestCondition && attributeGraphCondition { - openGraphCompatibilityTestsTarget.addCompatibilitySettings() + openAttributeGraphCompatibilityTestsTarget.addCompatibilitySettings() } else { - openGraphCompatibilityTestsTarget.dependencies.append("OpenAttributeGraph") + openAttributeGraphCompatibilityTestsTarget.dependencies.append("OpenAttributeGraph") } extension [Platform] { diff --git a/Scripts/bump_ag_pr.sh b/Scripts/bump_ag_pr.sh index 0f6229c0..0e4eaf49 100755 --- a/Scripts/bump_ag_pr.sh +++ b/Scripts/bump_ag_pr.sh @@ -64,7 +64,7 @@ while [[ $# -gt 0 ]]; do done SCRIPT_ROOT="$(dirname $(dirname $(filepath $0)))" -OAG_REPO_DIR="$SCRIPT_ROOT/.og_repo" +OAG_REPO_DIR="$SCRIPT_ROOT/.oag_repo" OPENATTRIBUTEGRAPH_ROOT="$OAG_REPO_DIR" AG_REPO_DIR="$SCRIPT_ROOT/.ag_repo" @@ -184,4 +184,4 @@ gh pr create \ --base main echo "✅ PR created successfully!" -echo "Branch: update-ag-$TARGET_BRANCH" \ No newline at end of file +echo "Branch: update-ag-$TARGET_BRANCH" diff --git a/Sources/OpenAttributeGraphCxx/DebugServer/DebugServer.mm b/Sources/OpenAttributeGraphCxx/DebugServer/DebugServer.mm index 665d223a..4b654664 100644 --- a/Sources/OpenAttributeGraphCxx/DebugServer/DebugServer.mm +++ b/Sources/OpenAttributeGraphCxx/DebugServer/DebugServer.mm @@ -30,7 +30,7 @@ OAG_EXTERN_C_BEGIN // DYLD_INTERPOSE does not work. Directly use the hook one here to match the semantics. -bool og_variant_has_internal_diagnostics(const char *subsystem); +bool oag_variant_has_internal_diagnostics(const char *subsystem); OAG_EXTERN_C_END // MARK: DebugServer public API Implementation @@ -265,7 +265,7 @@ if ( (mode & OAGDebugServerModeValid) && !OAG::DebugServer::has_shared_server() - && og_variant_has_internal_diagnostics("org.OpenSwiftUIProject.OpenAttributeGraph") + && oag_variant_has_internal_diagnostics("org.OpenSwiftUIProject.OpenAttributeGraph") ) { _shared_server = new DebugServer(mode); } diff --git a/Sources/OpenAttributeGraphCxx/DebugServer/interpose.c b/Sources/OpenAttributeGraphCxx/DebugServer/interpose.c index 97559277..0b179d4c 100644 --- a/Sources/OpenAttributeGraphCxx/DebugServer/interpose.c +++ b/Sources/OpenAttributeGraphCxx/DebugServer/interpose.c @@ -11,7 +11,7 @@ extern bool os_variant_has_internal_diagnostics(const char *subsystem); #endif -bool og_variant_has_internal_diagnostics(const char *subsystem) { +bool oag_variant_has_internal_diagnostics(const char *subsystem) { if (strcmp(subsystem, "org.OpenSwiftUIProject.OpenAttributeGraph") == 0) { return true; } else if (strcmp(subsystem, "com.apple.AttributeGraph") == 0) { @@ -30,5 +30,5 @@ bool og_variant_has_internal_diagnostics(const char *subsystem) { __attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \ __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; -DYLD_INTERPOSE(og_variant_has_internal_diagnostics, os_variant_has_internal_diagnostics) +DYLD_INTERPOSE(oag_variant_has_internal_diagnostics, os_variant_has_internal_diagnostics) #endif diff --git a/Sources/OpenAttributeGraphCxx/Util/HashTable.cpp b/Sources/OpenAttributeGraphCxx/Util/HashTable.cpp new file mode 100644 index 00000000..9dfbddb9 --- /dev/null +++ b/Sources/OpenAttributeGraphCxx/Util/HashTable.cpp @@ -0,0 +1,306 @@ +// +// HashTable.cpp +// OpenAttributeGraphCxx +// +// Status: Complete +// Modified based Compute code + +#include +#include +#include + +namespace util { + +uint64_t pointer_hash(void const *pointer) { + int64_t result = (-1 ^ (int64_t)(pointer) << 0x20) + (int64_t)pointer; + result = result ^ result >> 0x16; + result = result + (-1 ^ result << 0xd); + result = (result ^ result >> 8) * 9; + result = result ^ result >> 0xf; + result = result + (-1 ^ result << 0x1b); + return result ^ result >> 0x1f; +} + +bool pointer_compare(void const *a, void const *b) { return a == b; } + +uint64_t string_hash(char const *str) { + int64_t result = 0; + for (char const *c = str; *c; c += 1) { + result = result * 33 + *c; + } + return result; +} + +constexpr uint32_t initial_bucket_mask_width = 4; + +UntypedTable *UntypedTable::create() { return new UntypedTable(); } + +void UntypedTable::destroy(UntypedTable *value) { delete value; } + +UntypedTable::UntypedTable() { + _hash = pointer_hash; + _compare = pointer_compare; + _did_remove_key = nullptr; + _did_remove_value = nullptr; + _heap = nullptr; + _spare_node = 0; + _buckets = nullptr; + _count = 0; + _bucket_mask = 0; + _bucket_mask_width = 0; + _is_heap_owner = true; + _compare_by_pointer = true; +} + +UntypedTable::UntypedTable(hasher custom_hash, key_equal custom_compare, key_callback did_remove_key, + value_callback did_remove_value, Heap *heap) { + _hash = custom_hash != nullptr ? custom_hash : pointer_hash; + _compare = custom_compare != nullptr ? custom_compare : pointer_compare; + _did_remove_key = did_remove_key; + _did_remove_value = did_remove_value; + _heap = heap; + _spare_node = 0; + _buckets = nullptr; + _count = 0; + _bucket_mask = 0; + _bucket_mask_width = 0; + _is_heap_owner = heap == nullptr; + _compare_by_pointer = custom_compare == nullptr || custom_compare == pointer_compare; +} + +UntypedTable::~UntypedTable() { + if ((_did_remove_key || _did_remove_value) && _count) { + for (uint32_t bucket = 0; !(bucket >> _bucket_mask_width); bucket++) { + for (HashNode *node = _buckets[bucket]; node != nullptr; node = node->next) { + if (_did_remove_key) { + _did_remove_key(node->key); + } + if (_did_remove_value) { + _did_remove_value(node->value); + } + } + } + } + if (_bucket_mask_width > initial_bucket_mask_width) { + free(_buckets); + } + if (_is_heap_owner && _heap) { + delete _heap; + } +} + +#pragma mark - Managing buckets + +// Buckets are initially allocated by the util::Heap instance, +// until they grow past initial_bucket_mask_width where they are allocated using new.ˆ + +void UntypedTable::create_buckets() { + if (_buckets != nullptr) { + return; + } + + _bucket_mask_width = initial_bucket_mask_width; + _bucket_mask = (1 << initial_bucket_mask_width) - 1; + + if (_heap == nullptr) { + _heap = new Heap(nullptr, 0, Heap::minimum_increment); + } + + size_t num_buckets = (1 << initial_bucket_mask_width); + _buckets = _heap->alloc(num_buckets); + memset(_buckets, 0, sizeof(Bucket) * num_buckets); +} + +void UntypedTable::grow_buckets() { + if (_bucket_mask_width > 30) { + return; + } + + uint32_t old_width = _bucket_mask_width; + _bucket_mask_width = old_width + 1; + + Bucket *old_buckets = _buckets; + Bucket *new_buckets = nullptr; + + size_t num_buckets = 1 << _bucket_mask_width; + if (_bucket_mask_width > initial_bucket_mask_width) { + new_buckets = new Bucket[num_buckets]; + } else { + if (_heap == nullptr) { + _heap = new Heap(nullptr, 0, Heap::minimum_increment); + } + new_buckets = _heap->alloc(num_buckets); + } + memset(new_buckets, 0, sizeof(Bucket) * num_buckets); + + // redistribute old buckets into new + if (new_buckets) { + _bucket_mask = num_buckets - 1; + for (uint32_t i = 0; !(i >> old_width); i++) { + for (UntypedTable::HashNode *node = old_buckets[i]; node != nullptr; node = node->next) { + uint64_t new_bucket = _bucket_mask & node->hash_value; + node->next = new_buckets[new_bucket]; + new_buckets[new_bucket] = node; + } + } + _buckets = new_buckets; + + if (old_width > initial_bucket_mask_width) { + delete old_buckets; + } + } +} + +#pragma mark - Lookup + +UntypedTable::value_type UntypedTable::lookup(key_type key, nullable_key_type *found_key_out) const noexcept { + if (_count) { + uint64_t hash_value = _hash(key); + HashNode *node = _buckets[_bucket_mask & hash_value]; + if (_compare_by_pointer) { + for (; node != nullptr; node = node->next) { + if (node->key == key) { + if (found_key_out) { + *found_key_out = node->key; + } + return node->value; + } + } + } else if (node) { + for (; node != nullptr; node = node->next) { + if (node->hash_value == hash_value && _compare(node->key, key)) { + if (found_key_out) { + *found_key_out = node->key; + } + return node->value; + } + } + } + } + if (found_key_out) { + *found_key_out = nullptr; + } + return nullptr; +} + +#pragma mark - Modifying entries + +bool UntypedTable::insert(key_type key, value_type value) { + if (_buckets == nullptr) { + this->create_buckets(); + } + + void *result; + + uint64_t hash_value = _hash(key); + HashNode *node = _buckets[hash_value & _bucket_mask]; + + // replace existing if match + for (; node != nullptr; node = node->next) { + if (node->hash_value == hash_value && _compare(node->key, key)) { + if (_did_remove_key) { + _did_remove_key(node->key); + } + if (_did_remove_value) { + _did_remove_value(node->value); + } + node->key = key; + node->value = value; + return false; + } + } + + // insert new + if (_count + 1 > 4 << _bucket_mask_width) { + this->grow_buckets(); + } + if (!_heap) { + _heap = new Heap(nullptr, 0, Heap::minimum_increment); + } + + HashNode *inserted_node = _spare_node; + if (inserted_node) { + _spare_node = _spare_node->next; + } else { + inserted_node = _heap->alloc(); + } + + inserted_node->key = key; + inserted_node->value = value; + inserted_node->hash_value = hash_value; + + uint64_t bucket = _bucket_mask & hash_value; + inserted_node->next = _buckets[bucket]; + _buckets[bucket] = inserted_node; + + _count += 1; + + return true; +} + +bool UntypedTable::remove(key_type key) { + if (_count == 0) { + return false; + } + if (_compare_by_pointer) { + return this->remove_ptr(key); + } + uint64_t hash_value = _hash(key); + HashNode *node = _buckets[_bucket_mask & hash_value]; + + for (HashNode *candidate = node; candidate != nullptr; candidate = candidate->next) { + if (candidate->hash_value == hash_value && _compare(candidate->key, key)) { + node->next = candidate->next; + if (_did_remove_key) { + _did_remove_key(candidate->key); + } + if (_did_remove_value) { + _did_remove_value(candidate->value); + } + candidate->next = _spare_node; + _spare_node = candidate; + _count -= 1; + return true; + } + node = candidate; + } + + return false; +} + +bool UntypedTable::remove_ptr(key_type key) { + if (_count == 0) { + return false; + } + + HashNode *node = _buckets[_bucket_mask & _hash(key)]; + for (HashNode *candidate = node; candidate != nullptr; candidate = candidate->next) { + if (candidate->key == key) { + node->next = candidate->next; + if (_did_remove_key) { + _did_remove_key(candidate->key); + } + if (_did_remove_value) { + _did_remove_value(candidate->value); + } + candidate->next = _spare_node; + _spare_node = candidate; + _count -= 1; + return true; + } + node = candidate; + } + return false; +} + +void UntypedTable::for_each(entry_callback body, void *context) const { + if (_count) { + for (uint32_t i = 0; !(i >> _bucket_mask_width); i++) { + for (UntypedTable::HashNode *node = _buckets[i]; node != nullptr; node = node->next) { + body(node->key, node->value, context); + } + } + } +} + +} /* namespace util */ diff --git a/Sources/OpenAttributeGraphCxx/Util/Heap.cpp b/Sources/OpenAttributeGraphCxx/Util/Heap.cpp new file mode 100644 index 00000000..7efa3897 --- /dev/null +++ b/Sources/OpenAttributeGraphCxx/Util/Heap.cpp @@ -0,0 +1,99 @@ +// +// Heap.cpp +// OpenAttributeGraphCxx +// +// Status: Complete +// Modified based Compute code + +#include +#include + +namespace util { + +constexpr size_t default_increment = 0x2000; + +Heap *Heap::create(char *_Nullable start, size_t capacity, size_t increment) { + return new Heap(start, capacity, increment); +} + +void Heap::destroy(Heap *value) { delete value; } + +Heap::Heap(char *start, size_t capacity, size_t increment) { + // enforce minimum but treat 0 as the default + size_t effective_increment = increment > 0 ? std::max(increment, minimum_increment) : default_increment; + + _increment = effective_increment; + _node = nullptr; + reset(start, capacity); +}; + +util::Heap::~Heap() { reset(nullptr, 0); } + +void *util::Heap::alloc_(size_t size) { + if (_capacity >= size) { + char *result = _free_start; + _free_start += size; + _capacity -= size; + return result; + } + + if (size <= minimum_increment) { + int64_t increment = _increment; + char *buffer = static_cast(malloc(increment)); + + _free_start = buffer; + _capacity = increment; + + Node *node = alloc(); + + node->next = _node; + node->buffer = buffer; + _node = node; + + char *result = _free_start; + _free_start += size; + _capacity -= size; + return result; + } + + Node *node = alloc(); + void *result = malloc(size); + if (result) { + node->next = _node; + node->buffer = result; + _node = node; + } + return result; +} + +void util::Heap::reset(char *_Nullable start, size_t capacity) { + while (_node) { + void *buffer = _node->buffer; + _node = _node->next; + free(buffer); + } + + constexpr uintptr_t alignment_mask = sizeof(char *) - 1; + char *aligned_start = (char *)(((uintptr_t)start + alignment_mask) & ~alignment_mask); + + bool prealigned = ((uintptr_t)start & alignment_mask) == 0; + _free_start = prealigned ? start : aligned_start; + _capacity = capacity + (start - aligned_start); +} + +size_t util::Heap::num_nodes() const { + size_t count = 0; + for (Node *node = _node; node != nullptr; node = node->next) { + count += 1; + } + return count; +} + +void util::Heap::print() const { + fprintf(stdout, "Nodes\n"); + for (Node *node = _node; node != nullptr; node = node->next) { + fprintf(stdout, "address=%-16p; buffer=%-16p; next=%-16p\n", node, node->buffer, node->next); + } +} + +}; /* namespace util */ diff --git a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/DebugServer/DebugServer.hpp b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/DebugServer/DebugServer.hpp index 3f4e7260..813fe52a 100644 --- a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/DebugServer/DebugServer.hpp +++ b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/DebugServer/DebugServer.hpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include OAG_ASSUME_NONNULL_BEGIN diff --git a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/SwiftBridging.h b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/SwiftBridging.h new file mode 100644 index 00000000..73046f82 --- /dev/null +++ b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/SwiftBridging.h @@ -0,0 +1,311 @@ +// +// SwiftBridging.h +// OpenAttributeGraphCxx +// +// Cross-platform Swift bridging compatibility header to solve issue on non-Darwin swift toolchain +// Based on WebKit's SwiftBridging.h approach https://github.com/WebKit/WebKit/blob/84fc0edf9e8649a56cca35cc48c73f211e310d14/Source/WTF/wtf/SwiftBridging.h + +#pragma once + +#if __has_include() + +#include + +#ifndef SWIFT_NONESCAPABLE +#define SWIFT_NONESCAPABLE +#endif + +#ifndef SWIFT_ESCAPABLE +#define SWIFT_ESCAPABLE +#endif + +#ifndef SWIFT_RETURNS_UNRETAINED +#define SWIFT_RETURNS_UNRETAINED +#endif + +#ifndef SWIFT_ESCAPABLE_IF +#define SWIFT_ESCAPABLE_IF(...) +#endif + +#ifndef SWIFT_PRIVATE_FILEID +#define SWIFT_PRIVATE_FILEID(_fileID) +#endif + +#else + +// Copied from https://github.com/swiftlang/swift/blob/ff8b9f145320b02bc6de75163d78528f210bdba6/lib/ClangImporter/SwiftBridging/swift/bridging + +// -*- C++ -*- +//===------------------ bridging - C++ and Swift Interop --------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file provides common utilities and annotations that are useful for C++ +// codebases that interoperate with Swift. +// +//===----------------------------------------------------------------------===// +#ifndef SWIFT_CLANGIMPORTER_SWIFT_INTEROP_SUPPORT_H +#define SWIFT_CLANGIMPORTER_SWIFT_INTEROP_SUPPORT_H + +#ifdef __has_attribute +#define _CXX_INTEROP_HAS_ATTRIBUTE(x) __has_attribute(x) +#else +#define _CXX_INTEROP_HAS_ATTRIBUTE(x) 0 +#endif + +#if _CXX_INTEROP_HAS_ATTRIBUTE(swift_attr) + +/// Specifies that a C++ `class` or `struct` owns and controls the lifetime of all +/// of the objects it references. Such type should not reference any objects whose +/// lifetime is controlled externally. This annotation allows Swift to import methods +/// that return a `class` or `struct` type that is annotated with this macro. +#define SWIFT_SELF_CONTAINED __attribute__((swift_attr("import_owned"))) + +/// Specifies that a C++ method returns a value that is presumed to contain +/// objects whose lifetime is not dependent on `this` or other parameters passed +/// to the method. +#define SWIFT_RETURNS_INDEPENDENT_VALUE __attribute__((swift_attr("import_unsafe"))) + +#define _CXX_INTEROP_STRINGIFY(_x) #_x + +#define _CXX_INTEROP_CONCAT_(a,b,c,d,e,f,g,i,j,k,l,m,n,o,p,...) \ + #a "," #b "," #c "," #d "," #e "," #f "," #g "," #i "," #j "," #k "," \ + #l "," #m "," #n "," #o "," #p +#define _CXX_INTEROP_CONCAT(...) \ + _CXX_INTEROP_CONCAT_(__VA_ARGS__,,,,,,,,,,,,,,,,,) + +/// Specifies that a C++ `class` or `struct` is reference-counted using +/// the given `retain` and `release` functions. This annotation lets Swift import +/// such a type as reference counted type in Swift, taking advantage of Swift's +/// automatic reference counting. +/// +/// This example shows how to use this macro to let Swift know that +/// a non-copyable reference counted C++ class can be imported as a reference counted type in Swift: +/// ```c++ +/// class SWIFT_SHARED_REFERENCE(retainSharedObject, releaseSharedObject) +/// SharedObject : NonCopyable, IntrusiveReferenceCounted { +/// public: +/// static SharedObject* create(); +/// void doSomething(); +/// }; +/// +/// void retainSharedObject(SharedObject *); +/// void releaseSharedObject(SharedObject *); +/// ``` +/// +/// Then, the Swift programmer would be able to use it in the following manner: +/// +/// ```swift +/// let object = SharedObject.create() +/// object.doSomething() +/// // The Swift compiler will release object here. +/// ``` +#define SWIFT_SHARED_REFERENCE(_retain, _release) \ + __attribute__((swift_attr("import_reference"))) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:_retain)))) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:_release)))) + +/// Specifies that a C++ `class` or `struct` is a reference type whose lifetime +/// is presumed to be immortal, i.e. the reference to such object is presumed to +/// always be valid. This annotation lets Swift import such a type as a reference +/// type in Swift. +//// +/// This example shows how to use this macro to let Swift know that +/// a non-copyable singleton C++ class can be imported as a reference type in Swift: +/// ```c++ +/// class SWIFT_IMMORTAL_REFERENCE +/// LoggerSingleton : NonCopyable { +/// public: +/// static LoggerSingleton &getInstance(); +/// void log(int x); +/// }; +/// ``` +/// +/// Then, the Swift programmer would be able to use it in the following manner: +/// +/// ```swift +/// let logger = LoggerSingleton.getInstance() +/// logger.log(123) +/// ``` +#define SWIFT_IMMORTAL_REFERENCE \ + __attribute__((swift_attr("import_reference"))) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:immortal)))) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:immortal)))) + +/// Specifies that a C++ `class` or `struct` is a reference type whose lifetime +/// is not managed automatically. The programmer must validate that any reference +/// to such object is valid themselves. This annotation lets Swift import such a type as a reference type in Swift. +#define SWIFT_UNSAFE_REFERENCE \ + __attribute__((swift_attr("import_reference"))) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:immortal)))) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:immortal)))) \ + __attribute__((swift_attr("unsafe"))) + +/// Specifies a name that will be used in Swift for this declaration instead of its original name. +#define SWIFT_NAME(_name) __attribute__((swift_name(#_name))) + +/// Specifies that a specific C++ `class` or `struct` conforms to a +/// a specific Swift protocol. +/// +/// This example shows how to use this macro to conform a class template to a Swift protocol: +/// ``` +/// template +/// class SWIFT_CONFORMS_TO_PROTOCOL(SwiftModule.ProtocolName) +/// CustomClass {}; +/// ``` +#define SWIFT_CONFORMS_TO_PROTOCOL(_moduleName_protocolName) \ + __attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(conforms_to:_moduleName_protocolName)))) + +/// Specifies that a specific C++ method should be imported as a computed +/// property. If this macro is specified on a getter, a getter will be +/// synthesized. If this macro is specified on a setter, both a getter and +/// setter will be synthesized. +/// +/// For example: +/// ``` +/// int getX() SWIFT_COMPUTED_PROPERTY; +/// ``` +/// Will be imported as `var x: CInt {...}`. +#define SWIFT_COMPUTED_PROPERTY \ + __attribute__((swift_attr("import_computed_property"))) + +/// Specifies that a specific **constant** C++ member function should be imported as +/// `mutating` Swift method. This annotation should be added to constant C++ member functions +/// that mutate `mutable` fields in a C++ object, to let Swift know that this function is still mutating +/// and thus that it should become a `mutating` method in Swift. +#define SWIFT_MUTATING \ + __attribute__((swift_attr("mutating"))) + +/// Specifies that a specific c++ type such class or struct should be imported as type marked +/// as `@unchecked Sendable` type in swift. If this annotation is used, the type is therefore allowed to +/// use safely across async contexts. +/// +/// For example +/// ``` +/// class SWIFT_UNCHECKED_SENDABLE CustomUserType +/// { ... } +/// ``` +/// Will be imported as `struct CustomUserType: @unchecked Sendable` +#define SWIFT_UNCHECKED_SENDABLE \ + __attribute__((swift_attr("@Sendable"))) + +/// Specifies that a specific c++ type such class or struct should be imported +/// as a non-copyable Swift value type. +#define SWIFT_NONCOPYABLE \ + __attribute__((swift_attr("~Copyable"))) + +/// Specifies that a specific c++ type such class or struct should be imported +/// as a non-escapable Swift value type when the non-escapable language feature +/// is enabled. +#define SWIFT_NONESCAPABLE \ + __attribute__((swift_attr("~Escapable"))) + +/// Specifies that a specific c++ type such class or struct should be imported +/// as a escapable Swift value. While this matches the default behavior, +/// in safe mode interop mode it ensures that the type is not marked as +/// unsafe. +#define SWIFT_ESCAPABLE \ + __attribute__((swift_attr("Escapable"))) + +/// Specifies that a C++ `class` or `struct` should be imported as a escapable +/// Swift value if all of the specified template arguments are escapable. +#define SWIFT_ESCAPABLE_IF(...) \ + __attribute__((swift_attr("escapable_if:" _CXX_INTEROP_CONCAT(__VA_ARGS__)))) + +/// Specifies that the return value is passed as owned for C++ functions and +/// methods returning types annotated as `SWIFT_SHARED_REFERENCE` +#define SWIFT_RETURNS_RETAINED __attribute__((swift_attr("returns_retained"))) +/// Specifies that the return value is passed as unowned for C++ functions and +/// methods returning types annotated as `SWIFT_SHARED_REFERENCE` +#define SWIFT_RETURNS_UNRETAINED \ + __attribute__((swift_attr("returns_unretained"))) + +/// Applied to a C++ foreign reference type annotated with +/// SWIFT_SHARED_REFERENCE. Indicates that C++ APIs returning this type are +/// assumed to return an unowned (+0) value by default, unless explicitly annotated +/// with SWIFT_RETURNS_RETAINED. +/// +/// For example: +/// ```c++ +/// struct SWIFT_SHARED_REFERENCE(retainBar, releaseBar) +/// SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT +/// Bar { ... }; +/// ``` +/// +/// In Swift, C++ APIs returning `Bar*` will be assumed to return an unowned +/// value. +#define SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT \ + __attribute__((swift_attr("returned_as_unretained_by_default"))) + +/// Specifies that the non-public members of a C++ class, struct, or union can +/// be accessed from extensions of that type, in the given file ID. +/// +/// In other words, Swift's access controls will behave as if the non-public +/// members of the annotated C++ class were privated declared in the specified +/// Swift source file, rather than in a C++ header file/Clang module. +/// +/// For example, we can annotate a C++ class definition like this: +/// +/// ```c++ +/// class SWIFT_PRIVATE_FILEID("MySwiftModule/MySwiftFile.swift") +/// MyCxxClass { +/// private: +/// void privateMethod(); +/// int privateStorage; +/// }; +/// ``` +/// +/// Then, Swift extensions of `MyCxxClass` in `MySwiftModule/MySwiftFile.swift` +/// are allowed to access `privateMethod()` and `privateStorage`: +/// +/// ```swift +/// //-- MySwiftModule/SwiftFile.swift +/// extension MyCxxClass { +/// func ext() { +/// privateMethod() +/// print("\(privateStorage)") +/// } +/// } +/// ``` +/// +/// Non-public access is still forbidden outside of extensions and outside of +/// the designated file ID. +#define SWIFT_PRIVATE_FILEID(_fileID) \ + __attribute__((swift_attr("private_fileid:" _fileID))) + +#else // #if _CXX_INTEROP_HAS_ATTRIBUTE(swift_attr) + +// Empty defines for compilers that don't support `attribute(swift_attr)`. +#define SWIFT_SELF_CONTAINED +#define SWIFT_RETURNS_INDEPENDENT_VALUE +#define SWIFT_SHARED_REFERENCE(_retain, _release) +#define SWIFT_IMMORTAL_REFERENCE +#define SWIFT_UNSAFE_REFERENCE +#define SWIFT_NAME(_name) +#define SWIFT_CONFORMS_TO_PROTOCOL(_moduleName_protocolName) +#define SWIFT_COMPUTED_PROPERTY +#define SWIFT_MUTATING +#define SWIFT_UNCHECKED_SENDABLE +#define SWIFT_NONCOPYABLE +#define SWIFT_NONESCAPABLE +#define SWIFT_ESCAPABLE +#define SWIFT_ESCAPABLE_IF(...) +#define SWIFT_RETURNS_RETAINED +#define SWIFT_RETURNS_UNRETAINED +#define SWIFT_PRIVATE_FILEID(_fileID) + +#endif // #if _CXX_INTEROP_HAS_ATTRIBUTE(swift_attr) + +#undef _CXX_INTEROP_HAS_ATTRIBUTE + +#endif // SWIFT_CLANGIMPORTER_SWIFT_INTEROP_SUPPORT_H + +#endif diff --git a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/ForwardList.hpp b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/ForwardList.hpp new file mode 100644 index 00000000..298c0ec1 --- /dev/null +++ b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/ForwardList.hpp @@ -0,0 +1,179 @@ +// +// ForwardList.hpp +// OpenAttributeGraphCxx +// +// Status: Complete +// Modified based Compute code + +#ifndef OPENATTRIBUTEGRAPH_CXX_UTIL_FORWARD_LIST_HPP +#define OPENATTRIBUTEGRAPH_CXX_UTIL_FORWARD_LIST_HPP + +#include +#include + +OAG_ASSUME_NONNULL_BEGIN + +namespace util { + +/// ForwardList is a linked list container that uses util::Heap to allocate nodes, +/// reusing previously removed nodes where possible. +template class ForwardList { + public: + using reference = T &; + using const_reference = const T &; + + private: + struct Node { + Node *_Nullable next; + T value; + }; + + util::Heap *_heap; + Node *_Nullable _front; + Node *_Nullable _spare; + bool _is_heap_owner; + + public: + ForwardList(); + ForwardList(util::Heap *heap); + ~ForwardList(); + + // non-copyable + ForwardList(const ForwardList &) = delete; + ForwardList &operator=(const ForwardList &) = delete; + + // non-movable + ForwardList(ForwardList &&) = delete; + ForwardList &operator=(ForwardList &&) = delete; + + // MARK: Element access + + reference front(); + const_reference front() const; + + // MARK: Capacity + + bool empty() const noexcept { return _front == nullptr; } + + // MARK: Modifiers + + void push_front(const T &value); + void push_front(T &&value); + template void emplace_front(Args &&...args); + + void pop_front(); +}; + +template +ForwardList::ForwardList() : _heap(new Heap(nullptr, 0, util::Heap::minimum_increment)), _is_heap_owner(true){}; + +template ForwardList::ForwardList(util::Heap *heap) : _heap(heap), _is_heap_owner(false){}; + +template ForwardList::~ForwardList() { + if (_is_heap_owner && _heap) { + delete _heap; + } +}; + +template ForwardList::reference ForwardList::front() { + assert(!empty()); + return _front->value; +} + +template ForwardList::const_reference ForwardList::front() const { + assert(!empty()); + return _front->value; +} + +template void ForwardList::push_front(const T &value) { + Node *new_node; + if (_spare != nullptr) { + new_node = _spare; + _spare = _spare->next; + } else { + new_node = _heap->alloc(); + } + new_node->next = _front; + new_node->value = value; + _front = new_node; +} + +template void ForwardList::push_front(T &&value) { + Node *new_node; + if (_spare != nullptr) { + new_node = _spare; + _spare = _spare->previous; + } else { + new_node = _heap->alloc(); + } + new_node->next = _front; + new_node->value = std::move(value); + _front = new_node; +} + +template template void ForwardList::emplace_front(Args &&...args) { + Node *new_node; + if (_spare != nullptr) { + new_node = _spare; + _spare = _spare->next; + } else { + new_node = _heap->alloc(); + } + new_node->next = _front; + new (&new_node->value) T(args...); + _front = new_node; +} + +template void ForwardList::pop_front() { + if (_front == nullptr) { + return; + } + + Node *next = _front->next; + T value = _front->value; + + _front->next = _spare; + _spare = _front; + + _front = next; +} + +#ifdef SWIFT_TESTING + +class UInt64ForwardList : public ForwardList { + public: + static UInt64ForwardList *create(); + static void destroy(UInt64ForwardList *value); + + bool empty() const noexcept; + + uint64_t front(); + + void push_front(const uint64_t &element); + void push_front(uint64_t &&element); + + void pop_front(); + +} SWIFT_UNSAFE_REFERENCE; + +UInt64ForwardList *UInt64ForwardList::create() { return new UInt64ForwardList(); } + +void UInt64ForwardList::destroy(UInt64ForwardList *value) { delete value; } + +bool UInt64ForwardList::empty() const noexcept { return ForwardList::empty(); } + +uint64_t UInt64ForwardList::front() { return ForwardList::front(); } + +void UInt64ForwardList::push_front(const uint64_t &element) { ForwardList::push_front(element); } + +void UInt64ForwardList::push_front(uint64_t &&element) { ForwardList::push_front(element); } + +void UInt64ForwardList::pop_front() { ForwardList::pop_front(); } + +#endif + +} /* namespace util */ + +OAG_ASSUME_NONNULL_END + +#endif /* OPENATTRIBUTEGRAPH_CXX_UTIL_FORWARD_LIST_HPP */ diff --git a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/HashTable.hpp b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/HashTable.hpp new file mode 100644 index 00000000..374e9074 --- /dev/null +++ b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/HashTable.hpp @@ -0,0 +1,133 @@ +// +// HashTable.hpp +// OpenAttributeGraphCxx +// +// Status: Complete +// Modified based Compute code + +#ifndef OPENATTRIBUTEGRAPH_CXX_UTIL_HASHTABLE_HPP +#define OPENATTRIBUTEGRAPH_CXX_UTIL_HASHTABLE_HPP + +#include +#include + +OAG_ASSUME_NONNULL_BEGIN + +namespace util { + +class Heap; + +uint64_t string_hash(char const *str); + +class UntypedTable { +public: + using key_type = const void *_Nonnull; + using nullable_key_type = const void *_Nullable; + using value_type = const void *_Nullable; + using size_type = uint64_t; + using hasher = uint64_t (*)(void const *); + using key_equal = bool (*)(void const *, void const *); + using key_callback = void (*)(const key_type); + using value_callback = void (*)(const value_type); + using entry_callback = void (*)(const key_type, const value_type, void *context); + +private: + struct HashNode { + HashNode *next; + key_type key; + value_type value; + uint64_t hash_value; + }; + using Bucket = HashNode *_Nonnull; + + hasher _hash; + key_equal _compare; + key_callback _did_remove_key; + value_callback _did_remove_value; + Heap *_heap; + HashNode *_spare_node; + Bucket *_Nonnull _buckets; + uint64_t _count; + uint64_t _bucket_mask; + uint32_t _bucket_mask_width; + bool _is_heap_owner; + bool _compare_by_pointer; + + // Managing buckets + void create_buckets(); + void grow_buckets(); + +public: + static UntypedTable *create(); + static void destroy(UntypedTable *value); + + UntypedTable(); + UntypedTable(hasher _Nullable custom_hasher, key_equal _Nullable custom_compare, + key_callback _Nullable did_remove_key, value_callback _Nullable did_remove_value, + Heap *_Nullable heap); + ~UntypedTable(); + + // non-copyable + UntypedTable(const UntypedTable &) = delete; + UntypedTable &operator=(const UntypedTable &) = delete; + + // non-movable + UntypedTable(UntypedTable &&) = delete; + UntypedTable &operator=(UntypedTable &&) = delete; + + // Lookup + bool empty() const noexcept { return _count == 0; }; + size_type count() const noexcept { return _count; }; + value_type lookup(key_type key, nullable_key_type *_Nullable found_key) const noexcept; + void for_each(entry_callback body, void *context) const; + + // Modifiers + bool insert(const key_type key, const value_type value); + bool remove(const key_type key); + bool remove_ptr(const key_type key); +} SWIFT_UNSAFE_REFERENCE; + +template class Table : public UntypedTable { +public: + using key_type = Key; + using value_type = Value; + using hasher = uint64_t (*)(const key_type); + using key_equal = bool (*)(const key_type, const key_type); + using key_callback = void (*)(const key_type); + using value_callback = void (*)(const value_type); + using entry_callback = void (*)(const key_type, const value_type, void *context); + + Table() : UntypedTable() {}; + Table(hasher _Nullable custom_hasher, key_equal _Nullable custom_compare, key_callback _Nullable did_remove_key, + value_callback _Nullable did_remove_value, Heap *_Nullable heap) + : UntypedTable(reinterpret_cast(custom_hasher), + reinterpret_cast(custom_compare), + reinterpret_cast(did_remove_key), + reinterpret_cast(did_remove_value), heap) {}; + + // Lookup + + value_type lookup(const key_type key, key_type *_Nullable found_key) const noexcept { + auto result = UntypedTable::lookup(*(void **)&key, + reinterpret_cast(found_key)); + return *(value_type *)&result; + }; + + void for_each(entry_callback _Nonnull body, void *_Nullable context) const { + UntypedTable::for_each((UntypedTable::entry_callback)body, context); + }; + + // Modifying entries + + bool insert(const key_type key, const value_type value) { + return UntypedTable::insert(*(void **)&key, *(void **)&value); + }; + bool remove(const key_type key) { return UntypedTable::remove(*(void **)&key); }; + bool remove_ptr(const key_type key) { return UntypedTable::remove_ptr(key); }; +}; + +} /* namespace util */ + +OAG_ASSUME_NONNULL_END + +#endif /* OPENATTRIBUTEGRAPH_CXX_UTIL_HASHTABLE_HPP */ diff --git a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/Heap.hpp b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/Heap.hpp new file mode 100644 index 00000000..1e863cf8 --- /dev/null +++ b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/Heap.hpp @@ -0,0 +1,75 @@ +// +// Heap.hpp +// OpenAttributeGraphCxx +// +// Status: Complete +// Modified based Compute code + +#ifndef OPENATTRIBUTEGRAPH_CXX_UTIL_HEAP_HPP +#define OPENATTRIBUTEGRAPH_CXX_UTIL_HEAP_HPP + +#include +#include + +OAG_ASSUME_NONNULL_BEGIN + +namespace util { + +class Heap { +protected: + typedef struct Node { + struct Node *next; + void *buffer; + } Node; + + size_t _increment; + Node *_node; + char *_free_start; + size_t _capacity; + + void *alloc_(size_t arg1); + +public: + static constexpr size_t minimum_increment = 0x400; + + static Heap *create(char *_Nullable start, size_t capacity, size_t increment); + static void destroy(Heap *value); + + Heap(char *_Nullable start, size_t capacity, size_t increment); + ~Heap(); + + // non-copyable + Heap(const Heap &) = delete; + Heap &operator=(const Heap &) = delete; + + // non-movable + Heap(Heap &&) = delete; + Heap &operator=(Heap &&) = delete; + + template inline T *_Nonnull alloc(size_t count = 1) { + return static_cast(alloc_(sizeof(T) * count)); + }; + void reset(char *_Nullable start, size_t capacity); + + // Debugging + + size_t num_nodes() const; + size_t increment() const { return _increment; } + size_t capacity() const { return _capacity; } + + void print() const; +} SWIFT_UNSAFE_REFERENCE; + +template class InlineHeap : public Heap { +private: + char _inline_buffer[_inline_size] = {}; + +public: + InlineHeap() : Heap(_inline_buffer, _inline_size, 0) {} +}; + +} /* namespace util */ + +OAG_ASSUME_NONNULL_END + +#endif /* OPENATTRIBUTEGRAPH_CXX_UTIL_HEAP_HPP */ diff --git a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/cf_ptr.hpp b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/cf_ptr.hpp index 91e914dc..3cedb0a9 100644 --- a/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/cf_ptr.hpp +++ b/Sources/OpenAttributeGraphCxx/include/OpenAttributeGraphCxx/Util/cf_ptr.hpp @@ -97,8 +97,10 @@ template class cf_ptr { explicit operator bool() const OAG_NOEXCEPT { return _storage != nullptr; } }; /* class cf_ptr */ +#ifdef SWIFT_TESTING using cf_data_ptr = cf_ptr; using cf_mutable_data_ptr = cf_ptr; +#endif /* SWIFT_TESTING */ } /* namespace util */ diff --git a/Sources/OpenAttributeGraphShims/DebugClient.swift b/Sources/OpenAttributeGraphShims/DebugClient.swift index 0afe23dd..7671ced8 100644 --- a/Sources/OpenAttributeGraphShims/DebugClient.swift +++ b/Sources/OpenAttributeGraphShims/DebugClient.swift @@ -31,7 +31,7 @@ public final class DebugClient { } private var connection: NWConnection? - private let queue = DispatchQueue(label: "org.openswiftuiproject.opengraph.debugclient") + private let queue = DispatchQueue(label: "org.openswiftuiproject.openattributegraph.debugclient") public init() {} diff --git a/Tests/OpenAttributeGraphCxxTests/Util/ForwardListTests.swift b/Tests/OpenAttributeGraphCxxTests/Util/ForwardListTests.swift new file mode 100644 index 00000000..d29ae5f6 --- /dev/null +++ b/Tests/OpenAttributeGraphCxxTests/Util/ForwardListTests.swift @@ -0,0 +1,93 @@ +// +// ForwardListTests.swift +// OpenAttributeGraphCxxTests + +import OpenAttributeGraphCxx_Private.Util +import Testing + +@Suite("List tests") +struct ForwardListTests { + + @Test("Initialize empty list") + @available(iOS 16.4, *) + func initEmpty() { + let list = util.UInt64ForwardList.create() + defer { + util.UInt64ForwardList.destroy(list) + } + + #expect(list.empty()) + } + + @Test("Push element") + @available(iOS 16.4, *) + func pushElement() { + let list = util.UInt64ForwardList.create() + defer { + util.UInt64ForwardList.destroy(list) + } + + list.push_front(1) + + #expect(list.empty() == false) + + let front = list.front() + #expect(front == 1) + } + + @Test("Push multiple elements") + @available(iOS 16.4, *) + func pushMultipleElements() { + let list = util.UInt64ForwardList.create() + defer { + util.UInt64ForwardList.destroy(list) + } + + list.push_front(1) + list.push_front(2) + list.push_front(3) + + let front = list.front() + #expect(front == 3) + } + + @Test("Remove element") + @available(iOS 16.4, *) + func removeElement() { + let list = util.UInt64ForwardList.create() + defer { + util.UInt64ForwardList.destroy(list) + } + + list.push_front(1) + list.push_front(2) + list.push_front(3) + list.pop_front() + + let front = list.front() + #expect(front == 2) + } + + @Test("Sequential operations") + @available(iOS 16.4, *) + func sequentialOperations() { + let list = util.UInt64ForwardList.create() + defer { + util.UInt64ForwardList.destroy(list) + } + + // Add multiple elements + for i in 0..<5 { + list.push_front(UInt64(i)) + } + + // Remove all elements and verify order (LIFO - last in, first out) + for i in (0..<5).reversed() { + #expect(!list.empty()) + #expect(list.front() == UInt64(i)) + list.pop_front() + } + + #expect(list.empty()) + } +} diff --git a/Tests/OpenAttributeGraphCxxTests/Util/HashTableTests.swift b/Tests/OpenAttributeGraphCxxTests/Util/HashTableTests.swift new file mode 100644 index 00000000..842029de --- /dev/null +++ b/Tests/OpenAttributeGraphCxxTests/Util/HashTableTests.swift @@ -0,0 +1,161 @@ +// +// HashTableTests.swift +// OpenAttributeGraphCxxTests + +import OpenAttributeGraphCxx_Private.Util +import Testing + +@Suite("HashTable tests") +struct HashTableTests { + + @Test("Initialize empty table") + @available(iOS 16.4, *) + func initEmpty() { + let table = util.UntypedTable.create() + defer { + util.UntypedTable.destroy(table) + } + + #expect(table.empty()) + #expect(table.count() == 0) + } + + @Test("Insert entry") + @available(iOS 16.4, *) + func insertEntry() { + class Value { + let prop: String + init(prop: String) { + self.prop = prop + } + } + + let table = util.UntypedTable.create() + defer { + util.UntypedTable.destroy(table) + } + + let key = "key1" + let value = Value(prop: "valueProp") + withUnsafePointer(to: key) { keyPointer in + withUnsafePointer(to: value) { valuePointer in + let inserted = table.insert(keyPointer, valuePointer) + + #expect(inserted == true) + #expect(!table.empty()) + #expect(table.count() == 1) + + // Verify the value can be found + let foundValue = table.__lookupUnsafe(keyPointer, nil) + #expect(foundValue?.assumingMemoryBound(to: Value.self).pointee.prop == "valueProp") + } + } + } + + @Test("Insert multiple entries") + @available(iOS 16.4, *) + func insertMultipleEntries() { + class Value { + let prop: String + init(prop: String) { + self.prop = prop + } + } + + let table = util.UntypedTable.create() + defer { + util.UntypedTable.destroy(table) + } + + // Use manually allocated pointers to ensure unique addresses + let key1Ptr = UnsafeMutablePointer.allocate(capacity: 1) + let key2Ptr = UnsafeMutablePointer.allocate(capacity: 1) + let key3Ptr = UnsafeMutablePointer.allocate(capacity: 1) + + defer { + key1Ptr.deallocate() + key2Ptr.deallocate() + key3Ptr.deallocate() + } + + key1Ptr.pointee = 1 + key2Ptr.pointee = 2 + key3Ptr.pointee = 3 + + let value1 = Value(prop: "value1") + let value2 = Value(prop: "value2") + let value3 = Value(prop: "value3") + + // Insert entries using unique heap-allocated pointers as keys + withUnsafePointer(to: value1) { valuePointer in + let inserted = table.insert(key1Ptr, valuePointer) + #expect(inserted == true) + } + + withUnsafePointer(to: value2) { valuePointer in + let inserted = table.insert(key2Ptr, valuePointer) + #expect(inserted == true) + } + + withUnsafePointer(to: value3) { valuePointer in + let inserted = table.insert(key3Ptr, valuePointer) + #expect(inserted == true) + } + + #expect(!table.empty()) + #expect(table.count() == 3) + } + + @Test("Remove entry") + @available(iOS 16.4, *) + func removeEntry() { + class Value { + let prop: String + init(prop: String) { + self.prop = prop + } + } + + let table = util.UntypedTable.create() + defer { + util.UntypedTable.destroy(table) + } + + let key = "key1" + let value = Value(prop: "valueProp") + withUnsafePointer(to: key) { keyPointer in + withUnsafePointer(to: value) { valuePointer in + let inserted = table.insert(keyPointer, valuePointer) + + try! #require(inserted == true) + try! #require(table.count() == 1) + + let removed = table.remove(keyPointer) + + #expect(removed == true) + #expect(table.count() == 0) + #expect(table.empty()) + + // Verify the value can no longer be found + let foundValue = table.__lookupUnsafe(keyPointer, nil) + #expect(foundValue == nil) + } + } + } + + @Test("Remove from empty table") + @available(iOS 16.4, *) + func removeFromEmptyTable() { + let table = util.UntypedTable.create() + defer { + util.UntypedTable.destroy(table) + } + + let key = "nonexistent" + withUnsafePointer(to: key) { keyPointer in + let removed = table.remove(keyPointer) + #expect(removed == false) + #expect(table.count() == 0) + } + } +} diff --git a/Tests/OpenAttributeGraphCxxTests/Util/HeapTests.swift b/Tests/OpenAttributeGraphCxxTests/Util/HeapTests.swift new file mode 100644 index 00000000..02ca9676 --- /dev/null +++ b/Tests/OpenAttributeGraphCxxTests/Util/HeapTests.swift @@ -0,0 +1,91 @@ +// +// HeapTests.swift +// OpenAttributeGraphCxxTests + +import OpenAttributeGraphCxx_Private.Util +import Testing + +@Suite("Heap tests") +struct HeapTests { + + let nodeSize = 16 + + @Test("Initializing with default arguments") + @available(iOS 16.4, *) + func initDefault() { + let heap = util.Heap.create(nil, 0, 0) + defer { + util.Heap.destroy(heap) + } + + #expect(heap.capacity() == 0) + #expect(heap.increment() == 0x2000) + #expect(heap.num_nodes() == 0) + } + + @Test("Initializing with custom increment") + @available(iOS 16.4, *) + func initWithCustomIncrement() { + let customIncrement = 4096 + let heap = util.Heap.create(nil, 0, customIncrement) + defer { + util.Heap.destroy(heap) + } + + #expect(heap.capacity() == 0) + #expect(heap.increment() == customIncrement) + #expect(heap.num_nodes() == 0) + } + + @Test("Creating heap with initial buffer") + @available(iOS 16.4, *) + func createWithInitialBuffer() { + var buffer = Array(repeating: 0, count: 1024) + + let heap = buffer.withUnsafeMutableBytes { bufferPtr in + let charPtr = bufferPtr.bindMemory(to: CChar.self) + return util.Heap.create(charPtr.baseAddress, 1024, 2048) + } + defer { + util.Heap.destroy(heap) + } + + #expect(heap.capacity() == 1024) + #expect(heap.increment() == 2048) + #expect(heap.num_nodes() == 0) + } + + @Test("Reset heap functionality") + @available(iOS 16.4, *) + func resetHeap() { + let heap = util.Heap.create(nil, 0, 1024) + defer { + util.Heap.destroy(heap) + } + + // Reset heap to different configuration + heap.reset(nil, 0) + + // Verify heap properties after reset + #expect(heap.increment() == 1024) + #expect(heap.capacity() == 0) + #expect(heap.num_nodes() == 0) + } + + @Test("Print heap debug information") + @available(iOS 16.4, *) + func printHeapInfo() { + let heap = util.Heap.create(nil, 0, 1024) + defer { + util.Heap.destroy(heap) + } + + // Test that print method can be called without crashing + heap.print() + + // Verify heap is still functional after print + #expect(heap.increment() == 1024) + #expect(heap.num_nodes() == 0) + #expect(heap.capacity() == 0) + } +}