Skip to content

Commit e12e51d

Browse files
committed
Fix stale graph edges during teardown
1 parent 3590801 commit e12e51d

4 files changed

Lines changed: 104 additions & 11 deletions

File tree

‎Sources/ComputeCxx/Attribute/AttributeID/WeakAttributeID.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ bool WeakAttributeID::expired() const {
1010
uint64_t raw_page_seed = data::table::shared().raw_page_seed(_identifier.page_ptr());
1111
if (raw_page_seed & 0xff00000000) {
1212
auto zone_info = data::zone::info::from_raw_value(uint32_t(raw_page_seed));
13-
if (zone_info.zone_id() == _seed) {
13+
if (zone_info.zone_id() == _seed && !zone_info.is_deleted()) {
1414
return false;
1515
}
1616
}

‎Sources/ComputeCxx/Data/Zone.h‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@ class zone {
2525
info(uint32_t zone_id) : _value(zone_id & zone_id_mask) {};
2626

2727
uint32_t zone_id() const { return _value & zone_id_mask; };
28-
info with_deleted() const { return info(_value | deleted); };
28+
bool is_deleted() const { return (_value & deleted) != 0; };
29+
info with_deleted() const {
30+
auto result = *this;
31+
result._value |= deleted;
32+
return result;
33+
};
2934

3035
uint32_t to_raw_value() const { return _value; };
31-
static info from_raw_value(uint32_t value) { return info(value); };
36+
static info from_raw_value(uint32_t value) {
37+
info result(0);
38+
result._value = value;
39+
return result;
40+
};
3241
};
3342

3443
private:

‎Sources/ComputeCxx/Graph/Graph.cpp‎

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,28 @@ platform_lock Graph::_all_graphs_lock = PLATFORM_LOCK_INIT;
3636

3737
pthread_key_t Graph::_current_update_key = 0;
3838

39+
namespace {
40+
41+
bool attribute_page_is_allocated(AttributeID attribute) {
42+
if (!attribute || attribute.is_nil()) {
43+
return false;
44+
}
45+
return data::table::shared().raw_page_seed(attribute.page_ptr()) & 0xff00000000;
46+
}
47+
48+
bool attribute_zone_is_live(AttributeID attribute) {
49+
if (!attribute || attribute.is_nil()) {
50+
return false;
51+
}
52+
auto raw_page_seed = data::table::shared().raw_page_seed(attribute.page_ptr());
53+
if (!(raw_page_seed & 0xff00000000)) {
54+
return false;
55+
}
56+
return !data::zone::info::from_raw_value(uint32_t(raw_page_seed)).is_deleted();
57+
}
58+
59+
} // namespace
60+
3961
Graph::Graph()
4062
: _heap(nullptr, 0, 0), _interned_types(nullptr, nullptr, nullptr, nullptr, &_heap),
4163
_contexts_by_id(nullptr, nullptr, nullptr, nullptr, &_heap), _id(IAGMakeUniqueID()) {
@@ -552,23 +574,40 @@ void Graph::remove_indirect_node(data::ptr<IndirectNode> indirect_node) {
552574
}
553575

554576
void Graph::remove_removed_input(AttributeID attribute, AttributeID input) {
577+
// An indirect input in the current invalidation batch can have a deleted
578+
// zone while still resolving to a live source, so only require its page to
579+
// remain allocated until after resolution.
580+
if (!attribute_page_is_allocated(input)) {
581+
return;
582+
}
583+
555584
auto resolved_input =
556585
input.resolve(TraversalOptions::SkipMutableReference | TraversalOptions::EvaluateWeakReferences).attribute();
586+
if (!attribute_zone_is_live(resolved_input)) {
587+
return;
588+
}
589+
590+
auto input_subgraph = resolved_input.subgraph();
591+
if (!input_subgraph || input_subgraph->graph() != this || !input_subgraph->is_valid()) {
592+
return;
593+
}
594+
557595
if (auto input_node = resolved_input.get_node()) {
558-
if (!resolved_input.subgraph()->is_invalidated()) {
559-
remove_output_edge(input_node, attribute);
560-
}
596+
remove_output_edge(input_node, attribute);
561597
} else if (auto input_indirect_node = resolved_input.get_indirect_node()) {
562-
if (!resolved_input.subgraph()->is_invalidated()) {
563-
if (input_indirect_node->is_mutable()) {
564-
remove_output_edge(input_indirect_node.unsafe_cast<MutableIndirectNode>(), attribute);
565-
}
598+
if (input_indirect_node->is_mutable()) {
599+
remove_output_edge(input_indirect_node.unsafe_cast<MutableIndirectNode>(), attribute);
566600
}
567601
}
568602
}
569603

570604
bool Graph::remove_removed_output(AttributeID attribute, AttributeID output, bool option) {
571-
if (output.subgraph()->is_invalidated()) {
605+
if (!attribute_zone_is_live(output)) {
606+
return false;
607+
}
608+
609+
auto output_subgraph = output.subgraph();
610+
if (!output_subgraph || output_subgraph->graph() != this || !output_subgraph->is_valid()) {
572611
return false;
573612
}
574613

‎Tests/ComputeTests/Shared/Attribute/AttributeBodyTests.swift‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,51 @@ struct AttributeBodyTests {
5353
}
5454
}
5555

56+
@Test
57+
func weakAttributeExpiresBeforeBodyIsDestroyed() {
58+
struct TestBody: _AttributeBody {
59+
var weakAttribute: WeakAttribute<Int>
60+
var weakAttributeExpired: UnsafeMutablePointer<Bool>
61+
62+
static func _destroySelf(_ self: UnsafeMutableRawPointer) {
63+
let testBody = self.assumingMemoryBound(to: Self.self)
64+
testBody.pointee.weakAttributeExpired.pointee = testBody.pointee.weakAttribute.attribute == nil
65+
}
66+
67+
static var _hasDestroySelf: Bool {
68+
return true
69+
}
70+
}
71+
72+
let graph = Graph()
73+
let subgraph = Subgraph(graph: graph)
74+
let oldSubgraph = Subgraph.current
75+
defer {
76+
Subgraph.current = oldSubgraph
77+
}
78+
Subgraph.current = subgraph
79+
80+
let attribute = Attribute(value: 0)
81+
var weakAttributeExpired = false
82+
withUnsafeMutablePointer(to: &weakAttributeExpired) { weakAttributeExpiredPointer in
83+
let body = TestBody(
84+
weakAttribute: WeakAttribute(attribute),
85+
weakAttributeExpired: weakAttributeExpiredPointer
86+
)
87+
let _ = withUnsafePointer(to: body) { bodyPointer in
88+
Attribute<Int>(body: bodyPointer, value: nil, flags: []) {
89+
return { _, _ in }
90+
}
91+
}
92+
93+
#expect(weakAttributeExpiredPointer.pointee == false)
94+
95+
subgraph.invalidate()
96+
97+
#expect(weakAttributeExpiredPointer.pointee == true)
98+
}
99+
}
100+
56101
@Test
57102
func doesNotCallDestroySelfWhenHasDestroySelfIsFalse() {
58103
struct TestBody: _AttributeBody {

0 commit comments

Comments
 (0)