std: Add checked graph publication and owned liveness topology
e4713575e3030997ca28814c2493ec346bfd10a8c7cc20a2cb103515c4016653
1 parent
55199dd6
Makefile
+1 -1
| 105 | 105 | BIN_RUNNER := test/runner.rv64 |
|
| 106 | 106 | BIN_TEST_RUN := test/run |
|
| 107 | 107 | ||
| 108 | 108 | bin-test: $(BIN_RUNNER) $(BIN_TEST_EXE_BIN) |
|
| 109 | 109 | @echo |
|
| 110 | - | @$(BIN_TEST_RUN) |
|
| 110 | + | @RAD_BIN="$(RAD_BIN)" $(BIN_TEST_RUN) |
|
| 111 | 111 | ||
| 112 | 112 | # Runner binary: the lowering IL checker. |
|
| 113 | 113 | $(BIN_RUNNER): test/runner.rad $(STD_LIB) $(RAD_BIN) |
|
| 114 | 114 | @echo "radiance test/runner.rad => $@" |
|
| 115 | 115 | @$(RADIANCE) $(STD) -pkg runner -mod test/runner.rad -entry runner -o $@ |
lib/std.rad
+1 -0
| 1 | 1 | //! The Radiance Standard Library. |
|
| 2 | 2 | ||
| 3 | 3 | export mod io; |
|
| 4 | 4 | export mod collections; |
|
| 5 | + | export mod graph; |
|
| 5 | 6 | export mod char; |
|
| 6 | 7 | export mod lang; |
|
| 7 | 8 | export mod sys; |
|
| 8 | 9 | export mod arch; |
|
| 9 | 10 | export mod fmt; |
lib/std/graph.rad
added
+119 -0
| 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 and publish the completed graph. |
|
| 98 | + | /// An incomplete graph returns its builder in the error for recovery. |
|
| 99 | + | export fn freeze 'g (builder: Builder 'g) -> Frozen 'g throws (FreezeError 'g) { |
|
| 100 | + | if builder.pending <> 0 { |
|
| 101 | + | throw FreezeError 'g::Incomplete(builder); |
|
| 102 | + | } |
|
| 103 | + | let case Builder 'g { owner, count, pending: _ } = builder |
|
| 104 | + | else panic "freeze: invalid builder"; |
|
| 105 | + | return Frozen 'g { owner, count }; |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | /// Check a node before reading its typed payload through a frozen graph. |
|
| 109 | + | export fn checkFrozen 'g (graph: &Frozen 'g, node: Node 'g) throws (Error) { |
|
| 110 | + | if graph.owner <> node.owner { |
|
| 111 | + | throw Error::ForeignNode; |
|
| 112 | + | } |
|
| 113 | + | assert *node.initialized; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | /// Return the number of nodes in a frozen graph. |
|
| 117 | + | export fn len 'g (graph: &Frozen 'g) -> u32 { |
|
| 118 | + | return graph.count; |
|
| 119 | + | } |
lib/std/graph/tests.rad
added
+129 -0
| 1 | + | //! Graph ownership, publication, and failure recovery tests. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | ||
| 6 | + | /// Empty graphs can be published without node allocations. |
|
| 7 | + | @test fn testEmptyGraph() throws (testing::TestError) { |
|
| 8 | + | static DATA: [u8; 64] = [0; 64]; |
|
| 9 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 10 | + | use arena as storage in { |
|
| 11 | + | let builder = try! super::new(&storage); |
|
| 12 | + | assert super::pending(&builder) == 0; |
|
| 13 | + | let frozen = try! super::freeze(builder); |
|
| 14 | + | assert super::len(&frozen) == 0; |
|
| 15 | + | } |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | /// Node identities and completion counts survive repeated completion and freeze. |
|
| 19 | + | @test fn testNodeCompletion() throws (testing::TestError) { |
|
| 20 | + | for count in [1 as u32, 2, 8, 64] { |
|
| 21 | + | static DATA: [u8; 1024] = [0; 1024]; |
|
| 22 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 23 | + | use arena as storage in { |
|
| 24 | + | let mut builder = try! super::new(&storage); |
|
| 25 | + | let first = try! super::reserve(&mut builder, &storage); |
|
| 26 | + | let mut nodes: [super::Node 'storage; 64] = [first; 64]; |
|
| 27 | + | for i in 1..count { |
|
| 28 | + | set nodes[i] = try! super::reserve(&mut builder, &storage); |
|
| 29 | + | for j in 0..i { |
|
| 30 | + | assert nodes[i] <> nodes[j]; |
|
| 31 | + | } |
|
| 32 | + | } |
|
| 33 | + | assert super::pending(&builder) == count; |
|
| 34 | + | for i in 0..count { |
|
| 35 | + | try! super::check(&builder, nodes[i]); |
|
| 36 | + | try! super::complete(&mut builder, nodes[i]); |
|
| 37 | + | assert super::pending(&builder) == count - i - 1; |
|
| 38 | + | try! super::complete(&mut builder, nodes[i]); |
|
| 39 | + | assert super::pending(&builder) == count - i - 1; |
|
| 40 | + | } |
|
| 41 | + | let frozen = try! super::freeze(builder); |
|
| 42 | + | assert super::len(&frozen) == count; |
|
| 43 | + | assert nodes[0] == first; |
|
| 44 | + | for i in 0..count { |
|
| 45 | + | try! super::checkFrozen(&frozen, nodes[i]); |
|
| 46 | + | } |
|
| 47 | + | } |
|
| 48 | + | } |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Builders in one session have separate ownership and completion authority. |
|
| 52 | + | @test fn testForeignNode() throws (testing::TestError) { |
|
| 53 | + | static DATA: [u8; 128] = [0; 128]; |
|
| 54 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 55 | + | use arena as storage in { |
|
| 56 | + | let mut first = try! super::new(&storage); |
|
| 57 | + | let mut second = try! super::new(&storage); |
|
| 58 | + | let a = try! super::reserve(&mut first, &storage); |
|
| 59 | + | let b = try! super::reserve(&mut second, &storage); |
|
| 60 | + | let mut rejected = false; |
|
| 61 | + | try super::complete(&mut first, b) catch error { |
|
| 62 | + | assert error == super::Error::ForeignNode; |
|
| 63 | + | set rejected = true; |
|
| 64 | + | }; |
|
| 65 | + | assert rejected; |
|
| 66 | + | assert super::pending(&first) == 1; |
|
| 67 | + | assert super::pending(&second) == 1; |
|
| 68 | + | try! super::complete(&mut first, a); |
|
| 69 | + | let frozen = try! super::freeze(first); |
|
| 70 | + | set rejected = false; |
|
| 71 | + | try super::checkFrozen(&frozen, b) catch error { |
|
| 72 | + | assert error == super::Error::ForeignNode; |
|
| 73 | + | set rejected = true; |
|
| 74 | + | }; |
|
| 75 | + | assert rejected; |
|
| 76 | + | try! super::complete(&mut second, b); |
|
| 77 | + | let other = try! super::freeze(second); |
|
| 78 | + | try! super::checkFrozen(&other, b); |
|
| 79 | + | } |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// An incomplete freeze returns the sole builder for completion and retry. |
|
| 83 | + | @test fn testIncompleteFreezeRecovery() throws (testing::TestError) { |
|
| 84 | + | static DATA: [u8; 128] = [0; 128]; |
|
| 85 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 86 | + | use arena as storage in { |
|
| 87 | + | let mut builder = try! super::new(&storage); |
|
| 88 | + | let first = try! super::reserve(&mut builder, &storage); |
|
| 89 | + | let second = try! super::reserve(&mut builder, &storage); |
|
| 90 | + | try! super::complete(&mut builder, first); |
|
| 91 | + | try super::freeze(builder) catch error { |
|
| 92 | + | let case super::FreezeError 'storage::Incomplete(recovered) = error else panic; |
|
| 93 | + | let mut retry = recovered; |
|
| 94 | + | assert super::pending(&retry) == 1; |
|
| 95 | + | try! super::complete(&mut retry, second); |
|
| 96 | + | let frozen = try! super::freeze(retry); |
|
| 97 | + | assert super::len(&frozen) == 2; |
|
| 98 | + | try! super::checkFrozen(&frozen, first); |
|
| 99 | + | try! super::checkFrozen(&frozen, second); |
|
| 100 | + | return; |
|
| 101 | + | }; |
|
| 102 | + | throw testing::TestError::Failed; |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Exhausted identity storage does not add incomplete nodes to the builder. |
|
| 107 | + | @test fn testAllocationFailure() throws (testing::TestError) { |
|
| 108 | + | for capacity in 0..9 { |
|
| 109 | + | static DATA: [u8; 16] = [0; 16]; |
|
| 110 | + | let mut arena = alloc::new(&mut DATA[..capacity]); |
|
| 111 | + | use arena as storage in { |
|
| 112 | + | if let created = try? super::new(&storage) { |
|
| 113 | + | let mut builder = created; |
|
| 114 | + | let mut count: u32 = 0; |
|
| 115 | + | while let node = try? super::reserve(&mut builder, &storage) { |
|
| 116 | + | try! super::complete(&mut builder, node); |
|
| 117 | + | set count += 1; |
|
| 118 | + | } |
|
| 119 | + | assert super::pending(&builder) == 0; |
|
| 120 | + | let frozen = try! super::freeze(builder); |
|
| 121 | + | assert super::len(&frozen) == count; |
|
| 122 | + | assert count == capacity - 1; |
|
| 123 | + | } else { |
|
| 124 | + | assert capacity == 0; |
|
| 125 | + | } |
|
| 126 | + | } |
|
| 127 | + | assert alloc::used(&arena) == capacity; |
|
| 128 | + | } |
|
| 129 | + | } |
lib/std/lang/gen/regalloc.rad
+1 -0
| 13 | 13 | //! Note that the IL is not modified. The allocator produces a mapping that |
|
| 14 | 14 | //! instruction selection uses to emit physical registers. Spilled values are |
|
| 15 | 15 | //! handled by [`isel`] inserting loads/stores. |
|
| 16 | 16 | ||
| 17 | 17 | export mod liveness; |
|
| 18 | + | export mod flow; |
|
| 18 | 19 | export mod spill; |
|
| 19 | 20 | export mod assign; |
|
| 20 | 21 | ||
| 21 | 22 | use std::lang::il; |
|
| 22 | 23 | use std::lang::alloc; |
lib/std/lang/gen/regalloc/flow.rad
added
+108 -0
| 1 | + | //! Owned control-flow topology for dataflow analysis. |
|
| 2 | + | ||
| 3 | + | use std::graph; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | use std::lang::il; |
|
| 6 | + | ||
| 7 | + | /// Largest successor count represented by a slice length. |
|
| 8 | + | constant MAX_SUCCESSORS: u32 = 0xffffffff; |
|
| 9 | + | ||
| 10 | + | /// Stable block identity and private successor storage. |
|
| 11 | + | export opaque record Block: 's + Copy { |
|
| 12 | + | /// Graph capability identity. |
|
| 13 | + | token: graph::Node 's, |
|
| 14 | + | /// Block position in the function's dataflow tables. |
|
| 15 | + | index: u32, |
|
| 16 | + | /// Successors published when construction is complete. |
|
| 17 | + | links: &'s cell &'s [Block 's], |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Immutable topology whose storage belongs to the analysis session. |
|
| 21 | + | export opaque record Flow: 's + Copy { |
|
| 22 | + | /// Shared authority for every block in the topology. |
|
| 23 | + | frozen: graph::Frozen 's, |
|
| 24 | + | /// Stable block handles in function order. |
|
| 25 | + | blocks: &'s [Block 's], |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Copy successor topology from valid IL into session-owned storage. |
|
| 29 | + | /// Instruction and switch tables must remain valid during this call. |
|
| 30 | + | export unsafe fn snapshot 's (blocks: &[il::Block], storage: &Session 's) |
|
| 31 | + | -> Flow 's throws (alloc::AllocError) |
|
| 32 | + | { |
|
| 33 | + | let mut builder = try graph::new(storage); |
|
| 34 | + | let none: [Block 's; 0] = []; |
|
| 35 | + | let empty: &'s [Block 's] = try storage.copy(&none[..]); |
|
| 36 | + | if blocks.len == 0 { |
|
| 37 | + | return Flow 's { frozen: try! graph::freeze(builder), blocks: empty }; |
|
| 38 | + | } |
|
| 39 | + | let first = try reserve(&mut builder, 0, empty, storage); |
|
| 40 | + | let entries = try storage.fill(first, blocks.len); |
|
| 41 | + | for i in 1..blocks.len { |
|
| 42 | + | set entries[i] = try reserve(&mut builder, i, empty, storage); |
|
| 43 | + | } |
|
| 44 | + | for block, i in blocks { |
|
| 45 | + | let instructions: &[il::Instr] = block.instrs; |
|
| 46 | + | let mut links = empty; |
|
| 47 | + | if instructions.len > 0 { |
|
| 48 | + | let term = &instructions[instructions.len - 1]; |
|
| 49 | + | match term { |
|
| 50 | + | case il::Instr::Jmp { target, .. } => { |
|
| 51 | + | set links = try storage.fill(entries[*target], 1); |
|
| 52 | + | }, |
|
| 53 | + | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
|
| 54 | + | let targets = try storage.fill(entries[*thenTarget], 2); |
|
| 55 | + | set targets[1] = entries[*elseTarget]; |
|
| 56 | + | set links = &targets[..]; |
|
| 57 | + | }, |
|
| 58 | + | case il::Instr::Switch { defaultTarget, cases, .. } => { |
|
| 59 | + | let cases: &[il::SwitchCase] = *cases; |
|
| 60 | + | if cases.len == MAX_SUCCESSORS { |
|
| 61 | + | throw alloc::AllocError::OutOfMemory; |
|
| 62 | + | } |
|
| 63 | + | let targets = try storage.fill(entries[*defaultTarget], cases.len + 1); |
|
| 64 | + | for item, j in cases { |
|
| 65 | + | set targets[j + 1] = entries[item.target]; |
|
| 66 | + | } |
|
| 67 | + | set links = &targets[..]; |
|
| 68 | + | }, |
|
| 69 | + | else => {}, |
|
| 70 | + | } |
|
| 71 | + | } |
|
| 72 | + | try! define(&mut builder, entries[i], links); |
|
| 73 | + | } |
|
| 74 | + | return Flow 's { frozen: try! graph::freeze(builder), blocks: &entries[..] }; |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Reserve a block before its outgoing links are known. |
|
| 78 | + | fn reserve 's (builder: &mut graph::Builder 's, index: u32, empty: &'s [Block 's], storage: &Session 's) |
|
| 79 | + | -> Block 's throws (alloc::AllocError) |
|
| 80 | + | { |
|
| 81 | + | let links = try storage.new(empty); |
|
| 82 | + | let token = try graph::reserve(builder, storage); |
|
| 83 | + | return Block 's { token, index, links: &cell *links }; |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Complete a block after all successors have been checked. |
|
| 87 | + | fn define 's (builder: &mut graph::Builder 's, block: Block 's, links: &'s [Block 's]) |
|
| 88 | + | throws (graph::Error) |
|
| 89 | + | { |
|
| 90 | + | try graph::check(builder, block.token); |
|
| 91 | + | for successor in links { |
|
| 92 | + | try graph::check(builder, successor.token); |
|
| 93 | + | } |
|
| 94 | + | set *block.links = links; |
|
| 95 | + | try graph::complete(builder, block.token); |
|
| 96 | + | } |
|
| 97 | + | ||
| 98 | + | /// Borrow the successors of a published block. |
|
| 99 | + | export fn successors 's (flow: &Flow 's, index: u32) -> &'s [Block 's] { |
|
| 100 | + | let block = flow.blocks[index]; |
|
| 101 | + | try! graph::checkFrozen(&flow.frozen, block.token); |
|
| 102 | + | return *block.links; |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | /// Return the dataflow table position retained by a block identity. |
|
| 106 | + | export fn index 's (block: Block 's) -> u32 { |
|
| 107 | + | return block.index; |
|
| 108 | + | } |
lib/std/lang/gen/regalloc/liveness.rad
+11 -42
| 27 | 27 | @test mod tests; |
|
| 28 | 28 | ||
| 29 | 29 | use std::lang::il; |
|
| 30 | 30 | use std::lang::alloc; |
|
| 31 | 31 | use std::lang::gen::bitset; |
|
| 32 | + | use super::flow; |
|
| 32 | 33 | ||
| 33 | 34 | /// Largest register index whose required entry count fits in u32. |
|
| 34 | 35 | constant MAX_REGISTER_INDEX: u32 = 0xfffffffe; |
|
| 35 | 36 | ||
| 36 | 37 | /// Liveness information for a function. |
| 91 | 92 | let block = &blocks[b]; |
|
| 92 | 93 | unsafe { |
|
| 93 | 94 | computeLocalDefsUses(block.params, block.instrs, &mut defs[b * words..(b + 1) * words], &mut uses[b * words..(b + 1) * words]); |
|
| 94 | 95 | } |
|
| 95 | 96 | } |
|
| 96 | - | propagateLiveness(blocks, liveIn, liveOut, defs, uses, words); |
|
| 97 | + | unsafe { |
|
| 98 | + | let topology = try flow::snapshot(blocks, storage); |
|
| 99 | + | propagateLiveness(&topology, blockCount, liveIn, liveOut, defs, uses, words); |
|
| 100 | + | } |
|
| 97 | 101 | return LiveInfo 'scratch { liveIn: &liveIn[..], liveOut: &liveOut[..], defs: &defs[..], uses: &uses[..], words, blockCount, maxReg }; |
|
| 98 | 102 | } |
|
| 99 | 103 | ||
| 100 | 104 | /// Propagate successor uses until every block's live-in row is stable. |
|
| 101 | - | fn propagateLiveness( |
|
| 102 | - | blocks: &[il::Block], |
|
| 105 | + | fn propagateLiveness 'scratch ( |
|
| 106 | + | topology: &flow::Flow 'scratch, |
|
| 107 | + | blockCount: u32, |
|
| 103 | 108 | liveIn: &mut [u32], |
|
| 104 | 109 | liveOut: &mut [u32], |
|
| 105 | 110 | defs: &[u32], |
|
| 106 | 111 | uses: &[u32], |
|
| 107 | 112 | words: u32, |
| 111 | 116 | ||
| 112 | 117 | while changed { |
|
| 113 | 118 | set changed = false; |
|
| 114 | 119 | ||
| 115 | 120 | // Process blocks in reverse order (approximates post-order). |
|
| 116 | - | let mut b = blocks.len; |
|
| 121 | + | let mut b = blockCount; |
|
| 117 | 122 | while b > 0 { |
|
| 118 | 123 | set b -= 1; |
|
| 119 | - | let block = &blocks[b]; |
|
| 120 | - | ||
| 121 | 124 | // Add successor live-in sets directly to this block's live-out set. |
|
| 122 | - | unsafe { |
|
| 123 | - | addSuccessorLiveIn(block.instrs, liveIn, words, &mut liveOut[b * words..(b + 1) * words]); |
|
| 125 | + | for successor in flow::successors(topology, b) { |
|
| 126 | + | unionBlockLiveIn(flow::index(successor), liveIn, words, &mut liveOut[b * words..(b + 1) * words]); |
|
| 124 | 127 | } |
|
| 125 | 128 | ||
| 126 | 129 | if computeAndUpdateLiveIn(&mut liveIn[b * words..(b + 1) * words], &liveOut[b * words..(b + 1) * words], &defs[b * words..(b + 1) * words], &uses[b * words..(b + 1) * words]) { |
|
| 127 | 130 | set changed = true; |
|
| 128 | 131 | } |
| 206 | 209 | if count > *max { |
|
| 207 | 210 | set *max = count; |
|
| 208 | 211 | } |
|
| 209 | 212 | } |
|
| 210 | 213 | ||
| 211 | - | /// Add successor live-in sets to the block's live-out set. |
|
| 212 | - | fn addSuccessorLiveIn(instructions: &[il::Instr], liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
|
| 213 | - | if instructions.len == 0 { |
|
| 214 | - | return; |
|
| 215 | - | } |
|
| 216 | - | let term = &instructions[instructions.len - 1]; |
|
| 217 | - | if let case il::Instr::Switch { cases, .. } = term { |
|
| 218 | - | unsafe { |
|
| 219 | - | mergeSuccessorLiveIn(term, *cases, liveIn, words, liveOut); |
|
| 220 | - | } |
|
| 221 | - | } else { |
|
| 222 | - | mergeSuccessorLiveIn(term, &[], liveIn, words, liveOut); |
|
| 223 | - | } |
|
| 224 | - | } |
|
| 225 | - | ||
| 226 | - | /// Merge live-in rows selected by a terminator and its borrowed switch cases. |
|
| 227 | - | fn mergeSuccessorLiveIn(term: &il::Instr, cases: &[il::SwitchCase], liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
|
| 228 | - | match term { |
|
| 229 | - | case il::Instr::Jmp { target, .. } => |
|
| 230 | - | unionBlockLiveIn(*target, liveIn, words, liveOut), |
|
| 231 | - | case il::Instr::Br { thenTarget, elseTarget, .. } => { |
|
| 232 | - | unionBlockLiveIn(*thenTarget, liveIn, words, liveOut); |
|
| 233 | - | unionBlockLiveIn(*elseTarget, liveIn, words, liveOut); |
|
| 234 | - | }, |
|
| 235 | - | case il::Instr::Switch { defaultTarget, .. } => { |
|
| 236 | - | unionBlockLiveIn(*defaultTarget, liveIn, words, liveOut); |
|
| 237 | - | for c in cases { |
|
| 238 | - | unionBlockLiveIn(c.target, liveIn, words, liveOut); |
|
| 239 | - | } |
|
| 240 | - | }, |
|
| 241 | - | else => {}, |
|
| 242 | - | } |
|
| 243 | - | } |
|
| 244 | - | ||
| 245 | 214 | /// Union a target block's live-in set into the block's live-out set. |
|
| 246 | 215 | fn unionBlockLiveIn(target: u32, liveIn: &[u32], words: u32, liveOut: &mut [u32]) { |
|
| 247 | 216 | bitset::union_(liveOut, &liveIn[target * words..(target + 1) * words]); |
|
| 248 | 217 | } |
|
| 249 | 218 |
lib/std/lang/gen/regalloc/liveness/tests.rad
+49 -0
| 5 | 5 | use std::lang::il; |
|
| 6 | 6 | use std::lang::gen::bitset; |
|
| 7 | 7 | use std::lang::gen::regalloc::spill; |
|
| 8 | 8 | use std::lang::gen::regalloc::assign; |
|
| 9 | 9 | use std::lang::gen::regalloc; |
|
| 10 | + | use std::lang::gen::regalloc::flow; |
|
| 10 | 11 | ||
| 12 | + | /// Frozen topology owns successor tables and preserves cyclic block identities. |
|
| 13 | + | @test unsafe fn testOwnedFlowSnapshot() throws (testing::TestError) { |
|
| 14 | + | let mut cases = [il::SwitchCase { value: 1, target: 1, args: &mut [] }]; |
|
| 15 | + | let mut first = [il::Instr::Switch { val: il::Val::Imm(0), defaultTarget: 0, |
|
| 16 | + | defaultArgs: &mut [], cases: &mut cases[..] }]; |
|
| 17 | + | let mut second = [il::Instr::Jmp { target: 0, args: &mut [] }]; |
|
| 18 | + | let blocks = [block("first", &mut first[..]), block("second", &mut second[..])]; |
|
| 19 | + | static DATA: [u8; 2048] = [0; 2048]; |
|
| 20 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 21 | + | use arena as storage in { |
|
| 22 | + | let frozen = try! flow::snapshot(&blocks[..], &storage); |
|
| 23 | + | set cases[0].target = 0; |
|
| 24 | + | set first[0] = il::Instr::Ret { val: il::Val::Imm(0) }; |
|
| 25 | + | set second[0] = il::Instr::Ret { val: il::Val::Imm(0) }; |
|
| 26 | + | let successors = flow::successors(&frozen, 0); |
|
| 27 | + | assert successors.len == 2; |
|
| 28 | + | assert flow::index(successors[0]) == 0; |
|
| 29 | + | assert flow::index(successors[1]) == 1; |
|
| 30 | + | let back = flow::successors(&frozen, 1); |
|
| 31 | + | assert back.len == 1 and back[0] == successors[0]; |
|
| 32 | + | } |
|
| 33 | + | alloc::reset(&mut arena); |
|
| 34 | + | assert alloc::used(&arena) == 0; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | ||
| 38 | + | /// Every incomplete allocation stage reports failure and permits arena disposal. |
|
| 39 | + | @test unsafe fn testFlowAllocationFailure() throws (testing::TestError) { |
|
| 40 | + | let mut instructions = [il::Instr::Jmp { target: 0, args: &mut [] }]; |
|
| 41 | + | let blocks = [block("loop", &mut instructions[..])]; |
|
| 42 | + | let mut succeeded = false; |
|
| 43 | + | for capacity in 0..257 { |
|
| 44 | + | static DATA: [u8; 256] = [0; 256]; |
|
| 45 | + | let mut arena = alloc::new(&mut DATA[..capacity]); |
|
| 46 | + | use arena as storage in { |
|
| 47 | + | if let frozen = try? flow::snapshot(&blocks[..], &storage) { |
|
| 48 | + | let links = flow::successors(&frozen, 0); |
|
| 49 | + | assert links.len == 1 and flow::index(links[0]) == 0; |
|
| 50 | + | set succeeded = true; |
|
| 51 | + | } else { |
|
| 52 | + | assert not succeeded; |
|
| 53 | + | } |
|
| 54 | + | } |
|
| 55 | + | alloc::reset(&mut arena); |
|
| 56 | + | assert alloc::used(&arena) == 0; |
|
| 57 | + | } |
|
| 58 | + | assert succeeded; |
|
| 59 | + | } |
|
| 11 | 60 | ||
| 12 | 61 | /// Construct a block whose instruction storage belongs to the caller. |
|
| 13 | 62 | unsafe fn block(name: *[u8], instructions: *unsafe mut [il::Instr]) -> il::Block { |
|
| 14 | 63 | return il::Block { label: name, params: &[], instrs: instructions, |
|
| 15 | 64 | locs: &[], preds: &[], loopDepth: 0 }; |
seed/radiance.rv64
+0 -0
Binary file changed.
seed/radiance.rv64.git
+1 -1
| 1 | - | f6689102ee79bee104a4566df03fad7f0ee35cc9814e268a52c2119273f66830 |
|
| 1 | + | 55199dd625f01384eae1694dba6d6c286292a12c466e40b968e8326bdf78f33e |
std.lib
+2 -0
| 6 | 6 | lib/std/io.rad |
|
| 7 | 7 | lib/std/intrinsics.rad |
|
| 8 | 8 | lib/std/sys.rad |
|
| 9 | 9 | lib/std/collections.rad |
|
| 10 | 10 | lib/std/collections/dict.rad |
|
| 11 | + | lib/std/graph.rad |
|
| 11 | 12 | lib/std/sys/unix.rad |
|
| 12 | 13 | lib/std/arch.rad |
|
| 13 | 14 | lib/std/arch/rv64.rad |
|
| 14 | 15 | lib/std/arch/rv64/image.rad |
|
| 15 | 16 | lib/std/arch/rv64/encode.rad |
| 48 | 49 | lib/std/lang/gen/bitset.rad |
|
| 49 | 50 | lib/std/lang/gen/data.rad |
|
| 50 | 51 | lib/std/lang/gen/types.rad |
|
| 51 | 52 | lib/std/lang/gen/regalloc.rad |
|
| 52 | 53 | lib/std/lang/gen/regalloc/liveness.rad |
|
| 54 | + | lib/std/lang/gen/regalloc/flow.rad |
|
| 53 | 55 | lib/std/lang/gen/regalloc/spill.rad |
|
| 54 | 56 | lib/std/lang/gen/regalloc/assign.rad |
|
| 55 | 57 | lib/std/arch/rv64/shared.rad |
|
| 56 | 58 | lib/std/arch/rv64/shared/catalog.rad |
|
| 57 | 59 | lib/std/arch/rv64/atomics.rad |
std.lib.test
+1 -0
| 1 | 1 | lib/std/testing.rad |
|
| 2 | 2 | lib/std/tests.rad |
|
| 3 | + | lib/std/graph/tests.rad |
|
| 3 | 4 | lib/std/char/tests.rad |
|
| 4 | 5 | lib/std/arch/rv64/tests.rad |
|
| 5 | 6 | lib/std/arch/rv64/asm/tests.rad |
|
| 6 | 7 | lib/std/arch/rv64/asm/scanner/tests.rad |
|
| 7 | 8 | lib/std/lang/alloc/tests.rad |
test/reject
added
+33 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Require semantic rejection of one source fixture with its expected diagnostic. |
|
| 3 | + | set -eu |
|
| 4 | + | ||
| 5 | + | source=$1 |
|
| 6 | + | expected=$(sed -n 's|^//! rejects: ||p' "$source") |
|
| 7 | + | test -n "$expected" |
|
| 8 | + | work=$(mktemp -d) |
|
| 9 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 10 | + | ||
| 11 | + | set -- |
|
| 12 | + | if grep -q '^//! session-support$' "$source"; then |
|
| 13 | + | set -- -pkg std |
|
| 14 | + | while IFS= read -r source_module; do |
|
| 15 | + | set -- "$@" -mod "$source_module" |
|
| 16 | + | done < test/support/std.lib |
|
| 17 | + | fi |
|
| 18 | + | set -- "$@" -pkg test -mod "$source" -entry test -dump il |
|
| 19 | + | for source_module in "${source%.rad}"/*.rad; do |
|
| 20 | + | if [ -f "$source_module" ]; then |
|
| 21 | + | set -- "$@" -mod "$source_module" |
|
| 22 | + | fi |
|
| 23 | + | done |
|
| 24 | + | ||
| 25 | + | status=0 |
|
| 26 | + | timeout 10 "${RAD_EMULATOR:-emulator}" -memory-size=385024 -data-size=348160 \ |
|
| 27 | + | -stack-size=1024 -run "${RAD_BIN:-bin/radiance.rv64.dev}" "$@" \ |
|
| 28 | + | >"$work/compiler.log" 2>&1 || status=$? |
|
| 29 | + | if [ "$status" -ne 1 ] || ! grep -F "$source:" "$work/compiler.log" | grep -Fq "error: $expected"; then |
|
| 30 | + | cat "$work/compiler.log" |
|
| 31 | + | echo "error: expected semantic rejection: $expected (exit $status)" |
|
| 32 | + | exit 1 |
|
| 33 | + | fi |
test/run
+13 -0
| 12 | 12 | # package required by session allocation. |
|
| 13 | 13 | # - If `//! returns: N` appears in the file, the test is compiled to |
|
| 14 | 14 | # a binary and executed; the exit code must match N. |
|
| 15 | 15 | # - If `//! rw-data-size: N` appears in the file, the emitted image header's |
|
| 16 | 16 | # rwdata size must match N bytes. |
|
| 17 | + | # - If `//! rejects: TEXT` appears, the compiler must reject the source with |
|
| 18 | + | # that diagnostic. Crashes and timeouts are failures. |
|
| 17 | 19 | ||
| 18 | 20 | RUNNER="test/runner.rv64" |
|
| 19 | 21 | TEST_DIR="test/tests" |
|
| 20 | 22 | EMU="${RAD_EMULATOR:-emulator} -stack-size=1024 -run" |
|
| 21 | 23 | EMU_RUN="${RAD_EMULATOR:-emulator} -no-jit -run" |
| 51 | 53 | *) base="$test" ;; |
|
| 52 | 54 | esac |
|
| 53 | 55 | ||
| 54 | 56 | ril="${base}.ril" |
|
| 55 | 57 | bin="${base}.rv64" |
|
| 58 | + | if grep -q '^//! rejects: ' "$test"; then |
|
| 59 | + | echo -n "test $test rejection ... " |
|
| 60 | + | if sh test/reject "$test"; then |
|
| 61 | + | echo "ok" |
|
| 62 | + | passed=$((passed + 1)) |
|
| 63 | + | else |
|
| 64 | + | echo "FAILED" |
|
| 65 | + | failed=$((failed + 1)) |
|
| 66 | + | fi |
|
| 67 | + | continue |
|
| 68 | + | fi |
|
| 56 | 69 | # IL check: run the runner if a .ril file exists. |
|
| 57 | 70 | if [ -f "$ril" ]; then |
|
| 58 | 71 | if grep -q '^//! session-support$' "$test"; then |
|
| 59 | 72 | echo -n "test $test ... " |
|
| 60 | 73 | if test/package-golden "$test"; then |
test/support/std.lib
+1 -0
| 1 | 1 | test/support/std.rad |
|
| 2 | + | test/support/std/graph.rad |
|
| 2 | 3 | test/support/std/lang.rad |
|
| 3 | 4 | test/support/std/lang/alloc.rad |
test/support/std.rad
+1 -0
| 1 | 1 | //! Minimal standard package root for binary lowering fixtures. |
|
| 2 | 2 | ||
| 3 | 3 | export mod lang; |
|
| 4 | + | export mod graph; |
test/support/std/graph.rad
added
+1 -0
| 1 | + | ../../../lib/std/graph.rad |
test/tests/graph.capability.rad
added
+26 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! session-support |
|
| 3 | + | //! Graph construction authority is consumed before shared publication. |
|
| 4 | + | ||
| 5 | + | use std::graph; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | ||
| 8 | + | /// Complete two reserved identities and retain them through publication. |
|
| 9 | + | @default fn main() -> u32 { |
|
| 10 | + | static DATA: [u8; 1024] = [0; 1024]; |
|
| 11 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 12 | + | use arena as storage in { |
|
| 13 | + | let mut builder = try! graph::new(&storage); |
|
| 14 | + | let first = try! graph::reserve(&mut builder, &storage); |
|
| 15 | + | let second = try! graph::reserve(&mut builder, &storage); |
|
| 16 | + | assert first <> second; |
|
| 17 | + | assert graph::pending(&builder) == 2; |
|
| 18 | + | try! graph::complete(&mut builder, first); |
|
| 19 | + | try! graph::complete(&mut builder, second); |
|
| 20 | + | let frozen = try! graph::freeze(builder); |
|
| 21 | + | try! graph::checkFrozen(&frozen, first); |
|
| 22 | + | try! graph::checkFrozen(&frozen, second); |
|
| 23 | + | assert graph::len(&frozen) == 2; |
|
| 24 | + | } |
|
| 25 | + | return 0; |
|
| 26 | + | } |
test/tests/graph.freeze.rad
added
+53 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! session-support |
|
| 3 | + | //! Typed cyclic graph construction and publication. |
|
| 4 | + | ||
| 5 | + | use std::graph; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | mod model; |
|
| 8 | + | ||
| 9 | + | /// Construct forward references, reject foreign links, and publish a cycle. |
|
| 10 | + | @default fn main() -> u32 { |
|
| 11 | + | static DATA: [u8; 4096] = [0; 4096]; |
|
| 12 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 13 | + | use arena as storage in { |
|
| 14 | + | let mut builder = try! graph::new(&storage); |
|
| 15 | + | let first = try! model::reserve(&mut builder, &storage); |
|
| 16 | + | let second = try! model::reserve(&mut builder, &storage); |
|
| 17 | + | let third = try! model::reserve(&mut builder, &storage); |
|
| 18 | + | let isolated = try! model::reserve(&mut builder, &storage); |
|
| 19 | + | let mut other = try! graph::new(&storage); |
|
| 20 | + | let foreign = try! model::reserve(&mut other, &storage); |
|
| 21 | + | let mut rejected = false; |
|
| 22 | + | try model::define(&mut builder, first, 99, foreign) catch error { |
|
| 23 | + | assert error == graph::Error::ForeignNode; |
|
| 24 | + | set rejected = true; |
|
| 25 | + | }; |
|
| 26 | + | assert rejected; |
|
| 27 | + | assert graph::pending(&builder) == 4; |
|
| 28 | + | try! model::define(&mut builder, first, 10, second); |
|
| 29 | + | try! model::define(&mut builder, second, 20, third); |
|
| 30 | + | try! model::define(&mut builder, third, 30, first); |
|
| 31 | + | try! model::define(&mut builder, isolated, 40, isolated); |
|
| 32 | + | try! model::define(&mut builder, first, 11, second); |
|
| 33 | + | let frozen = try! graph::freeze(builder); |
|
| 34 | + | assert graph::len(&frozen) == 4; |
|
| 35 | + | let a = try! model::read(&frozen, first); |
|
| 36 | + | let b = try! model::read(&frozen, a.next); |
|
| 37 | + | let c = try! model::read(&frozen, b.next); |
|
| 38 | + | assert a.value + b.value + c.value == 61; |
|
| 39 | + | assert c.next == first; |
|
| 40 | + | let d = try! model::read(&frozen, isolated); |
|
| 41 | + | assert d.value == 40 and d.next == isolated; |
|
| 42 | + | set rejected = false; |
|
| 43 | + | try model::read(&frozen, foreign) catch error { |
|
| 44 | + | assert error == graph::Error::ForeignNode; |
|
| 45 | + | set rejected = true; |
|
| 46 | + | }; |
|
| 47 | + | assert rejected; |
|
| 48 | + | try! model::define(&mut other, foreign, 50, foreign); |
|
| 49 | + | let otherFrozen = try! graph::freeze(other); |
|
| 50 | + | assert (try! model::read(&otherFrozen, foreign)).value == 50; |
|
| 51 | + | } |
|
| 52 | + | return 0; |
|
| 53 | + | } |
test/tests/graph.freeze/model.rad
added
+57 -0
| 1 | + | //! Typed definitions whose links require graph construction authority. |
|
| 2 | + | ||
| 3 | + | use std::graph; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | ||
| 6 | + | /// Definition payload and its direct successor. |
|
| 7 | + | export record Value: 'g + Copy { |
|
| 8 | + | /// Number stored by this definition. |
|
| 9 | + | value: u32, |
|
| 10 | + | /// Next definition in the graph. |
|
| 11 | + | next: Definition 'g, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | /// Initialization state of a definition slot. |
|
| 15 | + | union State: 'g + Copy { |
|
| 16 | + | /// Reserved forward reference. |
|
| 17 | + | Pending, |
|
| 18 | + | /// Complete payload with a checked outgoing link. |
|
| 19 | + | Ready(Value 'g), |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | /// Stable typed pointer to a definition controlled by one graph builder. |
|
| 23 | + | export opaque record Definition: 'g + Copy { |
|
| 24 | + | /// Private identity used to check graph authority. |
|
| 25 | + | token: graph::Node 'g, |
|
| 26 | + | /// Payload that only the defining module can change. |
|
| 27 | + | contents: &'g cell State 'g, |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Allocate a forward reference without publishing an incomplete payload. |
|
| 31 | + | export fn reserve 'g (builder: &mut graph::Builder 'g, storage: &Session 'g) |
|
| 32 | + | -> Definition 'g throws (alloc::AllocError) |
|
| 33 | + | { |
|
| 34 | + | let contents = try storage.new(State 'g::Pending); |
|
| 35 | + | let token = try graph::reserve(builder, storage); |
|
| 36 | + | return Definition 'g { token, contents: &cell *contents }; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Write a complete payload after checking the node and its outgoing link. |
|
| 40 | + | export fn define 'g (builder: &mut graph::Builder 'g, node: Definition 'g, |
|
| 41 | + | value: u32, next: Definition 'g) throws (graph::Error) |
|
| 42 | + | { |
|
| 43 | + | try graph::check(builder, node.token); |
|
| 44 | + | try graph::check(builder, next.token); |
|
| 45 | + | set *node.contents = State 'g::Ready(Value 'g { value, next }); |
|
| 46 | + | try graph::complete(builder, node.token); |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Read a complete definition through its graph's shared authority. |
|
| 50 | + | export fn read 'g (frozen: &graph::Frozen 'g, node: Definition 'g) |
|
| 51 | + | -> Value 'g throws (graph::Error) |
|
| 52 | + | { |
|
| 53 | + | try graph::checkFrozen(frozen, node.token); |
|
| 54 | + | let case State 'g::Ready(value) = *node.contents |
|
| 55 | + | else panic "read: incomplete definition"; |
|
| 56 | + | return value; |
|
| 57 | + | } |
test/tests/graph.reject.borrow.rad
added
+11 -0
| 1 | + | //! session-support |
|
| 2 | + | //! rejects: conflicting borrow of 'builder' |
|
| 3 | + | ||
| 4 | + | use std::graph; |
|
| 5 | + | ||
| 6 | + | /// A live construction borrow prevents publication. |
|
| 7 | + | fn reuse 'g (builder: graph::Builder 'g) { |
|
| 8 | + | let borrowed = &builder; |
|
| 9 | + | let frozen = try! graph::freeze(builder); |
|
| 10 | + | graph::pending(borrowed); |
|
| 11 | + | } |
test/tests/graph.reject.conditional.rad
added
+18 -0
| 1 | + | //! session-support |
|
| 2 | + | //! rejects: affine value used after move: 'builder' |
|
| 3 | + | ||
| 4 | + | use std::graph; |
|
| 5 | + | use std::lang::alloc; |
|
| 6 | + | ||
| 7 | + | /// Conditional bindings retain exclusive construction ownership. |
|
| 8 | + | @default fn main() -> u32 { |
|
| 9 | + | static DATA: [u8; 64] = [0; 64]; |
|
| 10 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 11 | + | use arena as storage in { |
|
| 12 | + | if let builder = try? graph::new(&storage) { |
|
| 13 | + | let frozen = try! graph::freeze(builder); |
|
| 14 | + | graph::pending(&builder); |
|
| 15 | + | } |
|
| 16 | + | } |
|
| 17 | + | return 0; |
|
| 18 | + | } |
test/tests/graph.reject.escape.rad
added
+15 -0
| 1 | + | //! session-support |
|
| 2 | + | //! rejects: type mismatch: expected record { owner: &'g u8, initialized: &'g cell bool } 'g, got record { owner: &'storage u8, initialized: &'storage cell bool } 'storage |
|
| 3 | + | ||
| 4 | + | use std::graph; |
|
| 5 | + | use std::lang::alloc; |
|
| 6 | + | ||
| 7 | + | /// A node cannot outlive the session that owns its storage. |
|
| 8 | + | fn escape 'g () -> graph::Node 'g { |
|
| 9 | + | static DATA: [u8; 64] = [0; 64]; |
|
| 10 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 11 | + | use arena as storage in { |
|
| 12 | + | let mut builder = try! graph::new(&storage); |
|
| 13 | + | return try! graph::reserve(&mut builder, &storage); |
|
| 14 | + | } |
|
| 15 | + | } |
test/tests/graph.reject.move.rad
added
+10 -0
| 1 | + | //! session-support |
|
| 2 | + | //! rejects: affine value used after move: 'builder' |
|
| 3 | + | ||
| 4 | + | use std::graph; |
|
| 5 | + | ||
| 6 | + | /// Construction authority cannot be used after publication. |
|
| 7 | + | fn reuse 'g (builder: graph::Builder 'g) { |
|
| 8 | + | let frozen = try! graph::freeze(builder); |
|
| 9 | + | graph::pending(&builder); |
|
| 10 | + | } |
test/tests/graph.reject.private.rad
added
+9 -0
| 1 | + | //! session-support |
|
| 2 | + | //! rejects: opaque record representation is private to its defining module |
|
| 3 | + | ||
| 4 | + | use std::graph; |
|
| 5 | + | ||
| 6 | + | /// Clients cannot modify a node's private completion state. |
|
| 7 | + | fn extract 'g (node: graph::Node 'g) { |
|
| 8 | + | set *node.initialized = false; |
|
| 9 | + | } |