From ef29bc336a16cee2091eb30b0a3aef606d2163d9 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Mon, 28 Sep 2026 14:19:21 +0200 Subject: [PATCH 01/11] Update bundle version to 0.6.0 --- Xcode/Configs/Compute.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xcode/Configs/Compute.xcconfig b/Xcode/Configs/Compute.xcconfig index 3298dbe..44898ec 100644 --- a/Xcode/Configs/Compute.xcconfig +++ b/Xcode/Configs/Compute.xcconfig @@ -34,7 +34,7 @@ USE_HEADERMAP = NO CODE_SIGN_STYLE = Automatic CURRENT_PROJECT_VERSION = 1 -MARKETING_VERSION = 0.5.2 +MARKETING_VERSION = 0.6.0 ENABLE_MODULE_VERIFIER = YES MODULE_VERIFIER_SUPPORTED_LANGUAGES = objective-c objective-c++ From 0fff4ec975b3875c0126ff05b57e99b06133b692 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Thu, 8 Oct 2026 13:27:07 +0200 Subject: [PATCH 02/11] Preserve move semantics in Vector.h --- Sources/ComputeCxx/Data/Vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComputeCxx/Data/Vector.h b/Sources/ComputeCxx/Data/Vector.h index 7c19fa3..67ef5b0 100644 --- a/Sources/ComputeCxx/Data/Vector.h +++ b/Sources/ComputeCxx/Data/Vector.h @@ -162,7 +162,7 @@ vector::iterator vector::insert(zone *zone, const_iterator pos, const T &v template vector::iterator vector::insert(zone *zone, const_iterator pos, T &&value) { if (pos == end()) { - push_back(zone, value); + push_back(zone, std::move(value)); return end() - 1; } auto offset = pos - begin(); From cc35059700fc0f59b52ae371fc544f1562945f21 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Fri, 9 Oct 2026 09:39:55 +0200 Subject: [PATCH 03/11] Add @escaping to all closure parameters passed to C++ --- Sources/Compute/Attribute/AnyAttribute.swift | 4 +-- Sources/Compute/Attribute/AttributeType.swift | 2 +- .../RuleContext/AnyRuleContext.swift | 6 +++-- Sources/Compute/Graph/Graph.swift | 26 +++++++++++-------- Sources/Compute/Graph/Subgraph.swift | 8 +++--- Sources/Compute/Runtime/Metadata.swift | 9 ++++--- 6 files changed, 33 insertions(+), 22 deletions(-) diff --git a/Sources/Compute/Attribute/AnyAttribute.swift b/Sources/Compute/Attribute/AnyAttribute.swift index b7d997c..3327494 100644 --- a/Sources/Compute/Attribute/AnyAttribute.swift +++ b/Sources/Compute/Attribute/AnyAttribute.swift @@ -5,14 +5,14 @@ func IAGGraphMutateAttribute( _ attribute: AnyAttribute, type: Metadata, invalidating: Bool, - modify: (UnsafeMutableRawPointer) -> Void + modify: @escaping (UnsafeMutableRawPointer) -> Void ) @_silgen_name("IAGGraphSearch") func IAGGraphSearch( attribute: AnyAttribute, options: SearchOptions, - predicate: (AnyAttribute) -> Bool + predicate: @escaping (AnyAttribute) -> Bool ) -> Bool extension AnyAttribute { diff --git a/Sources/Compute/Attribute/AttributeType.swift b/Sources/Compute/Attribute/AttributeType.swift index ca88289..36ce869 100644 --- a/Sources/Compute/Attribute/AttributeType.swift +++ b/Sources/Compute/Attribute/AttributeType.swift @@ -16,7 +16,7 @@ extension String { @_silgen_name("IAGRetainClosure") func IAGRetainClosure( - _ closure: (UnsafeMutableRawPointer, AnyAttribute) -> Void + _ closure: @escaping (UnsafeMutableRawPointer, AnyAttribute) -> Void ) -> _IAGClosureStorage extension _AttributeType { diff --git a/Sources/Compute/Attribute/RuleContext/AnyRuleContext.swift b/Sources/Compute/Attribute/RuleContext/AnyRuleContext.swift index dfd055a..1f20c03 100644 --- a/Sources/Compute/Attribute/RuleContext/AnyRuleContext.swift +++ b/Sources/Compute/Attribute/RuleContext/AnyRuleContext.swift @@ -1,7 +1,7 @@ import ComputeCxx @_silgen_name("IAGGraphWithUpdate") -func IAGGraphWithUpdate(_ attribute: AnyAttribute, body: () -> Void) +func IAGGraphWithUpdate(_ attribute: AnyAttribute, body: @escaping () -> Void) @frozen public struct AnyRuleContext { @@ -21,7 +21,9 @@ public struct AnyRuleContext { } public func update(body: () -> Void) { - IAGGraphWithUpdate(attribute, body: body) + withoutActuallyEscaping(body) { escapingBody in + IAGGraphWithUpdate(attribute, body: escapingBody) + } } public func changedValue( diff --git a/Sources/Compute/Graph/Graph.swift b/Sources/Compute/Graph/Graph.swift index 6584196..8a1797f 100644 --- a/Sources/Compute/Graph/Graph.swift +++ b/Sources/Compute/Graph/Graph.swift @@ -34,11 +34,13 @@ extension Graph { pointer.initialize(to: attributeType) return UnsafePointer(pointer) } - return internAttributeType( - ctx: ctx, - body: Metadata(body), - makeAttributeType: makeAttributeType - ) + return withoutActuallyEscaping(makeAttributeType) { escapingMakeAttributeType in + return internAttributeType( + ctx: ctx, + body: Metadata(body), + makeAttributeType: escapingMakeAttributeType + ) + } } } @@ -81,7 +83,7 @@ func IAGGraphSetInvalidationCallback( @_silgen_name("IAGGraphWithMainThreadHandler") func IAGGraphWithMainThreadHandler( _ graph: UnsafeRawPointer, - body: () -> Void, + body: @escaping () -> Void, mainThreadHandler: (() -> Void) -> Void ) @@ -118,11 +120,13 @@ extension Graph { } public func withMainThreadHandler(_ mainThreadHandler: (() -> Void) -> Void, do body: () -> Void) { - IAGGraphWithMainThreadHandler( - unsafeBitCast(self, to: UnsafeRawPointer.self), - body: body, - mainThreadHandler: mainThreadHandler - ) + withoutActuallyEscaping(body) { escapingBody in + IAGGraphWithMainThreadHandler( + unsafeBitCast(self, to: UnsafeRawPointer.self), + body: escapingBody, + mainThreadHandler: mainThreadHandler + ) + } } } diff --git a/Sources/Compute/Graph/Subgraph.swift b/Sources/Compute/Graph/Subgraph.swift index 546a965..f3e2897 100644 --- a/Sources/Compute/Graph/Subgraph.swift +++ b/Sources/Compute/Graph/Subgraph.swift @@ -3,7 +3,7 @@ import ComputeCxx @_silgen_name("IAGSubgraphAddObserver") func IAGSubgraphAddObserver( _ subgraph: UnsafeRawPointer, - observer: () -> Void + observer: @escaping () -> Void ) -> Int extension Subgraph { @@ -16,7 +16,7 @@ extension Subgraph { func IAGSubgraphApply( _ subgraph: UnsafeRawPointer, _ flags: Subgraph.Flags, - _ body: (AnyAttribute) -> Void + _ body: @escaping (AnyAttribute) -> Void ) extension Subgraph { @@ -36,7 +36,9 @@ extension Subgraph { _ flags: Subgraph.Flags, _ body: (AnyAttribute) -> Void ) { - IAGSubgraphApply(unsafeBitCast(self, to: UnsafeRawPointer.self), flags, body) + withoutActuallyEscaping(body) { escapingBody in + IAGSubgraphApply(unsafeBitCast(self, to: UnsafeRawPointer.self), flags, escapingBody) + } } } diff --git a/Sources/Compute/Runtime/Metadata.swift b/Sources/Compute/Runtime/Metadata.swift index b6494fb..76ff0af 100644 --- a/Sources/Compute/Runtime/Metadata.swift +++ b/Sources/Compute/Runtime/Metadata.swift @@ -13,7 +13,7 @@ public func forEachField(of type: Any.Type, do body: (UnsafePointer, Int, func IAGTypeApplyFields2( _ type: Metadata, options: Metadata.ApplyOptions, - body: (UnsafePointer, Int, Metadata) -> Bool + body: @escaping (UnsafePointer, Int, Metadata) -> Bool ) -> Bool extension Metadata { @@ -32,8 +32,11 @@ extension Metadata { ) -> Bool { - return IAGTypeApplyFields2(self, options: options) { fieldName, fieldOffset, fieldType in - return body(fieldName, fieldOffset, fieldType.type) + return withoutActuallyEscaping(body) { escapingBody in + let apply: (UnsafePointer, Int, Metadata) -> Bool = { fieldName, fieldOffset, fieldType in + return escapingBody(fieldName, fieldOffset, fieldType.type) + } + return IAGTypeApplyFields2(self, options: options, body: apply) } } From 1505ff5990179947882cee5c611fcce63cf00882 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Fri, 9 Oct 2026 10:27:22 +0200 Subject: [PATCH 04/11] Update bundle version to 0.6.1 --- Xcode/Configs/Compute.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xcode/Configs/Compute.xcconfig b/Xcode/Configs/Compute.xcconfig index 44898ec..abd4e8f 100644 --- a/Xcode/Configs/Compute.xcconfig +++ b/Xcode/Configs/Compute.xcconfig @@ -34,7 +34,7 @@ USE_HEADERMAP = NO CODE_SIGN_STYLE = Automatic CURRENT_PROJECT_VERSION = 1 -MARKETING_VERSION = 0.6.0 +MARKETING_VERSION = 0.6.1 ENABLE_MODULE_VERIFIER = YES MODULE_VERIFIER_SUPPORTED_LANGUAGES = objective-c objective-c++ From 516a0fdd7850a96a4c783602972127745b4af127 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 11 Oct 2026 16:51:29 +0800 Subject: [PATCH 05/11] Fix offsets and bounds in partial attribute comparisons --- .../Comparison/LayoutDescriptor.cpp | 4 +- .../Attribute/PartialComparisonTests.swift | 193 ++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 Tests/ComputeTests/Shared/Attribute/PartialComparisonTests.swift diff --git a/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp b/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp index 5710b86..8666b66 100644 --- a/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp +++ b/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp @@ -766,7 +766,9 @@ Partial find_partial(ValueLayout layout, size_t range_location, size_t range_siz } }; - return {reader.layout, accumulated_size}; + // The compared values already start at the requested range. + size_t location = accumulated_size - range_location; + return {reader.layout, location < range_size ? location : range_size}; } void print(std::string &output, ValueLayout layout) { diff --git a/Tests/ComputeTests/Shared/Attribute/PartialComparisonTests.swift b/Tests/ComputeTests/Shared/Attribute/PartialComparisonTests.swift new file mode 100644 index 0000000..a3dc9f0 --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/PartialComparisonTests.swift @@ -0,0 +1,193 @@ +import Foundation +import Testing + +@Suite(.serialized(for: \Subgraph.Type.current)) +struct PartialComparisonTests { + struct EnvironmentValues { + var value: Value + } + + enum Content { + case keyPath(KeyPath, Value>) + case value(Value) + } + + struct Container { + var prefix: Int + var content: Content + var suffix: Int + } + + struct ReadContent: StatefulRule { + @Attribute var content: Content + var environment: EnvironmentValues + var updates = 0 + + mutating func updateValue() { + updates += 1 + let input: Input + switch content { + case let .keyPath(keyPath): + input = environment[keyPath: keyPath] + case let .value(value): + input = value + } + value = (input, updates) + } + + typealias Value = (input: Input, updates: Int) + } + + @Test + func enumMemberAfterPrefix() async throws { + try await #require(processExitsWith: .success) { + setenv(asyncLayoutsEnvironmentVariable, "0", 1) + _ = prefetchCompareValues( + type: Metadata(Container.self), + options: [.comparisonModeEquatableAlways, .fetchLayoutsSynchronously], + priority: 0 + ) + + withGraph { + let source = Attribute(value: Container( + prefix: 0, content: .keyPath(\EnvironmentValues.value), suffix: 0 + )) + let result = Attribute(ReadContent( + content: source[offset: { .of(&$0.content) }], environment: .init(value: true) + )) + #expect(result.value.input) + #expect(result.value.updates == 1) + + source.value.prefix = 1 + #expect(result.value.input) + #expect(result.value.updates == 1) + + source.value.content = .value(false) + #expect(!result.value.input) + #expect(result.value.updates == 2) + } + } + } + + struct Payload: Equatable { + var first: Int + var second: Int + } + + struct ReadValue: StatefulRule { + @Attribute var input: Input + var updates = 0 + + mutating func updateValue() { + updates += 1 + value = (input, updates) + } + + typealias Value = (input: Input, updates: Int) + } + + struct Parity: Equatable { + var rawValue: Int + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.rawValue % 2 == rhs.rawValue % 2 + } + } + + @Test + func memberUsesItsEqualityImplementation() async throws { + try await #require(processExitsWith: .success) { + setenv(asyncLayoutsEnvironmentVariable, "0", 1) + _ = prefetchCompareValues( + type: Metadata(Triple.self), + options: [.comparisonModeEquatableAlways, .fetchLayoutsSynchronously], + priority: 0 + ) + + withGraph { + let source = Attribute(value: Triple(first: 0, second: Parity(rawValue: 0), third: 0)) + let result = Attribute(ReadValue(input: source[offset: { .of(&$0.second) }])) + #expect(result.value.input.rawValue == 0) + #expect(result.value.updates == 1) + + source.value = Triple(first: 1, second: Parity(rawValue: 2), third: 0) + #expect(result.value.updates == 1) + + source.value = Triple(first: 1, second: Parity(rawValue: 3), third: 0) + #expect(result.value.input.rawValue == 3) + #expect(result.value.updates == 2) + } + } + } + + struct InlineValues: Equatable { + var first: Int + var second: Int + var third: Int + } + + struct ValueContainer { + var values: InlineValues + var content: Content + } + + @Test + func byteComparisonStaysWithinTheMember() async throws { + try await #require(processExitsWith: .success) { + setenv(asyncLayoutsEnvironmentVariable, "0", 1) + _ = prefetchCompareValues( + type: Metadata(ValueContainer.self), + options: [.comparisonModeEquatableAlways, .fetchLayoutsSynchronously], + priority: 0 + ) + + withGraph { + let source = Attribute(value: ValueContainer( + values: InlineValues(first: 0, second: 1, third: 2), content: .value(true) + )) + let result = Attribute(ReadValue(input: source[offset: { .of(&$0.values.second) }])) + #expect(result.value.input == 1) + #expect(result.value.updates == 1) + + source.value.values.third = 3 + #expect(result.value.input == 1) + #expect(result.value.updates == 1) + + source.value.values.second = 4 + #expect(result.value.input == 4) + #expect(result.value.updates == 2) + } + } + } + + @Test + func enumPayloadIsNotCopiedFromAnAdjacentField() async throws { + try await #require(processExitsWith: .success) { + setenv(asyncLayoutsEnvironmentVariable, "0", 1) + _ = prefetchCompareValues( + type: Metadata(Container.self), + options: [.comparisonModeEquatableAlways, .fetchLayoutsSynchronously], + priority: 0 + ) + + withGraph { + let payload = Payload(first: 0, second: 0x1_0000_0000) + let source = Attribute(value: Container(prefix: 0, content: .value(payload), suffix: 0)) + let result = Attribute(ReadContent( + content: source[offset: { .of(&$0.content) }], environment: .init(value: payload) + )) + #expect(result.value.input == payload) + #expect(result.value.updates == 1) + + source.value.prefix = 1 + #expect(result.value.input == payload) + #expect(result.value.updates == 1) + + let changed = Payload(first: 1, second: payload.second) + source.value.content = .value(changed) + #expect(result.value.input == changed) + #expect(result.value.updates == 2) + } + } + } +} From a0b19ef94fc1dd2aa74c5eec165be64dc8f6d6bd Mon Sep 17 00:00:00 2001 From: James Moschou Date: Tue, 13 Oct 2026 09:28:51 +0200 Subject: [PATCH 06/11] Update bundle version to 0.6.2 --- Xcode/Configs/Compute.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xcode/Configs/Compute.xcconfig b/Xcode/Configs/Compute.xcconfig index abd4e8f..f2c9b0c 100644 --- a/Xcode/Configs/Compute.xcconfig +++ b/Xcode/Configs/Compute.xcconfig @@ -34,7 +34,7 @@ USE_HEADERMAP = NO CODE_SIGN_STYLE = Automatic CURRENT_PROJECT_VERSION = 1 -MARKETING_VERSION = 0.6.1 +MARKETING_VERSION = 0.6.2 ENABLE_MODULE_VERIFIER = YES MODULE_VERIFIER_SUPPORTED_LANGUAGES = objective-c objective-c++ From 5eb5234d084fcbe63f0a54b355c41247672e92be Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 14 Oct 2026 00:20:14 +0200 Subject: [PATCH 07/11] Use bitwise & instead of logical && in AttributeID::resolve_slow --- Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp b/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp index 5e7c8c8..e669514 100644 --- a/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp +++ b/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp @@ -73,7 +73,7 @@ OffsetAttributeID AttributeID::resolve_slow(TraversalOptions options) const { } } - if (options && TraversalOptions::EvaluateWeakReferences) { + if (options & TraversalOptions::EvaluateWeakReferences) { if (indirect_node->source().expired()) { if (options & TraversalOptions::AssertNotNil) { precondition_failure("invalid indirect ref: %u", _value); From 8d41777653d3f26e761d3a3a86432d72ded795ed Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 14 Oct 2026 01:48:05 +0200 Subject: [PATCH 08/11] Ensure all node edges get properly removed when a subgraph is being invalidated # Conflicts: # Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.cpp --- .../Attribute/AttributeID/AttributeID.cpp | 2 +- .../Attribute/AttributeID/WeakAttributeID.cpp | 15 +++++++++++++-- .../Attribute/AttributeID/WeakAttributeID.h | 11 +++++++++-- Sources/ComputeCxx/Graph/Graph.cpp | 10 +++++----- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp b/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp index e669514..7ebdddc 100644 --- a/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp +++ b/Sources/ComputeCxx/Attribute/AttributeID/AttributeID.cpp @@ -74,7 +74,7 @@ OffsetAttributeID AttributeID::resolve_slow(TraversalOptions options) const { } if (options & TraversalOptions::EvaluateWeakReferences) { - if (indirect_node->source().expired()) { + if (indirect_node->source().is_freed()) { if (options & TraversalOptions::AssertNotNil) { precondition_failure("invalid indirect ref: %u", _value); } diff --git a/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.cpp b/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.cpp index f22a517..e874113 100644 --- a/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.cpp +++ b/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.cpp @@ -6,7 +6,7 @@ namespace IAG { -bool WeakAttributeID::expired() const { +bool WeakAttributeID::is_expired() const { uint64_t raw_page_seed = data::table::shared().raw_page_seed(_identifier.page_ptr()); if (raw_page_seed & 0xff00000000) { auto zone_info = data::zone::info::from_raw_value(uint32_t(raw_page_seed)); @@ -17,8 +17,19 @@ bool WeakAttributeID::expired() const { return true; } +bool WeakAttributeID::is_freed() const { + uint64_t raw_page_seed = data::table::shared().raw_page_seed(_identifier.page_ptr()); + if (raw_page_seed & 0xff00000000) { + auto zone_info = data::zone::info::from_raw_value(uint32_t(raw_page_seed)); + if (zone_info.zone_id() == _seed) { + return false; + } + } + return true; +} + const AttributeID WeakAttributeID::evaluate() const { - return _identifier && !expired() ? _identifier : AttributeID(IAGAttributeNil); + return _identifier && !is_expired() ? _identifier : AttributeID(IAGAttributeNil); }; } // namespace IAG diff --git a/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.h b/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.h index 3f41e44..50f5f38 100644 --- a/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.h +++ b/Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.h @@ -25,9 +25,16 @@ class WeakAttributeID { const AttributeID identifier() const { return _identifier; }; uint32_t seed() const { return _seed; } - bool expired() const; + /// Returns true if the attribute can no longer be evaluated. + bool is_expired() const; - /// Returns the attribute it is has not expired, otherwise returns the nil + /// Returns true when the attribute's subgraph has been freed (or reused). + /// + /// This method may return false if the attribute's subgraph has been + /// invalidated but not yet destroyed. + bool is_freed() const; + + /// Returns the attribute if it has not expired, otherwise returns the nil /// attribute. const AttributeID evaluate() const; }; diff --git a/Sources/ComputeCxx/Graph/Graph.cpp b/Sources/ComputeCxx/Graph/Graph.cpp index 8084e12..dc96cc8 100644 --- a/Sources/ComputeCxx/Graph/Graph.cpp +++ b/Sources/ComputeCxx/Graph/Graph.cpp @@ -522,7 +522,7 @@ void Graph::remove_indirect_node(data::ptr indirect_node) { return; } - if (indirect_node->source().expired()) { + if (indirect_node->source().is_freed()) { return; } @@ -556,7 +556,7 @@ void Graph::remove_indirect_node(data::ptr indirect_node) { } break; } else { - if (source_indirect_node->source().expired()) { + if (source_indirect_node->source().is_freed()) { break; } @@ -624,7 +624,7 @@ bool Graph::remove_removed_output(AttributeID attribute, AttributeID output, boo if (output_indirect_node->is_mutable()) { auto &mutable_output_indirect_node = output_indirect_node->to_mutable(); auto initial_source = mutable_output_indirect_node.initial_source(); - if (initial_source.identifier() && !initial_source.identifier().is_nil() && !initial_source.expired()) { + if (initial_source.identifier() && !initial_source.identifier().is_nil() && !initial_source.is_expired()) { new_source = initial_source; new_offset = mutable_output_indirect_node.initial_offset(); } @@ -636,7 +636,7 @@ bool Graph::remove_removed_output(AttributeID attribute, AttributeID output, boo output_indirect_node->modify(new_source, new_offset); - if (new_source.identifier() && !new_source.identifier().is_nil() && !new_source.expired()) { + if (new_source.identifier() && !new_source.identifier().is_nil() && !new_source.is_expired()) { add_input_dependencies(output, new_source.identifier()); } @@ -917,7 +917,7 @@ bool Graph::indirect_attribute_reset(data::ptr indirect_node, bool auto initial_source = indirect_node->to_mutable().initial_source(); auto initial_offset = indirect_node->to_mutable().initial_offset(); - if (!initial_source.expired()) { + if (!initial_source.is_expired()) { new_source = initial_source; new_offset = initial_offset; } else { From eb71ec54eb5c04caad01520bb93ce6fcd506d028 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 14 Oct 2026 09:31:21 +0200 Subject: [PATCH 09/11] Update bundle version to 0.6.3 --- Xcode/Configs/Compute.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xcode/Configs/Compute.xcconfig b/Xcode/Configs/Compute.xcconfig index f2c9b0c..0110e5c 100644 --- a/Xcode/Configs/Compute.xcconfig +++ b/Xcode/Configs/Compute.xcconfig @@ -34,7 +34,7 @@ USE_HEADERMAP = NO CODE_SIGN_STYLE = Automatic CURRENT_PROJECT_VERSION = 1 -MARKETING_VERSION = 0.6.2 +MARKETING_VERSION = 0.6.3 ENABLE_MODULE_VERIFIER = YES MODULE_VERIFIER_SUPPORTED_LANGUAGES = objective-c objective-c++ From 88af343d12f297856b81ac709028cb310bfef1f4 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Tue, 13 Oct 2026 10:12:57 +0200 Subject: [PATCH 10/11] Change recursion to iteration in UpdateStack::update() # Conflicts: # Sources/ComputeCxx/Graph/UpdateStack.cpp --- Sources/ComputeCxx/Graph/UpdateStack.cpp | 88 +++++++++++++----------- Sources/ComputeCxx/Graph/UpdateStack.h | 1 + 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/Sources/ComputeCxx/Graph/UpdateStack.cpp b/Sources/ComputeCxx/Graph/UpdateStack.cpp index 0290f80..078e47a 100644 --- a/Sources/ComputeCxx/Graph/UpdateStack.cpp +++ b/Sources/ComputeCxx/Graph/UpdateStack.cpp @@ -152,6 +152,51 @@ bool Graph::UpdateStack::push_slow(data::ptr node_ptr, Node &node, bool ig return true; } +bool Graph::UpdateStack::push_inputs(Frame &frame, data::ptr node) { + for (auto input_index = frame.num_pushed_inputs, num_inputs = node->input_edges().size(); + input_index != num_inputs; ++input_index) { + const InputEdge &input_edge = node->input_edges()[input_index]; + + AttributeID input_attribute = input_edge.attribute; + while (input_attribute.is_indirect_node()) { + // TODO: warning: variable 'input_attribute' is uninitialized + // when used (revisit this) within its own initialization + auto input_attribute_source = input_attribute.get_indirect_node()->source().identifier(); + if (input_attribute.get_indirect_node()->is_mutable()) { + if (AttributeID dependency = input_attribute.get_indirect_node()->to_mutable().dependency()) { + if (!dependency.get_node()->is_value_initialized() || dependency.get_node()->is_dirty()) { + frame.num_pushed_inputs = input_index; + push(dependency.get_node(), *dependency.get_node(), false, false); + // pushed a frame regardless + return true; + } + } + } + input_attribute = input_attribute_source; + } + + if (auto input_node = input_attribute.get_node()) { + if (input_edge.options & IAGInputOptionsChanged) { + frame.pending = true; + } + + if (!input_node->is_value_initialized() || input_node->is_dirty()) { + + if (!(input_edge.options & IAGInputOptionsUnprefetched) && input_attribute.subgraph()->is_valid()) { + frame.num_pushed_inputs = input_index + 1; + if (push(input_node, *input_node, true, true)) { + return true; + } + } + + frame.pending = true; + } + } + } + + return false; +} + Graph::UpdateStatus Graph::UpdateStack::update() { while (true) { Frame &frame = _frames.back(); @@ -200,46 +245,9 @@ Graph::UpdateStatus Graph::UpdateStack::update() { // Push prefetched inputs - for (auto input_index = frame.num_pushed_inputs, num_inputs = node->input_edges().size(); - input_index != num_inputs; ++input_index) { - const InputEdge &input_edge = node->input_edges()[input_index]; - - AttributeID input_attribute = input_edge.attribute; - while (input_attribute.is_indirect_node()) { - // TODO: warning: variable 'input_attribute' is uninitialized - // when used (revisit this) within its own initialization - auto input_attribute_source = input_attribute.get_indirect_node()->source().identifier(); - if (input_attribute.get_indirect_node()->is_mutable()) { - if (AttributeID dependency = input_attribute.get_indirect_node()->to_mutable().dependency()) { - if (!dependency.get_node()->is_value_initialized() || dependency.get_node()->is_dirty()) { - frame.num_pushed_inputs = input_index; - push(dependency.get_node(), *dependency.get_node().get(), false, false); - // go to top regardless - return update(); - } - } - } - input_attribute = input_attribute_source; - } - - if (auto input_node = input_attribute.get_node()) { - if (input_edge.options & IAGInputOptionsChanged) { - frame.pending = true; - } - - if (!input_node->is_value_initialized() || input_node->is_dirty()) { - - if (!(input_edge.options & IAGInputOptionsUnprefetched) && input_attribute.subgraph()->is_valid()) { - frame.num_pushed_inputs = input_index + 1; - if (push(input_node, *input_node.get(), true, true)) { - // go to top - return update(); - } - } - - frame.pending = true; - } - } + if (push_inputs(frame, node)) { + // go to top + continue; } // Update value diff --git a/Sources/ComputeCxx/Graph/UpdateStack.h b/Sources/ComputeCxx/Graph/UpdateStack.h index 6805634..832891b 100644 --- a/Sources/ComputeCxx/Graph/UpdateStack.h +++ b/Sources/ComputeCxx/Graph/UpdateStack.h @@ -30,6 +30,7 @@ class Graph::UpdateStack { IAGGraphUpdateOptions _options; bool push_slow(data::ptr node_ptr, Node &node, bool ignore_cycles, bool initialize_value); + bool push_inputs(Frame &frame, data::ptr node); public: UpdateStack(Graph *graph, IAGGraphUpdateOptions options); From b129f659ac9f7e3791737314604d73764311644d Mon Sep 17 00:00:00 2001 From: James Moschou Date: Thu, 15 Oct 2026 12:50:45 +0200 Subject: [PATCH 11/11] Update bundle version to 0.6.4 --- Xcode/Configs/Compute.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xcode/Configs/Compute.xcconfig b/Xcode/Configs/Compute.xcconfig index 0110e5c..c64bfdc 100644 --- a/Xcode/Configs/Compute.xcconfig +++ b/Xcode/Configs/Compute.xcconfig @@ -34,7 +34,7 @@ USE_HEADERMAP = NO CODE_SIGN_STYLE = Automatic CURRENT_PROJECT_VERSION = 1 -MARKETING_VERSION = 0.6.3 +MARKETING_VERSION = 0.6.4 ENABLE_MODULE_VERIFIER = YES MODULE_VERIFIER_SUPPORTED_LANGUAGES = objective-c objective-c++