lib/std/graph.rad 4.3 KiB raw
1
//! Construction authority for region-bound graphs with stable node identities.
2
//!
3
//! Typed opaque wrappers keep their payloads and links private. They check all
4
//! linked nodes before writing a payload, then mark the node complete. Payload
5
//! writes require the builder. Frozen readers require the published graph.
6
7
@test mod tests;
8
9
use std::lang::alloc;
10
11
/// Largest node count represented by a graph.
12
constant MAX_NODE_COUNT: u32 = 0xffffffff;
13
14
/// Exclusive authority to construct one graph.
15
export opaque record Builder: 'g {
16
    /// Unique identity shared by this graph's nodes.
17
    owner: &'g u8,
18
    /// Number of reserved node identities.
19
    count: u32,
20
    /// Number of nodes whose payloads are not complete.
21
    pending: u32,
22
}
23
24
/// Stable identity retained by a typed node wrapper.
25
export opaque record Node: 'g + Copy {
26
    /// Identity of the graph that owns this node.
27
    owner: &'g u8,
28
    /// Completion state controlled by the graph builder.
29
    initialized: &'g cell bool,
30
}
31
32
/// Shared authority to read a completed graph.
33
export opaque record Frozen: 'g + Copy {
34
    /// Identity of the graph that owns all published nodes.
35
    owner: &'g u8,
36
    /// Number of published node identities.
37
    count: u32,
38
}
39
40
/// Invalid access through a graph capability.
41
export union Error: Copy {
42
    /// The node belongs to a different graph.
43
    ForeignNode,
44
}
45
46
/// A graph that cannot yet be published.
47
export union FreezeError: 'g {
48
    /// Construction authority for the graph that still has pending nodes.
49
    Incomplete(Builder 'g),
50
}
51
52
/// Create an empty graph whose identity lives in the allocation session.
53
export fn new 'g (storage: &Session 'g) -> Builder 'g throws (alloc::AllocError) {
54
    let owner = try storage.new(0 as u8);
55
    return Builder 'g { owner: &*owner, count: 0, pending: 0 };
56
}
57
58
/// Reserve a stable node identity before its typed payload is complete.
59
/// Allocation failure leaves the builder unchanged.
60
export fn reserve 'g (builder: &mut Builder 'g, storage: &Session 'g) -> Node 'g
61
    throws (alloc::AllocError)
62
{
63
    if builder.count == MAX_NODE_COUNT {
64
        throw alloc::AllocError::OutOfMemory;
65
    }
66
    let initialized = try storage.new(false);
67
    let result = Node 'g { owner: builder.owner, initialized: &cell *initialized };
68
    set builder.count += 1;
69
    set builder.pending += 1;
70
    return result;
71
}
72
73
/// Check a node before reading or writing its typed payload during construction.
74
/// Typed wrappers must also check every node linked by a payload write.
75
export fn check 'g (builder: &Builder 'g, node: Node 'g) throws (Error) {
76
    if builder.owner <> node.owner {
77
        throw Error::ForeignNode;
78
    }
79
}
80
81
/// Mark a node complete after its typed payload and links are initialized.
82
/// Completing an initialized node leaves the pending count unchanged.
83
export fn complete 'g (builder: &mut Builder 'g, node: Node 'g) throws (Error) {
84
    try check(builder, node);
85
    if not *node.initialized {
86
        assert builder.pending > 0;
87
        set *node.initialized = true;
88
        set builder.pending -= 1;
89
    }
90
}
91
92
/// Return the number of reserved nodes that still need initialization.
93
export fn pending 'g (builder: &Builder 'g) -> u32 {
94
    return builder.pending;
95
}
96
97
/// Consume construction authority without publishing the graph.
98
/// Node identity allocations remain owned by the allocation session.
99
export fn abandon 'g (builder: Builder 'g) {
100
    let case Builder 'g { owner: _, count: _, pending: _ } = builder
101
        else panic "abandon: invalid builder";
102
}
103
104
/// Consume construction authority and publish the completed graph.
105
/// An incomplete graph returns its builder in the error for recovery.
106
export fn freeze 'g (builder: Builder 'g) -> Frozen 'g throws (FreezeError 'g) {
107
    if builder.pending <> 0 {
108
        throw FreezeError 'g::Incomplete(builder);
109
    }
110
    let case Builder 'g { owner, count, pending: _ } = builder
111
        else panic "freeze: invalid builder";
112
    return Frozen 'g { owner, count };
113
}
114
115
/// Check a node before reading its typed payload through a frozen graph.
116
export fn checkFrozen 'g (graph: &Frozen 'g, node: Node 'g) throws (Error) {
117
    if graph.owner <> node.owner {
118
        throw Error::ForeignNode;
119
    }
120
    assert *node.initialized;
121
}
122
123
/// Return the number of nodes in a frozen graph.
124
export fn len 'g (graph: &Frozen 'g) -> u32 {
125
    return graph.count;
126
}