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/audit_runtime.c
CHANGED
|
@@ -51,13 +51,20 @@ typedef struct {
|
|
|
51
51
|
unsigned long pad; // keeps the payload 16-byte aligned
|
|
52
52
|
} nomen_audit_header;
|
|
53
53
|
|
|
54
|
+
// True when the NOMEN_AUDIT_TRACE env var is on. The getenv is not cached —
|
|
55
|
+
// the trace is a developer diagnostic, and alloc/free volumes under it are
|
|
56
|
+
// already dominated by the fprintf.
|
|
57
|
+
static int audit_trace(void) {
|
|
58
|
+
return getenv("NOMEN_AUDIT_TRACE") != 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
static void *audit_alloc(unsigned long size) {
|
|
55
62
|
nomen_audit_header *header = (nomen_audit_header *)malloc(size + sizeof(nomen_audit_header));
|
|
56
63
|
if (!header) return 0;
|
|
57
64
|
header->magic = NOMEN_AUDIT_MAGIC;
|
|
58
65
|
header->size = size;
|
|
59
66
|
header->state = NOMEN_AUDIT_LIVE;
|
|
60
|
-
if (
|
|
67
|
+
if (audit_trace()) fprintf(stderr, "ALLOC %p %lu\n", (void *)(header + 1), (unsigned long)size);
|
|
61
68
|
__atomic_add_fetch(&nomen_malloc_count, 1, __ATOMIC_SEQ_CST);
|
|
62
69
|
return (void *)(header + 1);
|
|
63
70
|
}
|
|
@@ -74,7 +81,7 @@ static nomen_audit_header *audit_header(void *ptr) {
|
|
|
74
81
|
// Retire a block: poison the header first so a second free of the same pointer
|
|
75
82
|
// is recognized as stale, then hand the memory back and drop the count.
|
|
76
83
|
static void audit_release(nomen_audit_header *header) {
|
|
77
|
-
if (
|
|
84
|
+
if (audit_trace()) fprintf(stderr, "FREE %p %lu\n", (void *)(header + 1), (unsigned long)header->size);
|
|
78
85
|
header->magic = 0;
|
|
79
86
|
header->state = NOMEN_AUDIT_DEAD;
|
|
80
87
|
__atomic_sub_fetch(&nomen_malloc_count, 1, __ATOMIC_SEQ_CST);
|
|
@@ -157,6 +164,9 @@ void nomen_free_wrap(void *ptr) {
|
|
|
157
164
|
// of passing it on, and leave the count alone: an unmatched free is not
|
|
158
165
|
// evidence that a live block was reclaimed.
|
|
159
166
|
if (!header || header->state != NOMEN_AUDIT_LIVE) {
|
|
167
|
+
if (audit_trace())
|
|
168
|
+
fprintf(stderr, "STALE-FREE %p (%s)\n", ptr,
|
|
169
|
+
header ? "already freed" : "not an audit allocation");
|
|
160
170
|
audit_record_stale();
|
|
161
171
|
audit_report_stale(0);
|
|
162
172
|
return;
|
package/core/System/Arena.nm
CHANGED
|
@@ -77,7 +77,14 @@ pub mv struct Arena<T> {
|
|
|
77
77
|
self.generations.store_int(index, 0)
|
|
78
78
|
}
|
|
79
79
|
self.values.grow(index + 1)
|
|
80
|
-
|
|
80
|
+
// The value arrives owned (`mv T value`): move a value-struct element
|
|
81
|
+
// into the fresh slot (the caller's storage zeroes, no deep copy);
|
|
82
|
+
// strings keep the deep-copying store (their pair arrives by value).
|
|
83
|
+
if T_IS_VALUE_STRUCT {
|
|
84
|
+
self.values.store_take(index, value)
|
|
85
|
+
} else {
|
|
86
|
+
self.values.store(index, value)
|
|
87
|
+
}
|
|
81
88
|
var handle = ArenaRef<T>()
|
|
82
89
|
handle.index = index
|
|
83
90
|
handle.generation = self.generations.load_int(index)
|
package/core/System/Buffer.nm
CHANGED
|
@@ -127,7 +127,6 @@ pub struct Buffer<T> {
|
|
|
127
127
|
self.data = calloc(size, 8)
|
|
128
128
|
return self.cap
|
|
129
129
|
}
|
|
130
|
-
|
|
131
130
|
internal func grow_int = (ref self, int needed, out int: out >= needed) {
|
|
132
131
|
if self.cap >= needed {
|
|
133
132
|
return self.cap
|
|
@@ -171,6 +170,125 @@ pub struct Buffer<T> {
|
|
|
171
170
|
```
|
|
172
171
|
}
|
|
173
172
|
|
|
173
|
+
// Labeled delegation point for raw-body callers in other containers
|
|
174
|
+
// (StableList's chunked storage calls this). Deliberately a RAW bitwise
|
|
175
|
+
// slot load — NOT a delegation to `load`: the owning-specialized `load`
|
|
176
|
+
// body materializes an owned copy (strdup) for string elements, while
|
|
177
|
+
// the accessor-family classification of every name that reaches user
|
|
178
|
+
// bindings (`at`, `load_tail` → this) promises a BORROW — the slot keeps
|
|
179
|
+
// sole ownership and the caller's binding is never freed. A strdup here
|
|
180
|
+
// would pair a counted allocation with a borrow binding that never frees
|
|
181
|
+
// it (audit LEAK).
|
|
182
|
+
internal func load_from = (self, int i: i >= 0 && i < self.cap, out T) {
|
|
183
|
+
```
|
|
184
|
+
#arch: c
|
|
185
|
+
return ((T*)(unsigned long long)self->data)[i];
|
|
186
|
+
```
|
|
187
|
+
```
|
|
188
|
+
#arch: aarch64
|
|
189
|
+
// x19 = self, x1 = i. The return register convention is per type
|
|
190
|
+
// KIND: `string` returns the fat pair in x0/x1 (T_FAT folds to 1),
|
|
191
|
+
// composites larger than 8 bytes return through the x8 sret buffer,
|
|
192
|
+
// scalars are width-matched into x0.
|
|
193
|
+
mov x14, #T_FAT
|
|
194
|
+
cmp x14, #1
|
|
195
|
+
b.eq .Lbuffer_load_from_fat
|
|
196
|
+
ldr x0, [x19, #8]
|
|
197
|
+
mov x3, #T_SIZE
|
|
198
|
+
madd x1, x1, x3, xzr
|
|
199
|
+
add x0, x0, x1
|
|
200
|
+
cmp x3, #16
|
|
201
|
+
b.gt .Lbuffer_load_from_copy
|
|
202
|
+
cmp x3, #8
|
|
203
|
+
b.gt .Lbuffer_load_from_pair
|
|
204
|
+
cmp x3, #1
|
|
205
|
+
b.eq .Lbuffer_load_from_1
|
|
206
|
+
cmp x3, #2
|
|
207
|
+
b.eq .Lbuffer_load_from_2
|
|
208
|
+
cmp x3, #4
|
|
209
|
+
b.eq .Lbuffer_load_from_4
|
|
210
|
+
ldr x0, [x0]
|
|
211
|
+
b .Lbuffer_load_from_end
|
|
212
|
+
.Lbuffer_load_from_1:
|
|
213
|
+
ldrb w0, [x0]
|
|
214
|
+
b .Lbuffer_load_from_end
|
|
215
|
+
.Lbuffer_load_from_2:
|
|
216
|
+
ldrh w0, [x0]
|
|
217
|
+
b .Lbuffer_load_from_end
|
|
218
|
+
.Lbuffer_load_from_4:
|
|
219
|
+
ldr w0, [x0]
|
|
220
|
+
b .Lbuffer_load_from_end
|
|
221
|
+
.Lbuffer_load_from_pair:
|
|
222
|
+
// Word-pair load (T_SIZE <= 16): both words into the sret
|
|
223
|
+
// buffer, no call.
|
|
224
|
+
ldp x9, x10, [x0]
|
|
225
|
+
stp x9, x10, [x8]
|
|
226
|
+
b .Lbuffer_load_from_end
|
|
227
|
+
.Lbuffer_load_from_copy:
|
|
228
|
+
mov x1, x0
|
|
229
|
+
mov x0, x8
|
|
230
|
+
mov x2, x3
|
|
231
|
+
bl _memcpy
|
|
232
|
+
b .Lbuffer_load_from_end
|
|
233
|
+
.Lbuffer_load_from_fat:
|
|
234
|
+
ldr x0, [x19, #8]
|
|
235
|
+
lsl x2, x1, #4
|
|
236
|
+
add x0, x0, x2
|
|
237
|
+
ldp x0, x1, [x0]
|
|
238
|
+
.Lbuffer_load_from_end:
|
|
239
|
+
```
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Initialize this buffer as a VIEW over a foreign slab (INTERNAL — the
|
|
243
|
+
// chunked-container primitive): `data` becomes `slab` and `cap` becomes
|
|
244
|
+
// `fill`, and the buffer owns NOTHING — the slab keeps its real owner
|
|
245
|
+
// (never grown, never freed by the view). The mono'd slot family
|
|
246
|
+
// (`store`/`load`/`load_from`/`shift`) then runs against the view
|
|
247
|
+
// unchanged, with its per-element specializations intact. This is how
|
|
248
|
+
// StableList's directory chunks reach the typed store without a staging
|
|
249
|
+
// relay — the same stack view the raw bodies build by hand, in typed
|
|
250
|
+
// Nomen. The holder must clear the view afterwards (see clear_view):
|
|
251
|
+
// an owning Buffer FIELD is destroyed by its holder's teardown.
|
|
252
|
+
internal func init_view = (ref self, uint64 slab, int fill: fill >= 0) {
|
|
253
|
+
self.data = slab
|
|
254
|
+
self.cap = fill
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Clear a view initialized by init_view: `data`/`cap` back to zero, so
|
|
258
|
+
// the holder's own field destroy reclaims nothing (the slab's real owner
|
|
259
|
+
// still owns it).
|
|
260
|
+
internal func clear_view = (ref self) {
|
|
261
|
+
self.data = 0
|
|
262
|
+
self.cap = 0
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Store `val` into slot `slot` of a foreign slab at `slab` (fill `fill`)
|
|
266
|
+
// through this buffer as a transient view — INTERNAL (StableList's
|
|
267
|
+
// directory chunks): init the view, run the mono'd `store` (the per-
|
|
268
|
+
// element deep-copy specialization), then clear the view so the holder's
|
|
269
|
+
// teardown reclaims nothing. The holder passes a Buffer FIELD, never a
|
|
270
|
+
// local — a local owning Buffer would be auto-destroyed at scope exit
|
|
271
|
+
// and free the slab it only borrows.
|
|
272
|
+
internal func store_in_slab = (
|
|
273
|
+
ref self,
|
|
274
|
+
uint64 slab,
|
|
275
|
+
int fill: fill >= 0,
|
|
276
|
+
int slot: slot >= 0,
|
|
277
|
+
T val
|
|
278
|
+
) {
|
|
279
|
+
self.init_view(slab, fill)
|
|
280
|
+
// `val` arrives owned from the container's `mv T` push parameter: a
|
|
281
|
+
// value-struct element moves into the chunk slot (store_take — the
|
|
282
|
+
// caller's storage zeroes, no deep copy); strings keep the
|
|
283
|
+
// deep-copying store (their pair arrives by value).
|
|
284
|
+
if T_IS_VALUE_STRUCT {
|
|
285
|
+
self.store_take(slot, val)
|
|
286
|
+
} else {
|
|
287
|
+
self.store(slot, val)
|
|
288
|
+
}
|
|
289
|
+
self.clear_view()
|
|
290
|
+
}
|
|
291
|
+
|
|
174
292
|
// Move a value out of a slot: return it and clear the slot to 0. This is
|
|
175
293
|
// the ownership-extracting primitive (used by e.g. List.pop) — unlike
|
|
176
294
|
// load_int (a borrow read), it relinquishes the slot so the buffer no
|
|
@@ -215,8 +333,9 @@ pub struct Buffer<T> {
|
|
|
215
333
|
}
|
|
216
334
|
|
|
217
335
|
// T_SIZE-aware primitives for value-struct elements. Slab is sized in
|
|
218
|
-
// multiples of T_SIZE;
|
|
219
|
-
//
|
|
336
|
+
// multiples of T_SIZE; multi-word slots copy as one word pair at
|
|
337
|
+
// T_SIZE <= 16 (ldp/stp — no call) and via memcpy above that, so
|
|
338
|
+
// structs of any width work correctly. Classes route to ClassBuffer<T> (per-element destroy), so T
|
|
220
339
|
// reaching here is always a value type (primitive or struct).
|
|
221
340
|
|
|
222
341
|
func alloc = (ref self, int size, out int: out >= size) {
|
|
@@ -264,8 +383,10 @@ pub struct Buffer<T> {
|
|
|
264
383
|
mov x3, #T_SIZE
|
|
265
384
|
madd x1, x1, x3, xzr
|
|
266
385
|
add x0, x0, x1
|
|
267
|
-
cmp x3, #
|
|
386
|
+
cmp x3, #16
|
|
268
387
|
b.gt .Lbuffer_load_T_copy
|
|
388
|
+
cmp x3, #8
|
|
389
|
+
b.gt .Lbuffer_load_T_pair
|
|
269
390
|
// Width-matched load (zero-extends) — an 8-byte ldr on a sub-8
|
|
270
391
|
// element would read past the slab's end and leave garbage in the
|
|
271
392
|
// upper bits.
|
|
@@ -286,6 +407,12 @@ pub struct Buffer<T> {
|
|
|
286
407
|
.Lbuffer_load_T_4:
|
|
287
408
|
ldr w0, [x0]
|
|
288
409
|
b .Lbuffer_load_T_end
|
|
410
|
+
.Lbuffer_load_T_pair:
|
|
411
|
+
// Word-pair load (T_SIZE <= 16): both words into the sret
|
|
412
|
+
// buffer, no call.
|
|
413
|
+
ldp x9, x10, [x0]
|
|
414
|
+
stp x9, x10, [x8]
|
|
415
|
+
b .Lbuffer_load_T_end
|
|
289
416
|
.Lbuffer_load_T_copy:
|
|
290
417
|
mov x1, x0
|
|
291
418
|
mov x0, x8
|
|
@@ -315,8 +442,10 @@ pub struct Buffer<T> {
|
|
|
315
442
|
mov x3, #T_SIZE
|
|
316
443
|
madd x1, x1, x3, xzr
|
|
317
444
|
add x0, x0, x1
|
|
318
|
-
cmp x3, #
|
|
445
|
+
cmp x3, #16
|
|
319
446
|
b.gt .Lbuffer_store_T_copy
|
|
447
|
+
cmp x3, #8
|
|
448
|
+
b.gt .Lbuffer_store_T_pair
|
|
320
449
|
// Sub-8 element types must use a width-matched store: an 8-byte str
|
|
321
450
|
// would clobber the following slots (and run past the slab's end).
|
|
322
451
|
cmp x3, #1
|
|
@@ -336,6 +465,12 @@ pub struct Buffer<T> {
|
|
|
336
465
|
.Lbuffer_store_T_4:
|
|
337
466
|
str w2, [x0]
|
|
338
467
|
b .Lbuffer_store_T_end
|
|
468
|
+
.Lbuffer_store_T_pair:
|
|
469
|
+
// Word-pair store (T_SIZE <= 16): the value's address (x2)
|
|
470
|
+
// feeds both words, no call.
|
|
471
|
+
ldp x9, x10, [x2]
|
|
472
|
+
stp x9, x10, [x0]
|
|
473
|
+
b .Lbuffer_store_T_end
|
|
339
474
|
.Lbuffer_store_T_copy:
|
|
340
475
|
mov x1, x2
|
|
341
476
|
mov x2, x3
|
|
@@ -344,6 +479,94 @@ pub struct Buffer<T> {
|
|
|
344
479
|
```
|
|
345
480
|
}
|
|
346
481
|
|
|
482
|
+
// TAKE a value into slot `i`. The caller's storage (`val`'s source — on
|
|
483
|
+
// both backends a value-struct parameter arrives as a POINTER to the
|
|
484
|
+
// caller's value) is byte-copied into the fresh slot and then ZEROED, so
|
|
485
|
+
// the buffer becomes the sole owner and the caller's own teardown frees
|
|
486
|
+
// nothing. The ownership-taking counterpart of `store` (which deep-copies
|
|
487
|
+
// a borrowed value): the `add`/`alloc` family's bodies use it when
|
|
488
|
+
// `T_IS_VALUE_STRUCT` folds true, eliding the per-slot deep copy.
|
|
489
|
+
// INTERNAL: only valid for a value-struct `T` (the only shape whose
|
|
490
|
+
// parameter is a pointer to the caller's storage — a `string`'s fat pair
|
|
491
|
+
// arrives by value, so take-store would zero the callee's copy and leave
|
|
492
|
+
// the caller's buffer shared).
|
|
493
|
+
internal inline func store_take = (ref self, int i: i >= 0 && i < self.cap, T val) {
|
|
494
|
+
```
|
|
495
|
+
#arch: c
|
|
496
|
+
{
|
|
497
|
+
T* _st = (T*)(unsigned long long)self->data;
|
|
498
|
+
#if T_IS_VALUE_STRUCT
|
|
499
|
+
// A value-struct parameter is a POINTER to the caller's storage.
|
|
500
|
+
T* _src = &(*val);
|
|
501
|
+
_st[i] = *_src;
|
|
502
|
+
memset((void*)_src, 0, T_SIZE);
|
|
503
|
+
#else
|
|
504
|
+
// Never called for a non-value-struct instantiation (the container
|
|
505
|
+
// bodies fold their `T_IS_VALUE_STRUCT` arm); this arm only keeps the
|
|
506
|
+
// emitted instance compiling.
|
|
507
|
+
T _tmp = val;
|
|
508
|
+
T* _src = &_tmp;
|
|
509
|
+
_st[i] = *_src;
|
|
510
|
+
(void)_src;
|
|
511
|
+
#endif
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
```
|
|
515
|
+
#arch: aarch64
|
|
516
|
+
// x19 = self, x1 = i, x2 = &val (the caller's storage)
|
|
517
|
+
ldr x0, [x19, #8]
|
|
518
|
+
mov x3, #T_SIZE
|
|
519
|
+
madd x1, x1, x3, xzr
|
|
520
|
+
add x0, x0, x1
|
|
521
|
+
cmp x3, #16
|
|
522
|
+
b.gt .Lbuffer_take_T_copy
|
|
523
|
+
cmp x3, #8
|
|
524
|
+
b.gt .Lbuffer_take_T_pair
|
|
525
|
+
cmp x3, #1
|
|
526
|
+
b.eq .Lbuffer_take_T_1
|
|
527
|
+
cmp x3, #2
|
|
528
|
+
b.eq .Lbuffer_take_T_2
|
|
529
|
+
cmp x3, #4
|
|
530
|
+
b.eq .Lbuffer_take_T_4
|
|
531
|
+
ldr x9, [x2]
|
|
532
|
+
str x9, [x0]
|
|
533
|
+
str xzr, [x2]
|
|
534
|
+
b .Lbuffer_take_T_end
|
|
535
|
+
.Lbuffer_take_T_1:
|
|
536
|
+
ldrb w9, [x2]
|
|
537
|
+
strb w9, [x0]
|
|
538
|
+
strb wzr, [x2]
|
|
539
|
+
b .Lbuffer_take_T_end
|
|
540
|
+
.Lbuffer_take_T_2:
|
|
541
|
+
ldrh w9, [x2]
|
|
542
|
+
strh w9, [x0]
|
|
543
|
+
strh wzr, [x2]
|
|
544
|
+
b .Lbuffer_take_T_end
|
|
545
|
+
.Lbuffer_take_T_4:
|
|
546
|
+
ldr w9, [x2]
|
|
547
|
+
str w9, [x0]
|
|
548
|
+
str wzr, [x2]
|
|
549
|
+
b .Lbuffer_take_T_end
|
|
550
|
+
.Lbuffer_take_T_pair:
|
|
551
|
+
ldp x9, x10, [x2]
|
|
552
|
+
stp x9, x10, [x0]
|
|
553
|
+
stp xzr, xzr, [x2]
|
|
554
|
+
b .Lbuffer_take_T_end
|
|
555
|
+
.Lbuffer_take_T_copy:
|
|
556
|
+
mov x4, x2
|
|
557
|
+
mov x1, x2
|
|
558
|
+
mov x2, x3
|
|
559
|
+
stp x4, x3, [sp, #-16]!
|
|
560
|
+
bl _memcpy
|
|
561
|
+
ldp x4, x3, [sp], #16
|
|
562
|
+
mov x0, x4
|
|
563
|
+
mov x1, #0
|
|
564
|
+
mov x2, x3
|
|
565
|
+
bl _memset
|
|
566
|
+
.Lbuffer_take_T_end:
|
|
567
|
+
```
|
|
568
|
+
}
|
|
569
|
+
|
|
347
570
|
// Move a value out of a T_SIZE-sized slot: return it and zero the slot so the
|
|
348
571
|
// buffer no longer holds a (possibly mv) copy. Declared `mv out T` so
|
|
349
572
|
// the result is treated as OWNED by the borrow checker — the slot is
|
|
@@ -364,8 +587,10 @@ pub struct Buffer<T> {
|
|
|
364
587
|
mov x3, #T_SIZE
|
|
365
588
|
madd x5, x1, x3, xzr
|
|
366
589
|
add x5, x4, x5
|
|
367
|
-
cmp x3, #
|
|
590
|
+
cmp x3, #16
|
|
368
591
|
b.gt .Lbuffer_move_T_copy
|
|
592
|
+
cmp x3, #8
|
|
593
|
+
b.gt .Lbuffer_move_T_pair
|
|
369
594
|
// Width-matched load + zero-clear: an 8-byte access on a sub-8
|
|
370
595
|
// element would zero the following slots and run past the slab.
|
|
371
596
|
cmp x3, #1
|
|
@@ -389,6 +614,14 @@ pub struct Buffer<T> {
|
|
|
389
614
|
ldr w0, [x5]
|
|
390
615
|
str wzr, [x5]
|
|
391
616
|
b .Lbuffer_move_T_end
|
|
617
|
+
.Lbuffer_move_T_pair:
|
|
618
|
+
// Word-pair move (T_SIZE <= 16): the sret pair takes both
|
|
619
|
+
// words and the slot zeroes, no calls.
|
|
620
|
+
ldp x9, x10, [x5]
|
|
621
|
+
stp x9, x10, [x8]
|
|
622
|
+
stp xzr, xzr, [x5]
|
|
623
|
+
mov x0, x8
|
|
624
|
+
b .Lbuffer_move_T_end
|
|
392
625
|
.Lbuffer_move_T_copy:
|
|
393
626
|
mov x0, x8
|
|
394
627
|
mov x1, x5
|
|
@@ -518,8 +751,10 @@ pub struct Buffer<T> {
|
|
|
518
751
|
mov x3, #T_SIZE
|
|
519
752
|
madd x1, x1, x3, xzr
|
|
520
753
|
add x0, x0, x1
|
|
521
|
-
cmp x3, #
|
|
754
|
+
cmp x3, #16
|
|
522
755
|
b.gt .Lbuffer_replace_T_copy
|
|
756
|
+
cmp x3, #8
|
|
757
|
+
b.gt .Lbuffer_replace_T_pair
|
|
523
758
|
// Width-matched store: an 8-byte str on a sub-8 element would
|
|
524
759
|
// clobber the following slots and run past the slab's end.
|
|
525
760
|
cmp x3, #1
|
|
@@ -539,6 +774,12 @@ pub struct Buffer<T> {
|
|
|
539
774
|
.Lbuffer_replace_T_4:
|
|
540
775
|
str w2, [x0]
|
|
541
776
|
b .Lbuffer_replace_T_end
|
|
777
|
+
.Lbuffer_replace_T_pair:
|
|
778
|
+
// Word-pair store (T_SIZE <= 16): the value's address (x2)
|
|
779
|
+
// feeds both words, no call.
|
|
780
|
+
ldp x9, x10, [x2]
|
|
781
|
+
stp x9, x10, [x0]
|
|
782
|
+
b .Lbuffer_replace_T_end
|
|
542
783
|
.Lbuffer_replace_T_copy:
|
|
543
784
|
mov x1, x2
|
|
544
785
|
mov x2, x3
|
|
@@ -558,6 +799,52 @@ pub struct Buffer<T> {
|
|
|
558
799
|
self.replace(i, value)
|
|
559
800
|
}
|
|
560
801
|
|
|
802
|
+
// Reclaim slot i's owned heap state WITHOUT writing the slot (the raw
|
|
803
|
+
// no-op below is correct for trivially-destructible elements; for
|
|
804
|
+
// `string` and mv value-struct elements both backends specialize this
|
|
805
|
+
// body — free the displaced pointer / guarded T_destroy — the same
|
|
806
|
+
// dispatch replace_T gets). This is the DESTROY half of replace,
|
|
807
|
+
// separated from the copy so the in-place store lowering can reclaim
|
|
808
|
+
// the displaced value and then CONSTRUCT the new one straight into the
|
|
809
|
+
// slot (no donor, no deep copy). Contract: the slot must be
|
|
810
|
+
// overwritten before it is read or destroyed again — Map.set_slot_ptr
|
|
811
|
+
// (the probe-then-construct primitive) and the in-place `set` splice
|
|
812
|
+
// are the callers, and both overwrite immediately.
|
|
813
|
+
internal func destroy_at = (ref self, int i: i >= 0 && i < self.cap) {
|
|
814
|
+
```
|
|
815
|
+
#arch: c
|
|
816
|
+
T* _slots = (T*)(unsigned long long)self->data;
|
|
817
|
+
#if T_NEEDS_STRDUP
|
|
818
|
+
// A string slot owns its strdup'd bytes — free the buffer before the
|
|
819
|
+
// zero. Owning value-struct elements never reach this body: their
|
|
820
|
+
// destroy_at is replaced at the call site by the owning-buffer
|
|
821
|
+
// specialization (payload reclaim + zero).
|
|
822
|
+
free(_slots[i].ptr);
|
|
823
|
+
#endif
|
|
824
|
+
memset((void*)&_slots[i], 0, T_SIZE);
|
|
825
|
+
```
|
|
826
|
+
```
|
|
827
|
+
#arch: aarch64
|
|
828
|
+
// Same contract as the C body: string slots free their strdup'd
|
|
829
|
+
// buffer first (the T_NEEDS_STRDUP dispatch folds away for every
|
|
830
|
+
// other element kind); owning value-struct elements take the
|
|
831
|
+
// call-site specialization instead.
|
|
832
|
+
ldr x4, [x19, #8]
|
|
833
|
+
mov x3, #T_SIZE
|
|
834
|
+
madd x5, x1, x3, xzr
|
|
835
|
+
add x5, x4, x5
|
|
836
|
+
mov x9, #T_NEEDS_STRDUP
|
|
837
|
+
cbz x9, .Lbuffer_destroy_at_T_zero
|
|
838
|
+
ldr x0, [x5]
|
|
839
|
+
bl _free
|
|
840
|
+
.Lbuffer_destroy_at_T_zero:
|
|
841
|
+
mov x0, x5
|
|
842
|
+
mov x1, #0
|
|
843
|
+
mov x2, #T_SIZE
|
|
844
|
+
bl _memset
|
|
845
|
+
```
|
|
846
|
+
}
|
|
847
|
+
|
|
561
848
|
// Address of slot `i` (library-only): the ELEMENT BORROW primitive —
|
|
562
849
|
// `ref T x = list.at(i)` (docs/HANDLERS.md Stage 4, the dynamic-tree
|
|
563
850
|
// dispatch) lowers to this, binding the alias to the slot itself rather
|
|
@@ -567,7 +854,7 @@ pub struct Buffer<T> {
|
|
|
567
854
|
// borrow-invalidation rules. Library-internal: the caller (the `List`
|
|
568
855
|
// wrapper, or trusted internals) bounds-checks; the body is raw-only so
|
|
569
856
|
// the missing-return net does not apply to the raw return.
|
|
570
|
-
|
|
857
|
+
internal func slot_ptr = (ref self, int i, out ptr T) {
|
|
571
858
|
```
|
|
572
859
|
#arch: c
|
|
573
860
|
return (T*)(unsigned long long)self->data + i;
|
|
@@ -614,8 +901,10 @@ pub struct Buffer<T> {
|
|
|
614
901
|
madd x6, x2, x3, x4
|
|
615
902
|
cmp x5, x6
|
|
616
903
|
b.eq .Lbuffer_shift_T_end
|
|
617
|
-
cmp x3, #
|
|
904
|
+
cmp x3, #16
|
|
618
905
|
b.gt .Lbuffer_shift_T_copy
|
|
906
|
+
cmp x3, #8
|
|
907
|
+
b.gt .Lbuffer_shift_T_pair
|
|
619
908
|
// Width-matched mv: an 8-byte access on a sub-8 element would
|
|
620
909
|
// clobber the following slot (and run past the slab's end).
|
|
621
910
|
cmp x3, #1
|
|
@@ -643,6 +932,13 @@ pub struct Buffer<T> {
|
|
|
643
932
|
str w7, [x5]
|
|
644
933
|
str wzr, [x6]
|
|
645
934
|
b .Lbuffer_shift_T_end
|
|
935
|
+
.Lbuffer_shift_T_pair:
|
|
936
|
+
// Word-pair shift (T_SIZE <= 16): both words move wholesale
|
|
937
|
+
// and the source pair zeroes, no calls.
|
|
938
|
+
ldp x7, x9, [x6]
|
|
939
|
+
stp x7, x9, [x5]
|
|
940
|
+
stp xzr, xzr, [x6]
|
|
941
|
+
b .Lbuffer_shift_T_end
|
|
646
942
|
.Lbuffer_shift_T_copy:
|
|
647
943
|
mov x0, x5
|
|
648
944
|
mov x1, x6
|
|
@@ -658,6 +954,67 @@ pub struct Buffer<T> {
|
|
|
658
954
|
```
|
|
659
955
|
}
|
|
660
956
|
|
|
957
|
+
// Bulk transfer for backward-shift deletion: move the `count` slots at
|
|
958
|
+
// `src` down to `dst` (src >= dst) as ONE block move, then zero the
|
|
959
|
+
// source slots the destination never covers — for the contiguous
|
|
960
|
+
// deletion shape (src = dst + 1) that is exactly one tail slot, so a
|
|
961
|
+
// whole range shift costs one memmove + one memset instead of
|
|
962
|
+
// count × (memcpy + memset) per-element `shift` calls. PROOF
|
|
963
|
+
// OBLIGATION on the caller (INTERNAL, byte-level like `shift`): every
|
|
964
|
+
// slot in [dst, dst + count) must be DEAD — zeroed by a move-out or
|
|
965
|
+
// otherwise unowned — because the block move overwrites them without
|
|
966
|
+
// reclaiming (the ownership proof is why the deletion algorithms zero
|
|
967
|
+
// and relocate in lockstep; a live displaced value belongs in `shift`,
|
|
968
|
+
// which reclaims dst first). The bytes transfer wholesale, so the move
|
|
969
|
+
// carries mv elements' heap pointers with them — correct for every
|
|
970
|
+
// element kind, raw for both backends (never names T).
|
|
971
|
+
internal inline func move_range = (
|
|
972
|
+
ref self,
|
|
973
|
+
int dst: dst >= 0,
|
|
974
|
+
int src: src >= dst,
|
|
975
|
+
int count: count >= 0 && src + count <= self.cap
|
|
976
|
+
) {
|
|
977
|
+
```
|
|
978
|
+
#arch: c
|
|
979
|
+
if (count > 0) {
|
|
980
|
+
T* _slots = (T*)(unsigned long long)self->data;
|
|
981
|
+
memmove((void*)&_slots[dst], (void*)&_slots[src],
|
|
982
|
+
(unsigned long long)count * T_SIZE);
|
|
983
|
+
// Zero the source slots the destination does not overwrite:
|
|
984
|
+
// [dst + count, src + count) — one slot for the contiguous
|
|
985
|
+
// deletion shape, the gap+1 slots for a strided one.
|
|
986
|
+
memset((void*)&_slots[dst + count], 0,
|
|
987
|
+
(unsigned long long)(src - dst) * T_SIZE);
|
|
988
|
+
}
|
|
989
|
+
```
|
|
990
|
+
```
|
|
991
|
+
#arch: aarch64
|
|
992
|
+
// x19 = self, x1 = dst, x2 = src, x3 = count. x0-x18 die across the
|
|
993
|
+
// bl calls, so the block math parks on the stack around _memmove.
|
|
994
|
+
cbz x3, .Lbuffer_move_range_T_end
|
|
995
|
+
ldr x4, [x19, #8]
|
|
996
|
+
mov x5, #T_SIZE
|
|
997
|
+
madd x6, x1, x5, x4
|
|
998
|
+
madd x7, x2, x5, x4
|
|
999
|
+
madd x9, x3, x5, xzr
|
|
1000
|
+
sub x10, x2, x1
|
|
1001
|
+
madd x10, x10, x5, xzr
|
|
1002
|
+
stp x6, x9, [sp, #-16]!
|
|
1003
|
+
str x10, [sp, #-16]!
|
|
1004
|
+
mov x0, x6
|
|
1005
|
+
mov x1, x7
|
|
1006
|
+
mov x2, x9
|
|
1007
|
+
bl _memmove
|
|
1008
|
+
ldr x10, [sp], #16
|
|
1009
|
+
ldp x6, x9, [sp], #16
|
|
1010
|
+
add x0, x6, x9
|
|
1011
|
+
mov x1, #0
|
|
1012
|
+
mov x2, x10
|
|
1013
|
+
bl _memset
|
|
1014
|
+
.Lbuffer_move_range_T_end:
|
|
1015
|
+
```
|
|
1016
|
+
}
|
|
1017
|
+
|
|
661
1018
|
// A non-mv (ptr, len) slice [start, end) over this buffer's element
|
|
662
1019
|
// storage, returned as a `view T`. It borrows from self: it may not outlive
|
|
663
1020
|
// self's scope and is invalidated if self is reassigned. This is the single
|