nomen-lang 0.8.0 → 0.9.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 +8 -1
- package/core/System/Buffer.nm +366 -9
- package/core/System/ClassBuffer.nm +133 -1
- package/core/System/Controls/Alert.nm +2 -2
- package/core/System/Controls/App.nm +12 -12
- package/core/System/Controls/Button.nm +1 -1
- package/core/System/Controls/CheckBox.nm +2 -2
- package/core/System/Controls/ColorPicker.nm +1 -1
- package/core/System/Controls/ComboBox.nm +1 -1
- package/core/System/Controls/Container.nm +7 -1
- package/core/System/Controls/GroupBox.nm +1 -1
- package/core/System/Controls/ImageView.nm +1 -1
- package/core/System/Controls/LevelIndicator.nm +1 -1
- package/core/System/Controls/ListView.nm +1 -1
- package/core/System/Controls/Menu.nm +1 -1
- package/core/System/Controls/OutlineView.nm +1 -1
- package/core/System/Controls/Pagination.nm +8 -8
- package/core/System/Controls/Popover.nm +1 -1
- package/core/System/Controls/Progress.nm +1 -1
- package/core/System/Controls/Radio.nm +2 -2
- package/core/System/Controls/SearchField.nm +1 -1
- package/core/System/Controls/SecureField.nm +1 -1
- package/core/System/Controls/Segmented.nm +1 -1
- package/core/System/Controls/Select.nm +1 -1
- package/core/System/Controls/Separator.nm +1 -1
- package/core/System/Controls/Sheet.nm +1 -1
- package/core/System/Controls/Slider.nm +1 -1
- package/core/System/Controls/Spinner.nm +2 -2
- package/core/System/Controls/SplitView.nm +1 -1
- package/core/System/Controls/Stepper.nm +1 -1
- package/core/System/Controls/Switch.nm +1 -1
- package/core/System/Controls/Table.nm +1 -1
- package/core/System/Controls/Tabs.nm +1 -1
- package/core/System/Controls/Text.nm +1 -1
- package/core/System/Controls/TextBox.nm +2 -2
- package/core/System/Controls/TextView.nm +1 -1
- package/core/System/Controls/Toggle.nm +2 -2
- package/core/System/Controls/Toolbar.nm +1 -1
- package/core/System/Controls/Window.nm +1 -2
- package/core/System/LinkedList.nm +8 -1
- package/core/System/List.nm +68 -12
- package/core/System/Map.nm +174 -18
- package/core/System/Mutex.nm +4 -4
- package/core/System/Random.nm +3 -4
- package/core/System/Set.nm +94 -20
- package/core/System/StableList.nm +371 -0
- package/core/System/Stream/Directory.nm +2 -2
- package/core/System/Stream/File.nm +2 -2
- package/core/System/Stream/Http.nm +2 -2
- package/core/System/String.nm +10 -5
- package/core/System/StringBuilder.nm +213 -2
- package/core/System/Text/CharIndex.nm +9 -9
- package/core/System/Text/Json.nm +60 -46
- package/core/System/Text/JsonTree.nm +120 -92
- package/core/System/Text/Regex.nm +23 -18
- package/core/System/Text/Utf8.nm +5 -5
- 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 +36575 -30747
- package/package.json +1 -1
- package/src/bench_loop.nm +9 -9
package/core/System/Set.nm
CHANGED
|
@@ -6,26 +6,46 @@
|
|
|
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
23
|
pub func add = (ref self, 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); a string's by-value pair keeps the
|
|
41
|
+
// deep-copying store (the caller still owns its buffer), and a
|
|
42
|
+
// class/trait handle is a pointer store either way.
|
|
43
|
+
if T_IS_VALUE_STRUCT {
|
|
44
|
+
self.slots.store_take(idx, value)
|
|
45
|
+
} else {
|
|
46
|
+
self.slots.store(idx, value)
|
|
47
|
+
}
|
|
48
|
+
self.hashes.store_u32(idx, h)
|
|
29
49
|
self.length += 1
|
|
30
50
|
}
|
|
31
51
|
}
|
|
@@ -35,7 +55,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
35
55
|
if cap == 0 {
|
|
36
56
|
return false
|
|
37
57
|
}
|
|
38
|
-
var int idx = self.find_slot(value, cap)
|
|
58
|
+
var int idx = self.find_slot(value, value.hash() as uint32, cap)
|
|
39
59
|
return self.used.load_u32(idx) != 0
|
|
40
60
|
}
|
|
41
61
|
|
|
@@ -44,7 +64,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
44
64
|
if cap == 0 {
|
|
45
65
|
return 0
|
|
46
66
|
}
|
|
47
|
-
var int idx = self.find_slot(key, cap)
|
|
67
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
48
68
|
if self.used.load_u32(idx) == 0 {
|
|
49
69
|
return 0
|
|
50
70
|
}
|
|
@@ -58,7 +78,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
58
78
|
if cap == 0 {
|
|
59
79
|
return fallback
|
|
60
80
|
}
|
|
61
|
-
var int idx = self.find_slot(key, cap)
|
|
81
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
62
82
|
if self.used.load_u32(idx) == 0 {
|
|
63
83
|
return fallback
|
|
64
84
|
}
|
|
@@ -71,7 +91,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
71
91
|
if cap == 0 {
|
|
72
92
|
panic("value not found in set")
|
|
73
93
|
}
|
|
74
|
-
var int idx = self.find_slot(key, cap)
|
|
94
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
75
95
|
if self.used.load_u32(idx) == 0 {
|
|
76
96
|
panic("value not found in set")
|
|
77
97
|
}
|
|
@@ -83,7 +103,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
83
103
|
if cap == 0 {
|
|
84
104
|
return
|
|
85
105
|
}
|
|
86
|
-
var int idx = self.find_slot(value, cap)
|
|
106
|
+
var int idx = self.find_slot(value, value.hash() as uint32, cap)
|
|
87
107
|
if self.used.load_u32(idx) == 0 {
|
|
88
108
|
return
|
|
89
109
|
}
|
|
@@ -96,7 +116,9 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
96
116
|
// so entries past a non-moveable one are not stranded behind an empty
|
|
97
117
|
// slot (which would make find_slot miss them). No tombstones, so
|
|
98
118
|
// 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.
|
|
119
|
+
// length — not a full O(cap) rehash on every delete. `home` reads the
|
|
120
|
+
// stored hash word (no re-hash per shifted entry), and the word moves
|
|
121
|
+
// with the entry — find_slot's hash short-circuit trusts it.
|
|
100
122
|
var int gap = idx
|
|
101
123
|
var int k = idx
|
|
102
124
|
while true {
|
|
@@ -107,7 +129,7 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
107
129
|
if self.used.load_u32(k) == 0 {
|
|
108
130
|
break
|
|
109
131
|
}
|
|
110
|
-
var int home = (self.
|
|
132
|
+
var int home = (self.hashes.load_u32(k) as int) & (cap - 1)
|
|
111
133
|
var int dgap = gap - home
|
|
112
134
|
if dgap < 0 {
|
|
113
135
|
dgap = dgap + cap
|
|
@@ -123,34 +145,68 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
123
145
|
// pointer at the vacated slot (leaked once used.store marks it
|
|
124
146
|
// empty; the buffer destroy skips unused slots).
|
|
125
147
|
self.slots.shift(gap, k)
|
|
148
|
+
self.hashes.store_u32(gap, self.hashes.load_u32(k))
|
|
126
149
|
self.used.store_u32(gap, 1)
|
|
127
150
|
gap = k
|
|
128
151
|
}
|
|
129
152
|
}
|
|
153
|
+
if T_NEEDS_STRDUP {
|
|
154
|
+
if gap == idx {
|
|
155
|
+
// No entry shifted: the removed entry still owns its strdup'd
|
|
156
|
+
// bytes in its slot (a shift's first move would have
|
|
157
|
+
// reclaimed them — see shift_T above). destroy_at frees the
|
|
158
|
+
// buffer and zeroes the slot: a later add() that lands in
|
|
159
|
+
// this hole stores over the bytes assuming a fresh slot, and
|
|
160
|
+
// a stranded-then-overwritten entry leaks.
|
|
161
|
+
self.slots.destroy_at(idx)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if T_IS_VALUE_STRUCT {
|
|
165
|
+
if gap == idx {
|
|
166
|
+
// Same hole for a value-struct element: reclaim the slot's
|
|
167
|
+
// owned fields (destroy_at frees and zeroes; scalars-only
|
|
168
|
+
// structs reclaim nothing and the call is a no-op).
|
|
169
|
+
self.slots.destroy_at(idx)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
130
172
|
self.used.store_u32(gap, 0)
|
|
131
173
|
self.length = self.length - 1
|
|
132
174
|
}
|
|
133
175
|
|
|
134
|
-
// An independent copy: a fresh Set whose
|
|
135
|
-
// `
|
|
136
|
-
// and entries.
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
176
|
+
// An independent copy: a fresh Set whose member slabs are fresh (`slots`
|
|
177
|
+
// deep-copied per element, `used`/`hashes` copied word by word), with the
|
|
178
|
+
// same length and entries. The u32-word buffers are copied through their
|
|
179
|
+
// own primitives — `Buffer.copy` is T_SIZE-aware (8-byte loads for
|
|
180
|
+
// `int`), which would read past a 4-byte-per-slot `alloc_u32` slab. Not
|
|
181
|
+
// available for class/trait elements (the member routes to ClassBuffer,
|
|
182
|
+
// which has no copy — the checker rejects the call; the shape is also
|
|
183
|
+
// unreachable today since `T: Hashable + Equatable` cannot be a trait
|
|
184
|
+
// without trait-conformance syntax).
|
|
140
185
|
pub func copy = (self, out Set<T>) {
|
|
141
186
|
var dst = Set<T>()
|
|
142
187
|
dst.slots = self.slots.copy()
|
|
143
|
-
dst.used
|
|
188
|
+
dst.used.alloc_u32(self.slots.cap)
|
|
189
|
+
dst.hashes.alloc_u32(self.slots.cap)
|
|
190
|
+
var i = 0
|
|
191
|
+
while i < self.slots.cap; i += 1 {
|
|
192
|
+
dst.used.store_u32(i, self.used.load_u32(i))
|
|
193
|
+
dst.hashes.store_u32(i, self.hashes.load_u32(i))
|
|
194
|
+
}
|
|
144
195
|
dst.length = self.length
|
|
145
196
|
return dst
|
|
146
197
|
}
|
|
147
198
|
|
|
148
|
-
func find_slot = (self, T value, int cap, out int: out >= 0 && out < cap) {
|
|
199
|
+
func find_slot = (self, T value, uint32 h, int cap, out int: out >= 0 && out < cap) {
|
|
149
200
|
// cap is a power of two: `hash mod cap` via a mask, non-negative
|
|
150
201
|
// for any hash — no adjustment needed.
|
|
151
|
-
var int idx = (
|
|
202
|
+
var int idx = (h as int) & (cap - 1)
|
|
152
203
|
while self.used.load_u32(idx) != 0 {
|
|
153
|
-
|
|
204
|
+
// The stored-hash word short-circuits the value compare: a probed
|
|
205
|
+
// slot whose hash differs (every step of a collision walk past
|
|
206
|
+
// the home slot) skips the value load and `==`. A hash match
|
|
207
|
+
// still falls through to `==`, so the 32-bit truncation costs
|
|
208
|
+
// nothing.
|
|
209
|
+
if self.hashes.load_u32(idx) == h && self.slots.load(idx) == value {
|
|
154
210
|
return idx
|
|
155
211
|
}
|
|
156
212
|
idx += 1
|
|
@@ -161,13 +217,20 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
161
217
|
return idx
|
|
162
218
|
}
|
|
163
219
|
|
|
220
|
+
// Rehash hash-free: the stored per-slot hash words move with the entries,
|
|
221
|
+
// so no `value.hash()` runs here and the insertion probe needs no value
|
|
222
|
+
// compares (values arrive unique — they came from a valid set). The old
|
|
223
|
+
// form re-inserted via `self.add(mv v)`, re-hashing every value and
|
|
224
|
+
// re-comparing values on every probed slot.
|
|
164
225
|
func rehash = (ref self, int new_cap) {
|
|
165
226
|
var Buffer<T> old_slots = mv self.slots swap Buffer<T>()
|
|
166
227
|
var Buffer<int> old_used = mv self.used swap Buffer<int>()
|
|
228
|
+
var Buffer<int> old_hashes = mv self.hashes swap Buffer<int>()
|
|
167
229
|
var int old_cap = old_slots.cap
|
|
168
230
|
|
|
169
231
|
self.slots.alloc(new_cap)
|
|
170
232
|
self.used.alloc_u32(new_cap)
|
|
233
|
+
self.hashes.alloc_u32(new_cap)
|
|
171
234
|
self.length = 0
|
|
172
235
|
|
|
173
236
|
var i = 0
|
|
@@ -182,7 +245,18 @@ pub mv struct Set<T: Hashable + Equatable> {
|
|
|
182
245
|
// once the new buffer has made its own copy — passing it inline
|
|
183
246
|
// would orphan the move_T result.
|
|
184
247
|
var T v = old_slots.move(i)
|
|
185
|
-
|
|
248
|
+
var uint32 h = old_hashes.load_u32(i)
|
|
249
|
+
var int idx = (h as int) & (new_cap - 1)
|
|
250
|
+
while self.used.load_u32(idx) != 0 {
|
|
251
|
+
idx += 1
|
|
252
|
+
if idx >= new_cap {
|
|
253
|
+
idx = 0
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
self.used.store_u32(idx, 1)
|
|
257
|
+
self.slots.store(idx, v)
|
|
258
|
+
self.hashes.store_u32(idx, h)
|
|
259
|
+
self.length += 1
|
|
186
260
|
}
|
|
187
261
|
}
|
|
188
262
|
}
|
|
@@ -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, 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, 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, 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, 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, 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) {
|
|
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) {
|
|
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
|
+
}
|
package/core/System/String.nm
CHANGED
|
@@ -276,21 +276,26 @@ pub mv struct string: Stringable, Hashable, Equatable {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
// Replace all non-overlapping occurrences of needle with replacement,
|
|
279
|
-
// left to right. Same edge behavior as replace_first.
|
|
279
|
+
// left to right. Same edge behavior as replace_first. Literal bytes
|
|
280
|
+
// between matches are copied in bulk runs (one `append_string_view` per
|
|
281
|
+
// run) — the old loop appended per byte.
|
|
280
282
|
pub func replace_all = (self, view string needle, view string replacement, out string) {
|
|
281
283
|
var sb = StringBuilder()
|
|
282
284
|
var i = 0
|
|
283
285
|
var int n = self.length
|
|
284
286
|
var int nlen = needle.length
|
|
285
|
-
|
|
286
|
-
|
|
287
|
+
var run_start = 0
|
|
288
|
+
while nlen > 0 && i + nlen <= n {
|
|
289
|
+
if match_at(self, needle, i) {
|
|
290
|
+
sb.append_string_view(self.slice(run_start, i))
|
|
287
291
|
sb.append_string_view(replacement)
|
|
288
292
|
i = i + nlen
|
|
293
|
+
run_start = i
|
|
289
294
|
} else {
|
|
290
|
-
|
|
291
|
-
i = i + 1
|
|
295
|
+
i += 1
|
|
292
296
|
}
|
|
293
297
|
}
|
|
298
|
+
sb.append_string_view(self.slice(run_start, n))
|
|
294
299
|
return sb.to_string()
|
|
295
300
|
}
|
|
296
301
|
|