nomen-lang 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/audit_runtime.c +12 -2
- package/core/System/Arena.nm +14 -5
- package/core/System/Array.nm +1 -1
- package/core/System/Atomic.nm +3 -3
- package/core/System/BigInt.nm +182 -23
- package/core/System/Buffer.nm +392 -33
- package/core/System/ClassBuffer.nm +145 -13
- package/core/System/Controls/Alert.nm +3 -3
- package/core/System/Controls/App.nm +17 -17
- package/core/System/Controls/Button.nm +2 -2
- package/core/System/Controls/CheckBox.nm +3 -3
- package/core/System/Controls/ColorPicker.nm +2 -2
- package/core/System/Controls/ComboBox.nm +2 -2
- package/core/System/Controls/Container.nm +39 -33
- package/core/System/Controls/DatePicker.nm +1 -1
- package/core/System/Controls/Form.nm +2 -2
- package/core/System/Controls/GroupBox.nm +2 -2
- package/core/System/Controls/ImageView.nm +2 -2
- package/core/System/Controls/Layout.nm +5 -5
- package/core/System/Controls/LevelIndicator.nm +2 -2
- package/core/System/Controls/ListView.nm +2 -2
- package/core/System/Controls/Menu.nm +2 -2
- package/core/System/Controls/NumberInput.nm +3 -3
- package/core/System/Controls/OtpInput.nm +2 -2
- package/core/System/Controls/OutlineView.nm +2 -2
- package/core/System/Controls/Pagination.nm +14 -14
- package/core/System/Controls/Popover.nm +2 -2
- package/core/System/Controls/Progress.nm +2 -2
- package/core/System/Controls/Radio.nm +3 -3
- package/core/System/Controls/Scrollable.nm +1 -1
- package/core/System/Controls/SearchField.nm +2 -2
- package/core/System/Controls/SecureField.nm +2 -2
- package/core/System/Controls/Segmented.nm +2 -2
- package/core/System/Controls/Select.nm +2 -2
- package/core/System/Controls/Separator.nm +2 -2
- package/core/System/Controls/Sheet.nm +2 -2
- package/core/System/Controls/Slider.nm +2 -2
- package/core/System/Controls/Spinner.nm +5 -5
- package/core/System/Controls/SplitView.nm +3 -3
- package/core/System/Controls/Stepper.nm +2 -2
- package/core/System/Controls/Switch.nm +2 -2
- package/core/System/Controls/Table.nm +2 -2
- package/core/System/Controls/Tabs.nm +2 -2
- package/core/System/Controls/Text.nm +2 -2
- package/core/System/Controls/TextBox.nm +3 -3
- package/core/System/Controls/TextView.nm +2 -2
- package/core/System/Controls/Toggle.nm +3 -3
- package/core/System/Controls/Toolbar.nm +2 -2
- package/core/System/Controls/Window.nm +12 -5
- package/core/System/Fiber.nm +16 -3
- package/core/System/Graph.nm +2 -2
- package/core/System/LinkedList.nm +13 -4
- package/core/System/List.nm +76 -17
- package/core/System/Map.nm +179 -21
- package/core/System/Mutex.nm +4 -4
- package/core/System/Random.nm +6 -7
- package/core/System/Set.nm +99 -23
- package/core/System/StableList.nm +371 -0
- package/core/System/Stream/Directory.nm +2 -2
- package/core/System/Stream/File.nm +18 -18
- package/core/System/Stream/Http.nm +5 -5
- package/core/System/Stream/Tcp.nm +5 -5
- package/core/System/String.nm +10 -5
- package/core/System/StringBuilder.nm +223 -12
- package/core/System/Task.nm +56 -64
- package/core/System/Test.nm +18 -18
- package/core/System/Text/CharIndex.nm +9 -9
- package/core/System/Text/Chars.nm +1 -1
- package/core/System/Text/Json.nm +72 -58
- package/core/System/Text/JsonTree.nm +126 -98
- package/core/System/Text/Regex.nm +43 -38
- package/core/System/Text/Utf8.nm +5 -5
- package/core/System/Thread.nm +26 -3
- package/core/System/Tree.nm +19 -3
- package/core/System/float.nm +36 -22
- package/core/System/int.nm +80 -28
- package/core/System/int16.nm +11 -0
- package/core/System/int32.nm +11 -0
- package/core/System/int64.nm +11 -0
- package/core/System/int8.nm +11 -0
- package/core/System/uint.nm +11 -0
- package/core/System/uint16.nm +11 -0
- package/core/System/uint32.nm +11 -0
- package/core/System/uint64.nm +11 -0
- package/core/System/uint8.nm +11 -0
- package/core/docs/System/StringBuilder.md +6 -0
- package/core/docs/System/Text/JsonTree.md +3 -2
- package/dist/index.mjs +37622 -30603
- package/package.json +1 -1
- package/src/bench_loop.nm +9 -9
package/core/System/Set.nm
CHANGED
|
@@ -6,26 +6,48 @@
|
|
|
6
6
|
* The capacity is always a power of two (initial 8, doubling on rehash), so
|
|
7
7
|
* slot indexing is a mask (`hash & (cap - 1)`) — correct for negative hashes
|
|
8
8
|
* too (two's-complement `&` already lands in `[0, cap)`).
|
|
9
|
+
*
|
|
10
|
+
* Each slot carries its value's hash (truncated to 32 bits) in a parallel
|
|
11
|
+
* `hashes` buffer. Probes compare the stored hash before the value itself
|
|
12
|
+
* (the Swiss-table short-circuit — the `==` only runs on a hash match, and a
|
|
13
|
+
* false match still falls through to `==`, so truncation costs nothing);
|
|
14
|
+
* rehash re-inserts hash-free, reading the stored word instead of calling
|
|
15
|
+
* `value.hash()` again.
|
|
9
16
|
**/
|
|
10
17
|
pub mv struct Set<T: Hashable + Equatable> {
|
|
11
18
|
var length = 0
|
|
12
19
|
var slots = Buffer<T>()
|
|
13
20
|
var used = Buffer<int>()
|
|
21
|
+
var hashes = Buffer<int>()
|
|
14
22
|
|
|
15
|
-
pub func add = (ref self, mv T value) {
|
|
23
|
+
pub func add = (ref self { hashes, slots, used }, mv T value) {
|
|
16
24
|
var int cap = self.slots.cap
|
|
17
25
|
if cap == 0 {
|
|
18
26
|
cap = self.slots.alloc(8)
|
|
19
27
|
self.used.alloc_u32(8)
|
|
28
|
+
self.hashes.alloc_u32(8)
|
|
20
29
|
}
|
|
21
30
|
if self.length * 4 >= cap * 3 {
|
|
22
31
|
self.rehash(cap * 2)
|
|
23
32
|
cap = self.slots.cap
|
|
24
33
|
}
|
|
25
|
-
var
|
|
34
|
+
var uint32 h = value.hash() as uint32
|
|
35
|
+
var int idx = self.find_slot(value, h, cap)
|
|
26
36
|
if self.used.load_u32(idx) == 0 {
|
|
27
37
|
self.used.store_u32(idx, 1)
|
|
28
|
-
|
|
38
|
+
// The value arrives owned (`mv T value`): a value-struct element
|
|
39
|
+
// moves into the fresh slot (store_take — the caller's storage is
|
|
40
|
+
// zeroed, no deep copy) when it OWNS resources; a non-owning value
|
|
41
|
+
// struct is a legal bare argument and keeps the copying store, a
|
|
42
|
+
// string's by-value pair keeps the deep-copying store (the caller
|
|
43
|
+
// still owns its buffer), and a class/trait handle is a pointer
|
|
44
|
+
// store either way.
|
|
45
|
+
if T_TAKES_OWNERSHIP {
|
|
46
|
+
self.slots.store_take(idx, value)
|
|
47
|
+
} else {
|
|
48
|
+
self.slots.store(idx, value)
|
|
49
|
+
}
|
|
50
|
+
self.hashes.store_u32(idx, h)
|
|
29
51
|
self.length += 1
|
|
30
52
|
}
|
|
31
53
|
}
|
|
@@ -35,7 +57,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
35
57
|
if cap == 0 {
|
|
36
58
|
return false
|
|
37
59
|
}
|
|
38
|
-
var int idx = self.find_slot(value, cap)
|
|
60
|
+
var int idx = self.find_slot(value, value.hash() as uint32, cap)
|
|
39
61
|
return self.used.load_u32(idx) != 0
|
|
40
62
|
}
|
|
41
63
|
|
|
@@ -44,7 +66,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
44
66
|
if cap == 0 {
|
|
45
67
|
return 0
|
|
46
68
|
}
|
|
47
|
-
var int idx = self.find_slot(key, cap)
|
|
69
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
48
70
|
if self.used.load_u32(idx) == 0 {
|
|
49
71
|
return 0
|
|
50
72
|
}
|
|
@@ -58,7 +80,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
58
80
|
if cap == 0 {
|
|
59
81
|
return fallback
|
|
60
82
|
}
|
|
61
|
-
var int idx = self.find_slot(key, cap)
|
|
83
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
62
84
|
if self.used.load_u32(idx) == 0 {
|
|
63
85
|
return fallback
|
|
64
86
|
}
|
|
@@ -71,19 +93,19 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
71
93
|
if cap == 0 {
|
|
72
94
|
panic("value not found in set")
|
|
73
95
|
}
|
|
74
|
-
var int idx = self.find_slot(key, cap)
|
|
96
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
75
97
|
if self.used.load_u32(idx) == 0 {
|
|
76
98
|
panic("value not found in set")
|
|
77
99
|
}
|
|
78
100
|
return self.slots.load(idx)
|
|
79
101
|
}
|
|
80
102
|
|
|
81
|
-
pub func remove = (ref self, T value) {
|
|
103
|
+
pub func remove = (ref self { hashes, slots, used }, T value) {
|
|
82
104
|
var int cap = self.slots.cap
|
|
83
105
|
if cap == 0 {
|
|
84
106
|
return
|
|
85
107
|
}
|
|
86
|
-
var int idx = self.find_slot(value, cap)
|
|
108
|
+
var int idx = self.find_slot(value, value.hash() as uint32, cap)
|
|
87
109
|
if self.used.load_u32(idx) == 0 {
|
|
88
110
|
return
|
|
89
111
|
}
|
|
@@ -96,7 +118,9 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
96
118
|
// so entries past a non-moveable one are not stranded behind an empty
|
|
97
119
|
// slot (which would make find_slot miss them). No tombstones, so
|
|
98
120
|
// find_slot/get/has/add are unchanged, and the cost is the local cluster
|
|
99
|
-
// length — not a full O(cap) rehash on every delete.
|
|
121
|
+
// length — not a full O(cap) rehash on every delete. `home` reads the
|
|
122
|
+
// stored hash word (no re-hash per shifted entry), and the word moves
|
|
123
|
+
// with the entry — find_slot's hash short-circuit trusts it.
|
|
100
124
|
var int gap = idx
|
|
101
125
|
var int k = idx
|
|
102
126
|
while true {
|
|
@@ -107,7 +131,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
107
131
|
if self.used.load_u32(k) == 0 {
|
|
108
132
|
break
|
|
109
133
|
}
|
|
110
|
-
var int home = (self.
|
|
134
|
+
var int home = (self.hashes.load_u32(k) as int) & (cap - 1)
|
|
111
135
|
var int dgap = gap - home
|
|
112
136
|
if dgap < 0 {
|
|
113
137
|
dgap = dgap + cap
|
|
@@ -123,34 +147,68 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
123
147
|
// pointer at the vacated slot (leaked once used.store marks it
|
|
124
148
|
// empty; the buffer destroy skips unused slots).
|
|
125
149
|
self.slots.shift(gap, k)
|
|
150
|
+
self.hashes.store_u32(gap, self.hashes.load_u32(k))
|
|
126
151
|
self.used.store_u32(gap, 1)
|
|
127
152
|
gap = k
|
|
128
153
|
}
|
|
129
154
|
}
|
|
155
|
+
if T_NEEDS_STRDUP {
|
|
156
|
+
if gap == idx {
|
|
157
|
+
// No entry shifted: the removed entry still owns its strdup'd
|
|
158
|
+
// bytes in its slot (a shift's first move would have
|
|
159
|
+
// reclaimed them — see shift_T above). destroy_at frees the
|
|
160
|
+
// buffer and zeroes the slot: a later add() that lands in
|
|
161
|
+
// this hole stores over the bytes assuming a fresh slot, and
|
|
162
|
+
// a stranded-then-overwritten entry leaks.
|
|
163
|
+
self.slots.destroy_at(idx)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if T_IS_VALUE_STRUCT {
|
|
167
|
+
if gap == idx {
|
|
168
|
+
// Same hole for a value-struct element: reclaim the slot's
|
|
169
|
+
// owned fields (destroy_at frees and zeroes; scalars-only
|
|
170
|
+
// structs reclaim nothing and the call is a no-op).
|
|
171
|
+
self.slots.destroy_at(idx)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
130
174
|
self.used.store_u32(gap, 0)
|
|
131
175
|
self.length = self.length - 1
|
|
132
176
|
}
|
|
133
177
|
|
|
134
|
-
// An independent copy: a fresh Set whose
|
|
135
|
-
// `
|
|
136
|
-
// and entries.
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
178
|
+
// An independent copy: a fresh Set whose member slabs are fresh (`slots`
|
|
179
|
+
// deep-copied per element, `used`/`hashes` copied word by word), with the
|
|
180
|
+
// same length and entries. The u32-word buffers are copied through their
|
|
181
|
+
// own primitives — `Buffer.copy` is T_SIZE-aware (8-byte loads for
|
|
182
|
+
// `int`), which would read past a 4-byte-per-slot `alloc_u32` slab. Not
|
|
183
|
+
// available for class/trait elements (the member routes to ClassBuffer,
|
|
184
|
+
// which has no copy — the checker rejects the call; the shape is also
|
|
185
|
+
// unreachable today since `T: Hashable + Equatable` cannot be a trait
|
|
186
|
+
// without trait-conformance syntax).
|
|
140
187
|
pub func copy = (self, out Set<T>) {
|
|
141
188
|
var dst = Set<T>()
|
|
142
189
|
dst.slots = self.slots.copy()
|
|
143
|
-
dst.used
|
|
190
|
+
dst.used.alloc_u32(self.slots.cap)
|
|
191
|
+
dst.hashes.alloc_u32(self.slots.cap)
|
|
192
|
+
var i = 0
|
|
193
|
+
while i < self.slots.cap; i += 1 {
|
|
194
|
+
dst.used.store_u32(i, self.used.load_u32(i))
|
|
195
|
+
dst.hashes.store_u32(i, self.hashes.load_u32(i))
|
|
196
|
+
}
|
|
144
197
|
dst.length = self.length
|
|
145
198
|
return dst
|
|
146
199
|
}
|
|
147
200
|
|
|
148
|
-
func find_slot = (self, T value, int cap, out int: out >= 0 && out < cap) {
|
|
201
|
+
func find_slot = (self, T value, uint32 h, int cap, out int: out >= 0 && out < cap) {
|
|
149
202
|
// cap is a power of two: `hash mod cap` via a mask, non-negative
|
|
150
203
|
// for any hash — no adjustment needed.
|
|
151
|
-
var int idx = (
|
|
204
|
+
var int idx = (h as int) & (cap - 1)
|
|
152
205
|
while self.used.load_u32(idx) != 0 {
|
|
153
|
-
|
|
206
|
+
// The stored-hash word short-circuits the value compare: a probed
|
|
207
|
+
// slot whose hash differs (every step of a collision walk past
|
|
208
|
+
// the home slot) skips the value load and `==`. A hash match
|
|
209
|
+
// still falls through to `==`, so the 32-bit truncation costs
|
|
210
|
+
// nothing.
|
|
211
|
+
if self.hashes.load_u32(idx) == h && self.slots.load(idx) == value {
|
|
154
212
|
return idx
|
|
155
213
|
}
|
|
156
214
|
idx += 1
|
|
@@ -161,13 +219,20 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
161
219
|
return idx
|
|
162
220
|
}
|
|
163
221
|
|
|
164
|
-
|
|
222
|
+
// Rehash hash-free: the stored per-slot hash words move with the entries,
|
|
223
|
+
// so no `value.hash()` runs here and the insertion probe needs no value
|
|
224
|
+
// compares (values arrive unique — they came from a valid set). The old
|
|
225
|
+
// form re-inserted via `self.add(mv v)`, re-hashing every value and
|
|
226
|
+
// re-comparing values on every probed slot.
|
|
227
|
+
func rehash = (ref self { hashes, slots, used }, int new_cap) {
|
|
165
228
|
var Buffer<T> old_slots = mv self.slots swap Buffer<T>()
|
|
166
229
|
var Buffer<int> old_used = mv self.used swap Buffer<int>()
|
|
230
|
+
var Buffer<int> old_hashes = mv self.hashes swap Buffer<int>()
|
|
167
231
|
var int old_cap = old_slots.cap
|
|
168
232
|
|
|
169
233
|
self.slots.alloc(new_cap)
|
|
170
234
|
self.used.alloc_u32(new_cap)
|
|
235
|
+
self.hashes.alloc_u32(new_cap)
|
|
171
236
|
self.length = 0
|
|
172
237
|
|
|
173
238
|
var i = 0
|
|
@@ -182,7 +247,18 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
182
247
|
// once the new buffer has made its own copy — passing it inline
|
|
183
248
|
// would orphan the move_T result.
|
|
184
249
|
var T v = old_slots.move(i)
|
|
185
|
-
|
|
250
|
+
var uint32 h = old_hashes.load_u32(i)
|
|
251
|
+
var int idx = (h as int) & (new_cap - 1)
|
|
252
|
+
while self.used.load_u32(idx) != 0 {
|
|
253
|
+
idx += 1
|
|
254
|
+
if idx >= new_cap {
|
|
255
|
+
idx = 0
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
self.used.store_u32(idx, 1)
|
|
259
|
+
self.slots.store(idx, v)
|
|
260
|
+
self.hashes.store_u32(idx, h)
|
|
261
|
+
self.length += 1
|
|
186
262
|
}
|
|
187
263
|
}
|
|
188
264
|
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A grow-only, ordered list whose element borrows survive `push` — including
|
|
3
|
+
* interior slot ADDRESSES (stable grow-only arrays; v2).
|
|
4
|
+
*
|
|
5
|
+
* Storage is CHUNKED so that no slot address ever moves: chunk 0 is the
|
|
6
|
+
* `items` Buffer, allocated once at exactly 256 slots and never resized;
|
|
7
|
+
* chunks 1.. live in the `chunks` directory as raw slab pointers (each
|
|
8
|
+
* `calloc`'d once at 256 slots). The directory itself may realloc — it holds
|
|
9
|
+
* slab ADDRESSES, which survive the move. That is what lets `slot_ptr`
|
|
10
|
+
* exist: `ref T x = sl.at(i)` binds the slot itself, the checker's `push`
|
|
11
|
+
* exemption keeps the alias alive across pushes, and writes through it land
|
|
12
|
+
* in the list. The list is still append-only — no `pop`/`set`/`remove_at` —
|
|
13
|
+
* because displacing an element would invalidate its borrows.
|
|
14
|
+
*
|
|
15
|
+
* The tail pieces (store/load/destroy for directory chunks) delegate to
|
|
16
|
+
* Buffer's mono'd methods through a stack `Buffer_T` view — `load_from` is a
|
|
17
|
+
* bitwise slot load (the borrow form; never the strdup-copy the
|
|
18
|
+
* owning-specialized `load` would materialize), the tail store is the typed
|
|
19
|
+
* `store` against a `Buffer.init_view` view (deep-copy semantics into the
|
|
20
|
+
* chunk slot, no staging relay), and `#destroy` destroys each directory chunk
|
|
21
|
+
* through a view whose `cap` is the chunk's FILL count, so no uninitialized
|
|
22
|
+
* slot is ever walked. Chunk 0 (`items`) and the directory's own slab are
|
|
23
|
+
* reclaimed by the ordinary field destroy: the specialized per-element
|
|
24
|
+
* destroy guards zeroed slots and frees its slab.
|
|
25
|
+
**/
|
|
26
|
+
pub mv struct StableList<T> {
|
|
27
|
+
var length = 0
|
|
28
|
+
// Chunk 0: an exact-cap 256-slot slab, allocated on first push, never
|
|
29
|
+
// grown — a realloc would move its slots and invalidate `slot_ptr` aliases.
|
|
30
|
+
var items = Buffer<T>()
|
|
31
|
+
// Directory of chunk-1.. slab addresses (uint64 words, `calloc`'d slabs of
|
|
32
|
+
// 256 T_SIZE bytes). The directory's slot array may realloc; the slabs it
|
|
33
|
+
// points at never move.
|
|
34
|
+
var chunks = Buffer<uint64>()
|
|
35
|
+
// A transient VIEW carrier for tail pushes: `push` points it at the
|
|
36
|
+
// target chunk slab, stores the value through it (deep-copy semantics via
|
|
37
|
+
// Buffer's specialized `store` — no staging relay, no byte copy), then
|
|
38
|
+
// clears it. A field (not a local) because an owning Buffer local is
|
|
39
|
+
// auto-destroyed at scope exit and would free the borrowed slab. Its own
|
|
40
|
+
// slab is always null, so its field destroy reclaims nothing.
|
|
41
|
+
var stage = Buffer<T>()
|
|
42
|
+
|
|
43
|
+
pub func push = (ref self { chunks, items, stage }, mv T value) {
|
|
44
|
+
if self.items.cap == 0 {
|
|
45
|
+
self.items.alloc(256)
|
|
46
|
+
}
|
|
47
|
+
if self.length < self.items.cap {
|
|
48
|
+
self.items.store(self.length, value)
|
|
49
|
+
} else {
|
|
50
|
+
var int dir_index = self.length / 256 - 1
|
|
51
|
+
if dir_index >= self.chunks.cap {
|
|
52
|
+
self.chunks.grow(dir_index + 1)
|
|
53
|
+
}
|
|
54
|
+
self.ensure_chunk(dir_index)
|
|
55
|
+
// The typed store lands the value straight into the chunk slot
|
|
56
|
+
// through the view — deep-copy semantics via Buffer's specialized
|
|
57
|
+
// `store`, no staging slot, no byte relay. Every T-value crossing
|
|
58
|
+
// stays inside Nomen's typed machinery.
|
|
59
|
+
self.stage.store_in_slab(
|
|
60
|
+
self.chunks.load(dir_index),
|
|
61
|
+
256,
|
|
62
|
+
self.length % 256,
|
|
63
|
+
value
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
self.length += 1
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Capacity for `extra` more appends: allocates chunk 0, grows the
|
|
70
|
+
// directory, and pre-callocs the target slab (the raw `ensure_chunk`'s
|
|
71
|
+
// zero-check keeps push's own calloc idempotent). Splitting it
|
|
72
|
+
// out of `push` is what makes the compiler's in-place push lowering
|
|
73
|
+
// possible: the destination slot for a `push(parse(x))` argument must
|
|
74
|
+
// exist before the argument call runs, so the backend reserves first
|
|
75
|
+
// and constructs the callee's result directly into the append slot
|
|
76
|
+
// (the in-place push contract). Byte-identical policy for extra=1: the same
|
|
77
|
+
// alloc/dir-grow/first-touch sequence push's inline branches performed.
|
|
78
|
+
internal func reserve = (ref self { chunks, items, stage }, int extra: extra >= 0, out int) {
|
|
79
|
+
if self.items.cap == 0 {
|
|
80
|
+
self.items.alloc(256)
|
|
81
|
+
}
|
|
82
|
+
if self.length + extra > self.items.cap {
|
|
83
|
+
var int last = self.length + extra - 1
|
|
84
|
+
var int dir_index = last / 256 - 1
|
|
85
|
+
if dir_index >= self.chunks.cap {
|
|
86
|
+
self.chunks.grow(dir_index + 1)
|
|
87
|
+
}
|
|
88
|
+
self.ensure_chunk(dir_index)
|
|
89
|
+
}
|
|
90
|
+
return self.items.cap
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Calloc the directory chunk's slab on first touch (zero words mark
|
|
94
|
+
// empty chunks) — push's tail store and the in-place append both rely on
|
|
95
|
+
// the slab existing before a slot address is taken from it.
|
|
96
|
+
internal func ensure_chunk = (ref self { chunks, items, stage }, int dir_index) {
|
|
97
|
+
```
|
|
98
|
+
#arch: c
|
|
99
|
+
if (((unsigned long long*)self->chunks.data)[dir_index] == 0) {
|
|
100
|
+
((unsigned long long*)self->chunks.data)[dir_index] =
|
|
101
|
+
(unsigned long long)calloc(256, T_SIZE);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
```
|
|
105
|
+
#arch: aarch64
|
|
106
|
+
// x0 = self, x1 = dir_index. Stack-less raw functions get no
|
|
107
|
+
// `mov x19, x0` prologue, so load self explicitly and keep it in
|
|
108
|
+
// x19 (the prologue saved the caller's) with dir_index in x20
|
|
109
|
+
// (saved here — x20 is not covered by the prologue) across the
|
|
110
|
+
// calloc call.
|
|
111
|
+
stp x19, x20, [sp, #-16]!
|
|
112
|
+
mov x19, x0
|
|
113
|
+
mov x20, x1
|
|
114
|
+
ldr x9, [x19, #48]
|
|
115
|
+
ldr x10, [x9, x20, lsl #3]
|
|
116
|
+
cmp x10, #0
|
|
117
|
+
b.ne .Lsl_ec_have
|
|
118
|
+
mov x0, #256
|
|
119
|
+
mov x1, #T_SIZE
|
|
120
|
+
bl _calloc
|
|
121
|
+
ldr x9, [x19, #48]
|
|
122
|
+
str x0, [x9, x20, lsl #3]
|
|
123
|
+
.Lsl_ec_have:
|
|
124
|
+
ldp x19, x20, [sp], #16
|
|
125
|
+
```
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Address of the APPEND slot (element `length`) — reserve must have run,
|
|
129
|
+
// so the slot exists. The in-place push lowering constructs the argument
|
|
130
|
+
// call's result straight through this address (sole ownership transfers
|
|
131
|
+
// into the slot — the chunk slab is calloc'd on first touch by
|
|
132
|
+
// `reserve`'s `ensure_chunk` before the slot address is taken).
|
|
133
|
+
internal func append_slot_ptr = (ref self { chunks, items, stage }, out ptr T) {
|
|
134
|
+
```
|
|
135
|
+
#arch: c
|
|
136
|
+
unsigned long long idx = (unsigned long long)self->length;
|
|
137
|
+
if (idx < 256) {
|
|
138
|
+
return (T*)(unsigned long long)self->items.data + idx;
|
|
139
|
+
}
|
|
140
|
+
unsigned long long chunk = ((unsigned long long*)self->chunks.data)[idx / 256 - 1];
|
|
141
|
+
return (T*)chunk + (idx % 256);
|
|
142
|
+
```
|
|
143
|
+
```
|
|
144
|
+
#arch: aarch64
|
|
145
|
+
// x0 = self (stack-less raw: x19 is NOT loaded — read the fields
|
|
146
|
+
// from x0 and return the slot address in x0; the body calls
|
|
147
|
+
// nothing, so x9-x13 scratch is safe). Field offsets mirror
|
|
148
|
+
// push's: length @8, items.data @24, chunks.data @48 (the
|
|
149
|
+
// Buffer mono is {_vt, data, cap} = 24 bytes).
|
|
150
|
+
ldr x9, [x0, #8]
|
|
151
|
+
ldr x10, [x0, #24]
|
|
152
|
+
cmp x9, #256
|
|
153
|
+
b.lt .Lsl_as_c0
|
|
154
|
+
and x11, x9, #255
|
|
155
|
+
lsr x12, x9, #8
|
|
156
|
+
sub x12, x12, #1
|
|
157
|
+
ldr x13, [x0, #48]
|
|
158
|
+
ldr x10, [x13, x12, lsl #3]
|
|
159
|
+
b .Lsl_as_have
|
|
160
|
+
.Lsl_as_c0:
|
|
161
|
+
mov x11, x9
|
|
162
|
+
.Lsl_as_have:
|
|
163
|
+
mov x12, #T_SIZE
|
|
164
|
+
madd x0, x11, x12, x10
|
|
165
|
+
```
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pub inline func at = (self, int i: i >= 0 && i < self.length, out T) {
|
|
169
|
+
if i < self.items.cap {
|
|
170
|
+
return self.items.load(i)
|
|
171
|
+
}
|
|
172
|
+
return self.load_tail(i)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// The directory-chunk half of `at`: a slot borrow exactly like `load`,
|
|
176
|
+
// routed through the chunk directory, delegating to the mono'd
|
|
177
|
+
// `load_from` through a stack view over the foreign slab. Named in the
|
|
178
|
+
// borrow-accessor family (string_return_analysis.ts) so a two-branch
|
|
179
|
+
// `at` classifies as a borrow on both branches.
|
|
180
|
+
internal func load_tail = (self, int i, out T) {
|
|
181
|
+
```
|
|
182
|
+
#arch: c
|
|
183
|
+
long dir_index = i / 256L - 1L;
|
|
184
|
+
long slot = i % 256L;
|
|
185
|
+
T_buffer _tmp;
|
|
186
|
+
_tmp._vt = 0;
|
|
187
|
+
_tmp.data = ((unsigned long long*)self->chunks.data)[dir_index];
|
|
188
|
+
_tmp.cap = 256;
|
|
189
|
+
return T_load_from(&_tmp, slot);
|
|
190
|
+
```
|
|
191
|
+
```
|
|
192
|
+
#arch: aarch64
|
|
193
|
+
// x19 = self, x1 = i. The result forwards: x0 for T_SIZE <= 16, or
|
|
194
|
+
// the x8 sret slot — which is this function's OWN sret slot, passed
|
|
195
|
+
// through untouched, so the callee writes the caller's destination
|
|
196
|
+
// directly.
|
|
197
|
+
sub sp, sp, #32
|
|
198
|
+
mov x9, #256
|
|
199
|
+
sdiv x10, x1, x9
|
|
200
|
+
msub x1, x10, x9, x1
|
|
201
|
+
sub x10, x10, #1
|
|
202
|
+
ldr x11, [x19, #48]
|
|
203
|
+
ldr x12, [x11, x10, lsl #3]
|
|
204
|
+
str xzr, [sp]
|
|
205
|
+
str x12, [sp, #8]
|
|
206
|
+
mov x13, #256
|
|
207
|
+
str x13, [sp, #16]
|
|
208
|
+
mov x0, sp
|
|
209
|
+
bl T_load_from
|
|
210
|
+
add sp, sp, #32
|
|
211
|
+
```
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
pub func at_or = (self, int i, T fallback, out T) {
|
|
215
|
+
if i >= 0 && i < self.length {
|
|
216
|
+
return self.at(i)
|
|
217
|
+
}
|
|
218
|
+
return fallback
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Bounds-checked access that traps when i is outside [0, self.length) —
|
|
222
|
+
// for indices that can't be proven at compile time and out-of-range is a
|
|
223
|
+
// bug, not a case.
|
|
224
|
+
pub func at_or_panic = (self, int i, out T) {
|
|
225
|
+
if i >= 0 && i < self.length {
|
|
226
|
+
return self.at(i)
|
|
227
|
+
}
|
|
228
|
+
panic("index out of range")
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Address of element `i` (INTERNAL — the ELEMENT BORROW primitive):
|
|
232
|
+
// `ref T x = sl.at(i)` lowers to this, binding the alias to the slot
|
|
233
|
+
// itself. The
|
|
234
|
+
// address is stable across any number of pushes (slabs are allocated
|
|
235
|
+
// once and never relocate), and the checker exempts `push` from borrow
|
|
236
|
+
// invalidation for this type, so the alias stays live and writes through
|
|
237
|
+
// it land in the list. The length constraint is the caller's proof
|
|
238
|
+
// obligation, exactly like `.at`.
|
|
239
|
+
internal func slot_ptr = (ref self { items }, int i: i >= 0 && i < self.length, out ptr T) {
|
|
240
|
+
if i < self.items.cap {
|
|
241
|
+
return self.items.slot_ptr(i)
|
|
242
|
+
}
|
|
243
|
+
return self.slot_ptr_tail(i)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// The slot address in a directory chunk (pure address arithmetic — the
|
|
247
|
+
// slab never moves, so the pointer stays valid for the list's lifetime).
|
|
248
|
+
internal func slot_ptr_tail = (self, int i, out ptr T) {
|
|
249
|
+
```
|
|
250
|
+
#arch: c
|
|
251
|
+
long dir_index = i / 256L - 1L;
|
|
252
|
+
long slot = i % 256L;
|
|
253
|
+
return (T*)(unsigned long long)((unsigned long long*)self->chunks.data)[dir_index] + slot;
|
|
254
|
+
```
|
|
255
|
+
```
|
|
256
|
+
#arch: aarch64
|
|
257
|
+
// x19 = self, x1 = i — the slot address, returned in x0.
|
|
258
|
+
mov x9, #256
|
|
259
|
+
sdiv x10, x1, x9
|
|
260
|
+
msub x1, x10, x9, x1
|
|
261
|
+
sub x10, x10, #1
|
|
262
|
+
ldr x11, [x19, #48]
|
|
263
|
+
ldr x12, [x11, x10, lsl #3]
|
|
264
|
+
mov x13, #T_SIZE
|
|
265
|
+
madd x0, x1, x13, x12
|
|
266
|
+
```
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// A deep copy: a fresh StableList<T> with an independent copy of every
|
|
270
|
+
// element. For mv element types (strings, mv value structs) each slot in
|
|
271
|
+
// the copy owns its own heap data (Buffer deep-copies on store), so the
|
|
272
|
+
// source and the copy share nothing. Not available for class/trait
|
|
273
|
+
// element types (the copy would share the stored instances — the checker
|
|
274
|
+
// rejects it). The element is bound to a local before push — the
|
|
275
|
+
// monomorphized body is never re-checked, so it can't rely on check-time
|
|
276
|
+
// call-arg hoisting for struct element types.
|
|
277
|
+
pub func copy = (self, out StableList<T>) {
|
|
278
|
+
var dst = StableList<T>()
|
|
279
|
+
var i = 0
|
|
280
|
+
while i < self.length {
|
|
281
|
+
var T v = self.at(i)
|
|
282
|
+
dst.push(v)
|
|
283
|
+
i += 1
|
|
284
|
+
}
|
|
285
|
+
return dst
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Chunk 0 first: `move` hands each element out (the binding's scope exit
|
|
289
|
+
// destroys it — exactly once), then `alloc(0)` retires the empty slab so
|
|
290
|
+
// the field destroy walks zero slots. Directory chunks: raw loop, each
|
|
291
|
+
// destroyed through a view whose cap is the chunk's FILL count (never
|
|
292
|
+
// its 256 capacity — uninitialized slots hold zeroes, and a user
|
|
293
|
+
// `#destroy` over zeroed elements is garbage). The directory's own slot
|
|
294
|
+
// array is reclaimed by the ordinary field destroy (plain free — pointer
|
|
295
|
+
// words own nothing).
|
|
296
|
+
// Only the directory slabs (chunks 1..) need explicit teardown — they are
|
|
297
|
+
// raw pointer words the field destroy cannot see — so each is destroyed
|
|
298
|
+
// through a cap=filled view (per-element destroy + slab free). Chunk 0
|
|
299
|
+
// (`items`) is reclaimed by the ordinary field destroy: the
|
|
300
|
+
// specialized per-element destroy guards zeroed slots (a calloc'd slot
|
|
301
|
+
// skips the element destroy instead of firing the user `#destroy` on
|
|
302
|
+
// unconstructed storage) and frees its own slab; pointer words own
|
|
303
|
+
// nothing.
|
|
304
|
+
// (The old `stage` relay field is gone — push's tail path stores into
|
|
305
|
+
// the chunk through a `Buffer.init_view` view.)
|
|
306
|
+
func #destroy = (ref self { chunks, items, stage }) {
|
|
307
|
+
self.destroy_tails()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Raw: destroy directory chunks 1.. — per chunk, `filled` slots + the
|
|
311
|
+
// slab, via `T_buffer_destroy` on a cap=filled view.
|
|
312
|
+
internal func destroy_tails = (ref self { chunks, items, stage }) {
|
|
313
|
+
```
|
|
314
|
+
#arch: c
|
|
315
|
+
long n_dir = (self->length - 1L) / 256L;
|
|
316
|
+
for (long c = 0; c < n_dir; c++) {
|
|
317
|
+
unsigned long long slab = ((unsigned long long*)self->chunks.data)[c];
|
|
318
|
+
if (slab == 0) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
long filled = (c == n_dir - 1L) ? (self->length - n_dir * 256L) : 256L;
|
|
322
|
+
T_buffer _tmp;
|
|
323
|
+
_tmp._vt = 0;
|
|
324
|
+
_tmp.data = slab;
|
|
325
|
+
_tmp.cap = filled;
|
|
326
|
+
T_buffer_destroy(&_tmp);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
```
|
|
330
|
+
#arch: aarch64
|
|
331
|
+
// x19 = self. n_dir = (length - 1) / 256; per chunk, a cap=filled
|
|
332
|
+
// view on the stack and `bl T_buffer_destroy`.
|
|
333
|
+
sub sp, sp, #48
|
|
334
|
+
ldr x20, [x19, #8]
|
|
335
|
+
sub x20, x20, #1
|
|
336
|
+
mov x9, #256
|
|
337
|
+
sdiv x20, x20, x9
|
|
338
|
+
str xzr, [sp, #24]
|
|
339
|
+
.Lsl_dt_loop:
|
|
340
|
+
ldr x21, [sp, #24]
|
|
341
|
+
cmp x21, x20
|
|
342
|
+
b.ge .Lsl_dt_end
|
|
343
|
+
lsl x22, x21, #3
|
|
344
|
+
ldr x9, [x19, #48]
|
|
345
|
+
ldr x10, [x9, x22]
|
|
346
|
+
cmp x10, #0
|
|
347
|
+
b.eq .Lsl_dt_next
|
|
348
|
+
mov x11, #256
|
|
349
|
+
cmp x21, x20
|
|
350
|
+
b.ne .Lsl_dt_full
|
|
351
|
+
ldr x11, [x19, #8]
|
|
352
|
+
msub x11, x20, x9, x11
|
|
353
|
+
b .Lsl_dt_have
|
|
354
|
+
.Lsl_dt_full:
|
|
355
|
+
mov x11, #256
|
|
356
|
+
.Lsl_dt_have:
|
|
357
|
+
str xzr, [sp]
|
|
358
|
+
str x10, [sp, #8]
|
|
359
|
+
str x11, [sp, #16]
|
|
360
|
+
mov x0, sp
|
|
361
|
+
bl T_buffer_destroy
|
|
362
|
+
.Lsl_dt_next:
|
|
363
|
+
ldr x21, [sp, #24]
|
|
364
|
+
add x21, x21, #1
|
|
365
|
+
str x21, [sp, #24]
|
|
366
|
+
b .Lsl_dt_loop
|
|
367
|
+
.Lsl_dt_end:
|
|
368
|
+
add sp, sp, #48
|
|
369
|
+
```
|
|
370
|
+
}
|
|
371
|
+
}
|