From 9ba92780a90cde1de75df993ebe5951376a9f1e9 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Tue, 13 Jan 2026 11:30:39 +0100 Subject: [PATCH 1/5] Remove swiftCheckoutPath from CompatibilityTesting/Package.swift --- CompatibilityTesting/Package.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/CompatibilityTesting/Package.swift b/CompatibilityTesting/Package.swift index 87371d3..34b0949 100644 --- a/CompatibilityTesting/Package.swift +++ b/CompatibilityTesting/Package.swift @@ -2,8 +2,6 @@ import PackageDescription -let swiftCheckoutPath = "\(Context.packageDirectory)/Checkouts/swift" - var dependencies: [Package.Dependency] = [ .package(name: "Compute", path: ".."), .package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"), From 2bcc284f07d32c80d1aadfc9275c3cff54f61592 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Fri, 16 Jan 2026 11:57:22 +0100 Subject: [PATCH 2/5] Fix C++ bug --- Sources/ComputeCxx/Comparison/LayoutDescriptor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComputeCxx/Comparison/LayoutDescriptor.h b/Sources/ComputeCxx/Comparison/LayoutDescriptor.h index c84ffe6..7349777 100644 --- a/Sources/ComputeCxx/Comparison/LayoutDescriptor.h +++ b/Sources/ComputeCxx/Comparison/LayoutDescriptor.h @@ -29,7 +29,7 @@ enum class HeapMode : uint16_t { GenericLocals = 1 << 2, }; inline bool operator&(HeapMode a, HeapMode b) { return (uint16_t)a & (uint16_t)b; } -inline HeapMode operator|(HeapMode a, HeapMode b) { return a | b; } +inline HeapMode operator|(HeapMode a, HeapMode b) { return (HeapMode)((uint16_t)a | (uint16_t)b); } extern unsigned char base_address; From 52a20a5955672f05635c8f04a484cb1e04003c86 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Thu, 15 Jan 2026 00:14:31 +0100 Subject: [PATCH 3/5] Fix mismatched method signature --- Sources/Compute/Attribute/RuleContext/RuleContext.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/Compute/Attribute/RuleContext/RuleContext.swift b/Sources/Compute/Attribute/RuleContext/RuleContext.swift index cb739da..b38cc5c 100644 --- a/Sources/Compute/Attribute/RuleContext/RuleContext.swift +++ b/Sources/Compute/Attribute/RuleContext/RuleContext.swift @@ -6,11 +6,8 @@ public struct RuleContext { self.attribute = attribute } - @_extern(c, "AGGraphWithUpdate") - private static func withUpdate(_ attribute: AnyAttribute, body: @escaping () -> Void) - - public func update(body: @escaping () -> Void) { - RuleContext.withUpdate(attribute.identifier, body: body) + public func update(body: () -> Void) { + AnyRuleContext(attribute: attribute.identifier).update(body: body) } public var value: Value { From 44e2f1f659db67ea56df0f7d3e03afb542ab88ee Mon Sep 17 00:00:00 2001 From: James Moschou Date: Sat, 17 Jan 2026 16:53:07 +0100 Subject: [PATCH 4/5] Add node cache --- Sources/Compute/Attribute/Rule/Rule.swift | 66 ++++++- .../Attribute/AttributeData/Node/Node.h | 5 +- Sources/ComputeCxx/Closure/ClosureFunction.h | 5 + Sources/ComputeCxx/Graph/AGGraph.cpp | 78 ++++++++ Sources/ComputeCxx/Graph/Graph.cpp | 21 +- Sources/ComputeCxx/Graph/Graph.h | 5 +- Sources/ComputeCxx/Subgraph/NodeCache.cpp | 52 +++++ Sources/ComputeCxx/Subgraph/NodeCache.h | 71 +++++++ Sources/ComputeCxx/Subgraph/Subgraph.cpp | 179 ++++++++++++++++++ Sources/ComputeCxx/Subgraph/Subgraph.h | 49 ++++- .../include/ComputeCxx/AGCachedValueOptions.h | 16 ++ .../ComputeCxx/include/ComputeCxx/AGGraph.h | 20 +- .../include/ComputeCxx/ComputeCxx.h | 1 + 13 files changed, 550 insertions(+), 18 deletions(-) create mode 100644 Sources/ComputeCxx/Subgraph/NodeCache.cpp create mode 100644 Sources/ComputeCxx/Subgraph/NodeCache.h create mode 100644 Sources/ComputeCxx/include/ComputeCxx/AGCachedValueOptions.h diff --git a/Sources/Compute/Attribute/Rule/Rule.swift b/Sources/Compute/Attribute/Rule/Rule.swift index 4875e3e..8af50a7 100644 --- a/Sources/Compute/Attribute/Rule/Rule.swift +++ b/Sources/Compute/Attribute/Rule/Rule.swift @@ -22,7 +22,6 @@ extension Rule { withUnsafePointer(to: initialValue) { initialValuePointer in Graph.setOutputValue(initialValuePointer) } - } public static func _update(_ self: UnsafeMutableRawPointer, attribute: AnyAttribute) { @@ -51,16 +50,50 @@ extension Rule { } -public struct CachedValueOptions {} +extension Graph { + @_extern(c, "AGGraphReadCachedAttribute") + static func readCachedAttribute( + hash: Int, + type: Metadata, + body: UnsafeRawPointer, + valueType: Metadata, + options: CachedValueOptions, + owner: AnyAttribute, + changed: UnsafeMutablePointer?, + attributeTypeID: (AGUnownedGraphContextRef) -> UInt32 + ) -> UnsafeRawPointer +} -extension Rule where Value: Hashable { +extension Rule where Self: Hashable { public func cachedValue(options: CachedValueOptions, owner: AnyAttribute?) -> Value { - fatalError("not implemented") + return withUnsafePointer(to: self) { bodyPointer in + Self._cachedValue( + options: options, + owner: owner, + hashValue: hashValue, + bodyPtr: bodyPointer, + update: { Self._update } + ).pointee + } } public func cachedValueIfExists(options: CachedValueOptions, owner: AnyAttribute?) -> Value? { - fatalError("not implemented") + return withUnsafePointer(to: self) { bodyPointer in + let value = __AGGraphReadCachedAttributeIfExists( + hashValue, + Metadata(Self.self), + bodyPointer, + Metadata(Value.self), + options, + owner ?? .nil, + nil + ) + guard let value else { + return nil + } + return value.assumingMemoryBound(to: Value.self).pointee + } } public static func _cachedValue( @@ -70,7 +103,28 @@ extension Rule where Value: Hashable { bodyPtr: UnsafeRawPointer, update: () -> (UnsafeMutableRawPointer, AnyAttribute) -> Void ) -> UnsafePointer { - fatalError("not implemented") + let value = Graph.readCachedAttribute( + hash: hashValue, + type: Metadata(Self.self), + body: bodyPtr, + valueType: Metadata(Value.self), + options: options, + owner: owner ?? .nil, + changed: nil + ) { graph in + return graph.internAttributeType(type: Metadata(Self.self)) { + let attributeType = _AttributeType( + selfType: Self.self, + valueType: Value.self, + flags: [], // TODO: check flags are empty + update: update() + ) + let pointer = UnsafeMutablePointer<_AttributeType>.allocate(capacity: 1) + pointer.initialize(to: attributeType) + return UnsafePointer(pointer) + } + } + return value.assumingMemoryBound(to: Value.self) } } diff --git a/Sources/ComputeCxx/Attribute/AttributeData/Node/Node.h b/Sources/ComputeCxx/Attribute/AttributeData/Node/Node.h index 7ae0bd7..1155a3c 100644 --- a/Sources/ComputeCxx/Attribute/AttributeData/Node/Node.h +++ b/Sources/ComputeCxx/Attribute/AttributeData/Node/Node.h @@ -53,7 +53,7 @@ class Node { unsigned int _has_indirect_value : 1 = 0; unsigned int _input_edges_traverse_contexts : 1 = 0; unsigned int _needs_sort_input_edges : 1 = 0; - unsigned int _cacheable : 1 = 0; + unsigned int _cached : 1 = 0; unsigned int _main_ref : 1 = 0; unsigned int _self_modified : 1 = 0; @@ -143,6 +143,9 @@ class Node { } } + bool is_cached() const { return _cached; } + void set_cached(bool value) { _cached = value; } + bool is_main_ref() const { return _main_ref; } void set_main_ref(bool value) { _main_ref = value; } diff --git a/Sources/ComputeCxx/Closure/ClosureFunction.h b/Sources/ComputeCxx/Closure/ClosureFunction.h index 5586555..a7b37cc 100644 --- a/Sources/ComputeCxx/Closure/ClosureFunction.h +++ b/Sources/ComputeCxx/Closure/ClosureFunction.h @@ -16,6 +16,7 @@ template class ClosureFunction { Context _context; public: + inline ClosureFunction(std::nullptr_t): _function(nullptr), _context(nullptr) {} inline ClosureFunction(Function function, Context context) noexcept : _function(function), _context(context) { void *mutable_context = const_cast(_context); ::swift::swift_retain(reinterpret_cast<::swift::HeapObject *>(mutable_context)); @@ -94,4 +95,8 @@ template requires std::same_as using ClosureFunctionPV = ClosureFunction; +template + requires std::unsigned_integral +using ClosureFunctionCI = ClosureFunction; + } // namespace AG diff --git a/Sources/ComputeCxx/Graph/AGGraph.cpp b/Sources/ComputeCxx/Graph/AGGraph.cpp index 8a39725..8ba4a68 100644 --- a/Sources/ComputeCxx/Graph/AGGraph.cpp +++ b/Sources/ComputeCxx/Graph/AGGraph.cpp @@ -639,6 +639,84 @@ void AGGraphSetInvalidationCallback(AGGraphRef graph, graph_context->set_invalidation_callback(AG::ClosureFunctionAV(callback, callback_context)); } +#pragma mark - Cached value + +namespace { + +void *read_cached_attribute(size_t hash, const AG::swift::metadata &metadata, const void *body, + const AG::swift::metadata &value_metadata, AGCachedValueOptions options, + AG::AttributeID owner_id, AGChangedValueFlags *flags_out, + AG::ClosureFunctionCI get_attribute_type_id) { + auto update = AG::Graph::current_update(); + auto update_stack = update.tag() == 0 ? update.get() : nullptr; + + AG::Subgraph *subgraph = nullptr; + if (owner_id && !owner_id.is_nil()) { + owner_id.validate_data_offset(); + subgraph = owner_id.subgraph(); + } else { + if (update_stack != nullptr) { + subgraph = AG::AttributeID(update_stack->frames().back().attribute).subgraph(); + } else { + subgraph = AG::Subgraph::current_subgraph(); + } + } + if (subgraph == nullptr) { + AG::precondition_failure("no subgraph"); + } + + AG::data::ptr cached_node = subgraph->cache_fetch(hash, metadata, body, get_attribute_type_id); + if (cached_node == nullptr) { + return nullptr; + } + + if (update_stack == nullptr) { + void *value = subgraph->graph()->value_ref(AG::AttributeID(cached_node), 0, value_metadata, flags_out); + subgraph->cache_insert(cached_node); // TODO: when this becomes an input, is it removed from cache? + return value; + } + + AGInputOptions input_options = options & AGCachedValueOptionsUnprefetched ? AGInputOptionsUnprefetched : AGInputOptionsNone; + return subgraph->graph()->input_value_ref(update_stack->frames().back().attribute, AG::AttributeID(cached_node), 0, + input_options, value_metadata, flags_out); +} + +} // namespace + +void *AGGraphReadCachedAttribute(size_t hash, AGTypeID type, const void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute owner, bool *_Nullable changed_out, + uint32_t (*closure)(const void *context AG_SWIFT_CONTEXT, + AGUnownedGraphContextRef graph_context) AG_SWIFT_CC(swift), + const void *closure_context) { + auto metadata = reinterpret_cast(type); + auto value_metadata = reinterpret_cast(value_type); + auto owner_id = AG::AttributeID(owner); + + AGChangedValueFlags flags = 0; + void *value = + read_cached_attribute(hash, *metadata, body, *value_metadata, options, owner_id, &flags, + AG::ClosureFunctionCI(closure, closure_context)); + if (changed_out) { + *changed_out = flags & AGChangedValueFlagsChanged ? true : false; + } + return value; +} + +void *AGGraphReadCachedAttributeIfExists(size_t hash, AGTypeID type, const void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute owner, bool *_Nullable changed_out) { + + auto metadata = reinterpret_cast(type); + auto value_metadata = reinterpret_cast(value_type); + auto owner_id = AG::AttributeID(owner); + + AGChangedValueFlags flags = 0; + void *value = read_cached_attribute(hash, *metadata, body, *value_metadata, options, owner_id, &flags, nullptr); + if (changed_out) { + *changed_out = flags & AGChangedValueFlagsChanged ? true : false; + } + return value; +} + #pragma mark - Update void AGGraphSetUpdate(const void *update) { diff --git a/Sources/ComputeCxx/Graph/Graph.cpp b/Sources/ComputeCxx/Graph/Graph.cpp index 38174b6..ec700b5 100644 --- a/Sources/ComputeCxx/Graph/Graph.cpp +++ b/Sources/ComputeCxx/Graph/Graph.cpp @@ -40,7 +40,7 @@ Graph::Graph() _contexts_by_id(nullptr, nullptr, nullptr, nullptr, &_heap), _id(AGMakeUniqueID()) { static platform_once_t make_keys; - platform_once(&make_keys, []() { + platform_once(&make_keys, []() { pthread_key_create(&Graph::_current_update_key, 0); Subgraph::make_current_subgraph_key(); }); @@ -188,6 +188,12 @@ void Graph::remove_subgraph(Subgraph &subgraph) { } } + if (subgraph.has_cached_nodes()) { + subgraph.set_has_cached_nodes(false); + auto iter = std::remove(_subgraphs_with_cached_nodes.begin(), _subgraphs_with_cached_nodes.end(), &subgraph); + _subgraphs_with_cached_nodes.erase(iter); + } + _num_subgraphs -= 1; } @@ -208,6 +214,19 @@ void Graph::invalidate_subgraphs() { } if (_main_handler == nullptr) { + auto iter = _subgraphs_with_cached_nodes.begin(), end = _subgraphs_with_cached_nodes.end(); + while (iter != end) { + auto subgraph = *iter; + subgraph->set_graph_invalidating_subgraphs(true); + subgraph->cache_collect(); + subgraph->set_graph_invalidating_subgraphs(false); + + if (!subgraph->has_cached_nodes()) { + end = _subgraphs_with_cached_nodes.erase(iter); + } else { + ++iter; + } + } while (!_invalidating_subgraphs.empty()) { auto subgraph = _invalidating_subgraphs.back(); _invalidating_subgraphs.pop_back(); diff --git a/Sources/ComputeCxx/Graph/Graph.h b/Sources/ComputeCxx/Graph/Graph.h index 3921e84..2e7cf75 100644 --- a/Sources/ComputeCxx/Graph/Graph.h +++ b/Sources/ComputeCxx/Graph/Graph.h @@ -105,6 +105,7 @@ class Graph { // Subgraphs vector _subgraphs; + vector _subgraphs_with_cached_nodes; vector _invalidating_subgraphs; bool _deferring_subgraph_invalidation; @@ -138,7 +139,6 @@ class Graph { void remove_input(data::ptr node, uint32_t index); void remove_input_edge(data::ptr node_ptr, Node &node, uint32_t index); - void remove_all_inputs(data::ptr node); void all_inputs_removed(data::ptr node); template void add_output_edge(data::ptr node, AttributeID output); @@ -236,6 +236,8 @@ class Graph { void add_subgraph(Subgraph &subgraph); void remove_subgraph(Subgraph &subgraph); + + void add_subgraphs_with_cached_nodes(Subgraph &subgraph) { _subgraphs_with_cached_nodes.push_back(&subgraph); }; class without_invalidating { private: @@ -292,6 +294,7 @@ class Graph { void remove_indirect_node(data::ptr node); uint32_t add_input(data::ptr node, AttributeID input, bool allow_nil, AGInputOptions options); + void remove_all_inputs(data::ptr node); void indirect_attribute_set(data::ptr indirect_node, AttributeID source); bool indirect_attribute_reset(data::ptr indirect_node, bool non_nil); diff --git a/Sources/ComputeCxx/Subgraph/NodeCache.cpp b/Sources/ComputeCxx/Subgraph/NodeCache.cpp new file mode 100644 index 0000000..b3ac073 --- /dev/null +++ b/Sources/ComputeCxx/Subgraph/NodeCache.cpp @@ -0,0 +1,52 @@ +#include "NodeCache.h" + +#include "Swift/SwiftShims.h" + +namespace AG { + +Subgraph::NodeCache::NodeCache() noexcept + : _heap(nullptr, 0, 0), _types(nullptr, nullptr, nullptr, nullptr, &_heap), + _items([](const ItemKey *item_key) -> uint64_t { return item_key->hash_and_age >> 8; }, + [](const ItemKey *a, const ItemKey *b) -> bool { + if (a == b) { + return true; + } + + if (a->type != b->type || (a->hash_and_age ^ b->hash_and_age) > 0xff) { + return false; + } + + const void *a_body = nullptr; + if (a->node) { + data::ptr a_node = a->node; + auto a_attribute_type = AttributeID(a_node).subgraph()->graph()->attribute_type(a_node->type_id()); + a_body = a->node->get_self(a_attribute_type); + } else { + a_body = a->body; + } + + const void *b_body = nullptr; + if (b->node) { + data::ptr b_node = b->node; + auto b_attribute_type = AttributeID(b_node).subgraph()->graph()->attribute_type(b_node->type_id()); + b_body = b->node->get_self(b_attribute_type); + } else { + b_body = b->body; + } + + return AGDispatchEquatable(a_body, b_body, a->type->type, a->type->equatable); + }, + nullptr, nullptr, &_heap), + _items_by_node(nullptr, nullptr, nullptr, nullptr, &_heap) {} + +Subgraph::NodeCache::~NodeCache() noexcept { + _items_by_node.for_each( + [](data::ptr node, Item *item, void *context) { + if (item) { + delete item; + } + }, + nullptr); +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Subgraph/NodeCache.h b/Sources/ComputeCxx/Subgraph/NodeCache.h new file mode 100644 index 0000000..cb61da3 --- /dev/null +++ b/Sources/ComputeCxx/Subgraph/NodeCache.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include + +#include "Subgraph.h" + +AG_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Subgraph::NodeCache { + public: + struct Item; + struct Type { + const swift::metadata *type; + const swift::equatable_witness_table *equatable; + Item *mru; + Item *lru; + uint32_t type_id; + }; + static_assert(sizeof(Type) == 40); + struct Item { + uint64_t hash_and_age; + data::ptr type; + data::ptr node; + Item *next; + Item *prev; + uint8_t age() { return hash_and_age & 0xff; } + void increment_age() { hash_and_age += 1; } + void reset_age() { hash_and_age &= 0xffffffffffffff00; } + }; + static_assert(sizeof(Item) == 32); + struct ItemKey { + uint64_t hash_and_age; // age bits are ignored + data::ptr type; + data::ptr node; + const void *_Nullable body; + }; + + private: + platform_lock _lock; // can't find how this is used.. + util::Heap _heap; + util::Table> _types; + util::Table _items; + + // allocated items + util::Table, Item *> _items_by_node; + + public: + NodeCache() noexcept; + ~NodeCache() noexcept; + + // non-copyable + NodeCache(const NodeCache &) = delete; + NodeCache &operator=(const NodeCache &) = delete; + + // non-movable + NodeCache(NodeCache &&) = delete; + NodeCache &operator=(NodeCache &&) = delete; + + util::Table> &types() { return _types; }; + util::Table &items() { return _items; }; + util::Table, Item *> &items_by_node() { return _items_by_node; }; +}; + +} // namespace AG + +AG_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Subgraph/Subgraph.cpp b/Sources/ComputeCxx/Subgraph/Subgraph.cpp index de1d2a8..bc97769 100644 --- a/Sources/ComputeCxx/Subgraph/Subgraph.cpp +++ b/Sources/ComputeCxx/Subgraph/Subgraph.cpp @@ -14,6 +14,7 @@ #include "Graph/Context.h" #include "Graph/Tree/TreeElement.h" #include "Graph/UpdateStack.h" +#include "NodeCache.h" #include "Trace/Trace.h" namespace AG { @@ -51,6 +52,9 @@ Subgraph::~Subgraph() { notify_observers(); delete observers; } + if (_cache) { + _cache->~NodeCache(); + } } #pragma mark - CFType @@ -713,6 +717,181 @@ void Subgraph::update(AGAttributeFlags mask) { _graph->foreach_trace([this](Trace &trace) { trace.end_update(*this); }); } +#pragma mark - Cache + +// age = 0x00: recently fetched, in items(), removed from recycle list +// age = 0x01: recently inserted, in items(), inserted in recycle list as mru +// age = 0xff: collected, removed from items(), in recycle list + +data::ptr Subgraph::cache_fetch(size_t hash, const swift::metadata &metadata, const void *body, + ClosureFunctionCI get_attribute_type_id) { + if (_cache == nullptr) { + _cache = alloc_bytes(sizeof(NodeCache), 7).unsafe_cast(); + new (_cache.get()) NodeCache(); + } + + data::ptr type = _cache->types().lookup(&metadata, nullptr); + if (type == nullptr) { + auto equatable = metadata.equatable(); + if (equatable == nullptr) { + precondition_failure("cache key must be equatable: %s", metadata.name(false)); + } + + type = alloc_bytes(sizeof(NodeCache::Type), 7).unsafe_cast(); + type->type = &metadata; + type->equatable = equatable; + type->mru = nullptr; + type->lru = nullptr; + type->type_id = get_attribute_type_id(_graph); + _cache->types().insert(&metadata, type); + } + + NodeCache::ItemKey item_key = {hash << 8, type, nullptr, body}; + NodeCache::Item *item = _cache->items().lookup(&item_key, nullptr); + if (item) { + // cache hit: remove from lru (now active) + if (item->age() > 0) { + // remove item from list + if (item->next != nullptr) { + item->next->prev = item->prev; + } else { + type->lru = item->prev; + } + if (item->prev != nullptr) { + item->prev->next = item->next; + } else { + type->mru = item->next; + } + + item->reset_age(); + } + } else { + // cache miss + if (!get_attribute_type_id) { + return nullptr; + } + + // try reusing a lru item, only if it has been collected at least once to prevent trashing + if (type->lru && type->lru->age() >= 2) { + item = type->lru; + + // remove item from list + if (item->next != nullptr) { + item->next->prev = item->prev; + } else { + type->lru = item->prev; + } + if (item->prev != nullptr) { + item->prev->next = item->next; + } else { + type->mru = item->next; + } + + // remove item + if (item->age() != 0xff) { + _cache->items().remove_ptr((const NodeCache::ItemKey *)item); + } + + // reset node data + _graph->remove_all_inputs(item->node); + item->node->set_dirty(true); + item->node->set_pending(true); + item->node->update_self(*_graph, body); + item->hash_and_age = hash << 8; + } else { + data::ptr node = graph()->add_attribute(*this, type->type_id, body, nullptr); + item = new NodeCache::Item(item_key.hash_and_age, item_key.type, node, nullptr, nullptr); + node->set_cached(true); + _cache->items_by_node().insert(node, item); + } + + // reinsert in lookup map + _cache->items().insert((const NodeCache::ItemKey *)item, item); + } + + return item->node; +} + +void Subgraph::cache_insert(data::ptr node) { + if (!is_valid()) { + return; + } + + if (!node->output_edges().empty()) { + return; + } + if (node->is_updating()) { + return; + } + + if (!node->is_cached()) { + return; + } + + auto attribute_type = graph()->attribute_type(node->type_id()); + data::ptr type = _cache->types().lookup(&attribute_type.body_metadata(), nullptr); + NodeCache::Item *item = _cache->items_by_node().lookup(node, nullptr); + if (!item) { + return; // shouldn't happen + } + + if (item->age() < 0xff) { + item->increment_age(); + } + + // insert as mru + item->next = type->mru; + item->prev = nullptr; + type->mru = item; + if (item->next) { + item->next->prev = item; + } else { + type->lru = item; + } + + if (!has_cached_nodes()) { + if (!is_graph_invalidating_subgraphs()) { + _graph->add_subgraphs_with_cached_nodes(*this); + } + set_has_cached_nodes(true); + } +} + +void Subgraph::cache_collect() { + set_has_cached_nodes(false); + + if (_cache == nullptr || !is_valid()) { + return; + } + + std::pair context = {this, _cache.get()}; + _cache->types().for_each( + [](const swift::metadata *metadata, const data::ptr type, void *context) { + auto inner_context = reinterpret_cast *>(context); + Subgraph *subgraph = inner_context->first; + NodeCache *cache = inner_context->second; + + for (NodeCache::Item *item = type->mru; item != nullptr; item = item->next) { + if (item->age() == 0xff) { + // stop processing, all subsequent items will have age == 0xff too + break; + } + + item->increment_age(); + + if (item->age() == 0xff) { + cache->items().remove_ptr((const NodeCache::ItemKey *)item); + item->node->destroy_self(*subgraph->graph()); + item->node->destroy_value(*subgraph->graph()); + subgraph->graph()->remove_all_inputs(item->node); + } else { + subgraph->set_has_cached_nodes(true); + } + } + }, + &context); +} + #pragma mark - Tree void Subgraph::begin_tree(AttributeID value, const swift::metadata *type, uint32_t flags) { diff --git a/Sources/ComputeCxx/Subgraph/Subgraph.h b/Sources/ComputeCxx/Subgraph/Subgraph.h index e6504a0..873f12c 100644 --- a/Sources/ComputeCxx/Subgraph/Subgraph.h +++ b/Sources/ComputeCxx/Subgraph/Subgraph.h @@ -27,6 +27,7 @@ class SubgraphObject { class Subgraph : public data::zone { public: + class NodeCache; class SubgraphChild { private: enum { @@ -60,6 +61,7 @@ class Subgraph : public data::zone { uint32_t _traversal_seed; uint32_t _index; + data::ptr _cache = nullptr; Graph::TreeElementID _tree_root; AGAttributeFlags _flags; @@ -76,6 +78,14 @@ class Subgraph : public data::zone { InvalidationState _invalidation_state = InvalidationState::None; + enum class CacheState : uint8_t { + None = 0, + HasCachedNodes = 1, + GraphInvalidatingSubgraphs = 2, + }; + + CacheState _cache_state = CacheState::None; + // Attribute list void insert_attribute(AttributeID attribute, bool updatable); void unlink_attribute(AttributeID attribute); @@ -83,11 +93,11 @@ class Subgraph : public data::zone { public: Subgraph(SubgraphObject *object, Graph::Context &context, AttributeID attribute); ~Subgraph(); - + // Non-copyable Subgraph(const Subgraph &) = delete; Subgraph &operator=(const Subgraph &) = delete; - + // Non-movable Subgraph(Subgraph &&) = delete; Subgraph &operator=(Subgraph &&) = delete; @@ -128,9 +138,7 @@ class Subgraph : public data::zone { return _invalidation_state >= InvalidationState::Deferred && _invalidation_state <= InvalidationState::GraphDestroyed; } - bool is_invalidated() const { - return _invalidation_state == InvalidationState::Completed; - } + bool is_invalidated() const { return _invalidation_state == InvalidationState::Completed; } void invalidate_and_delete_(bool delete_zone_data); void invalidate_deferred(Graph &graph); void invalidate_now(Graph &graph); @@ -178,9 +186,34 @@ class Subgraph : public data::zone { static std::atomic _last_traversal_seed; void apply(uint32_t options, ClosureFunctionAV body); - + void update(AGAttributeFlags mask); + // MARK: Cache + + bool has_cached_nodes() const { return ((uint8_t)_cache_state & (uint8_t)CacheState::HasCachedNodes) != 0; }; + void set_has_cached_nodes(bool value) { + if (value) { + _cache_state = CacheState((uint8_t)_cache_state | (uint8_t)CacheState::HasCachedNodes); + } else { + _cache_state = CacheState((uint8_t)_cache_state & ~(uint8_t)CacheState::HasCachedNodes); + } + }; + bool is_graph_invalidating_subgraphs() const { return ((uint8_t)_cache_state & (uint8_t)CacheState::GraphInvalidatingSubgraphs) != 0; }; + void set_graph_invalidating_subgraphs(bool value) { + if (value) { + _cache_state = CacheState((uint8_t)_cache_state | (uint8_t)CacheState::GraphInvalidatingSubgraphs); + } else { + _cache_state = CacheState((uint8_t)_cache_state & ~(uint8_t)CacheState::GraphInvalidatingSubgraphs); + } + }; + + data::ptr cache_fetch(size_t hash, const swift::metadata &metadata, const void *body, + ClosureFunctionCI get_attribute_type_id); + void cache_insert(data::ptr node); + + void cache_collect(); + // MARK: Tree Graph::TreeElementID tree_root() { return _tree_root; }; @@ -193,9 +226,9 @@ class Subgraph : public data::zone { AttributeID tree_node_at_index(Graph::TreeElementID tree_element, uint64_t index); Graph::TreeElementID tree_subgraph_child(Graph::TreeElementID tree_element); - + // MARK: Printing - + void print(uint32_t indent_level); }; diff --git a/Sources/ComputeCxx/include/ComputeCxx/AGCachedValueOptions.h b/Sources/ComputeCxx/include/ComputeCxx/AGCachedValueOptions.h new file mode 100644 index 0000000..4795e0c --- /dev/null +++ b/Sources/ComputeCxx/include/ComputeCxx/AGCachedValueOptions.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +AG_ASSUME_NONNULL_BEGIN + +AG_EXTERN_C_BEGIN + +typedef AG_OPTIONS(uint32_t, AGCachedValueOptions) { + AGCachedValueOptionsNone = 0, + AGCachedValueOptionsUnprefetched = 1, +} AG_SWIFT_NAME(CachedValueOptions); + +AG_EXTERN_C_END + +AG_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/include/ComputeCxx/AGGraph.h b/Sources/ComputeCxx/include/ComputeCxx/AGGraph.h index cdb18f5..e824076 100644 --- a/Sources/ComputeCxx/include/ComputeCxx/AGGraph.h +++ b/Sources/ComputeCxx/include/ComputeCxx/AGGraph.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +54,8 @@ AGGraphRef AGGraphCreateShared(AGGraphRef _Nullable graph) AG_SWIFT_NAME(AGGraph AG_EXPORT AG_REFINED_FOR_SWIFT -AGUnownedGraphContextRef AGGraphGetGraphContext(AGGraphRef graph) AG_SWIFT_NAME(getter:AGGraphRef.graphContext(self:)); +AGUnownedGraphContextRef AGGraphGetGraphContext(AGGraphRef graph) + AG_SWIFT_NAME(getter:AGGraphRef.graphContext(self:)); AG_EXPORT AG_REFINED_FOR_SWIFT @@ -262,6 +264,22 @@ void AGGraphSetInvalidationCallback(AGGraphRef graph, AG_SWIFT_CC(swift), const void *callback_context); +// MARK: Cached value + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void *AGGraphReadCachedAttribute(size_t hash, AGTypeID type, const void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute owner, bool *_Nullable changed_out, + uint32_t (*closure)(const void *context AG_SWIFT_CONTEXT, + AGUnownedGraphContextRef graph_context) AG_SWIFT_CC(swift), + const void *closure_context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void *_Nullable AGGraphReadCachedAttributeIfExists(size_t hash, AGTypeID type, const void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute owner, + bool *_Nullable changed_out); + // MARK: Update typedef AG_ENUM(uint32_t, AGGraphUpdateStatus) { diff --git a/Sources/ComputeCxx/include/ComputeCxx/ComputeCxx.h b/Sources/ComputeCxx/include/ComputeCxx/ComputeCxx.h index b301762..57b361b 100644 --- a/Sources/ComputeCxx/include/ComputeCxx/ComputeCxx.h +++ b/Sources/ComputeCxx/include/ComputeCxx/ComputeCxx.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include From ff056399763face6ccd3b2b4a8112766b91e673e Mon Sep 17 00:00:00 2001 From: James Moschou Date: Sat, 17 Jan 2026 17:04:58 +0100 Subject: [PATCH 5/5] Change to --- Sources/ComputeCxx/Subgraph/NodeCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComputeCxx/Subgraph/NodeCache.h b/Sources/ComputeCxx/Subgraph/NodeCache.h index cb61da3..29dcc17 100644 --- a/Sources/ComputeCxx/Subgraph/NodeCache.h +++ b/Sources/ComputeCxx/Subgraph/NodeCache.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include "Subgraph.h"