1import core/mem
2import core/vec
3
4--- Ledger entry storing an allocated address and its byte extent. Used by
5--- bounded regions in caller-supplied initialized storage.
6public allocation: type = struct
7 address: usize
8 extent: usize
9end allocation
10
11bounded_ledger: type = struct
12 values: []mut allocation
13 used: usize
14end bounded_ledger
15
16region_value: type (provider: type) = struct
17 parent: ptr mut provider
18 allocations: variant
19 empty |
20 recorded: (values: vec.list(allocation)) |
21 bounded: (values: bounded_ledger)
22 end allocations
23end region_value
24
25--- Allocator that records payload allocations through a borrowed parent.
26--- Releasing the region frees all recorded payloads; the parent must outlive
27--- the region.
28public region: type (provider: type) = region_value(provider)
29
30--- Create a region whose ledger grows through its parent allocator. Ledger
31--- allocation failure rolls back the just-allocated payload.
32public new: (provider: type is mem.allocator, parent: ptr mut provider)
33 -> (value: region(provider) from parent) =
34 value = (parent: parent, allocations: empty)
35end new
36
37--- Create a region with a bounded caller-owned ledger. Each live payload
38--- needs one entry; an exhausted ledger reports `mem.out_of_memory` and
39--- returns the new payload to the parent.
40---
41--- The caller reserves one initialized record per simultaneously live payload.
42--- No ledger request is sent to the parent in this form.
43public over: (provider: type is mem.allocator,
44 parent: ptr mut provider, records: []mut allocation)
45 -> (value: region(provider) from parent, records) =
46 value = (parent: parent,
47 allocations: bounded(values: (values: records, used: 0)))
48end over
49
50region_alloc: (provider: type is mem.allocator,
51 inout state: region(provider), size: usize, alignment: usize)
52 -> (block: ptr mut u8) ! mem.out_of_memory =
53 block = try mem.allocate(state.parent.val, size, alignment)
54 record: allocation = (address: usize(block), extent: size)
55 record_allocation(state, record) else (problem)
56 mem.free(state.parent.val, block, size)
57 fail problem
58 end
59end region_alloc
60
61-- The constructor has no independent ledger destination to hide behind its
62-- `from parent` contract. Materialize the list only when recording a payload.
63record_allocation: (provider: type is mem.allocator,
64 inout state: region(provider), record: allocation)
65 -> none ! mem.out_of_memory =
66 match state.allocations
67 empty: begin
68 mut records := vec.new(item: allocation)
69 try vec.push(records, state.parent.val, record)
70 state.allocations = recorded(values: records)
71 end
72 recorded(inout records): begin
73 try vec.push(records, state.parent.val, record)
74 end
75 bounded(inout records): begin
76 values: []mut allocation = records.values
77 fail mem.out_of_memory when records.used == lenof values
78 records.values[records.used] = record
79 inc records.used
80 end
81 end match
82end record_allocation
83
84region_free: (provider: type is mem.allocator, inout state: region(provider),
85 block: ptr mut u8, size: usize) -> none =
86 _ = state.parent
87 _ = block
88 _ = size
89end region_free
90
91region_grow: (provider: type,
92 inout state: region(provider), block: ptr mut u8,
93 old_size: usize, new_size: usize, alignment: usize)
94 -> (grown: bool) =
95 _ = state.parent
96 _ = block
97 _ = old_size
98 _ = new_size
99 _ = alignment
100 grown = false
101end region_grow
102
103(provider: type is mem.allocator) region_value(provider) is mem.allocator
104 (alloc: region_alloc, grow: region_grow, free: region_free)
105
106--- Free recorded payloads in reverse order and release any dynamic ledger
107--- backing. End every payload use first; a bounded caller ledger remains
108--- available for reuse.
109---
110--- End every alias before this call. Every payload and ledger allocation
111--- is returned to the parent, including failed-construction payloads. A
112--- monotonic parent keeps consumed backing until its own explicit reset.
113public release: (provider: type is mem.allocator,
114 inout state: region(provider)) -> none =
115 match state.allocations
116 empty: return
117 recorded(inout records): release_records(state.parent, records)
118 bounded(inout records): begin
119 release_bounded(state.parent, records.values, records.used)
120 records.used = 0
121 return
122 end
123 end match
124 state.allocations = empty
125end release
126
127release_bounded: (provider: type is mem.allocator, parent: ptr mut provider,
128 records: []mut allocation, used: usize) -> none =
129 mut remaining: usize = used
130 while remaining > 0 do
131 dec remaining
132 record: allocation = records[remaining]
133 block: ptr mut u8 = ptr(record.address)
134 mem.free(parent.val, block, record.extent)
135 end while
136end release_bounded
137
138-- Keep the ledger view and parent capability as separate parameters while
139-- freeing payloads; the payload alias does not expose the whole region.
140release_records: (provider: type is mem.allocator, parent: ptr mut provider,
141 inout records: vec.list(allocation)) -> none =
142 mut remaining: usize = vec.length(records)
143 while remaining > 0 do
144 dec remaining
145 record: allocation = record_at(records, remaining)
146 block: ptr mut u8 = ptr(record.address)
147 mem.free(parent.val, block, record.extent)
148 end while
149 vec.release(records, parent.val)
150end release_records
151
152-- The extent-bearing record is reference-free. Read it within the helper's
153-- view scope before calling the parent, then dispose the ledger as a whole.
154record_at: (records: vec.list(allocation), index: usize) -> (value: allocation) =
155 initialized := vec.used(records)
156 value = initialized[index]
157end record_at