Landin library reference source

core/region/region.ldn

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