From 7c921b53d3f64852b88d7ec044db818dcb8b8a1b Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 22 Oct 2026 00:39:07 +0800 Subject: [PATCH] Fix inline vector storage lifetime during subgraph updates --- Sources/ComputeCxx/Vector/Vector.h | 6 ++- .../Shared/Subgraph/SubgraphUpdateTests.swift | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index fb1c0a9..4e8bf27 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -31,7 +31,11 @@ class vector { using size_type = _size_type; private: - T _inline_buffer[_inline_capacity]; + // Elements are constructed and destroyed explicitly. After relocation to + // the heap, the inline storage must not destroy the old element copies. + union { + T _inline_buffer[_inline_capacity]; + }; T *_Nullable _buffer = nullptr; size_type _size = 0; size_type _capacity = _inline_capacity; diff --git a/Tests/ComputeTests/Shared/Subgraph/SubgraphUpdateTests.swift b/Tests/ComputeTests/Shared/Subgraph/SubgraphUpdateTests.swift index d2883ce..2319bda 100644 --- a/Tests/ComputeTests/Shared/Subgraph/SubgraphUpdateTests.swift +++ b/Tests/ComputeTests/Shared/Subgraph/SubgraphUpdateTests.swift @@ -2,6 +2,49 @@ import Testing @Suite struct SubgraphUpdateTests { + @Test + func updatePreservesChildrenBeyondInlineCapacity() async { + await #expect(processExitsWith: .success) { + struct CountingRule: StatefulRule { + typealias Value = Int + var count = 0 + + mutating func updateValue() { + count += 1 + value = count + } + } + + withGraph { + let parent = Subgraph.current! + let flags = Subgraph.Flags(rawValue: 1) + let children = (0..<64).map { _ in Subgraph(graph: parent.graph) } + let attributes = children.map { child in + parent.addChild(child) + return child.apply { + let attribute = Attribute(CountingRule()) + attribute.flags = flags + return attribute + } + } + + for expectedValue in 1...3 { + for attribute in attributes { + attribute.invalidateValue() + } + parent.update(flags: flags) + for child in children { + precondition(child.isValid) + } + for attribute in attributes { + precondition(attribute.value == expectedValue) + } + } + withExtendedLifetime(children) {} + } + } + } + @Test func updateIterativelyEvaluatesDirtiedAttributesDuringTheSamePass() { class Counter {