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.
Files changed (73) hide show
  1. package/audit_runtime.c +12 -2
  2. package/core/System/Arena.nm +8 -1
  3. package/core/System/Buffer.nm +366 -9
  4. package/core/System/ClassBuffer.nm +133 -1
  5. package/core/System/Controls/Alert.nm +2 -2
  6. package/core/System/Controls/App.nm +12 -12
  7. package/core/System/Controls/Button.nm +1 -1
  8. package/core/System/Controls/CheckBox.nm +2 -2
  9. package/core/System/Controls/ColorPicker.nm +1 -1
  10. package/core/System/Controls/ComboBox.nm +1 -1
  11. package/core/System/Controls/Container.nm +7 -1
  12. package/core/System/Controls/GroupBox.nm +1 -1
  13. package/core/System/Controls/ImageView.nm +1 -1
  14. package/core/System/Controls/LevelIndicator.nm +1 -1
  15. package/core/System/Controls/ListView.nm +1 -1
  16. package/core/System/Controls/Menu.nm +1 -1
  17. package/core/System/Controls/OutlineView.nm +1 -1
  18. package/core/System/Controls/Pagination.nm +8 -8
  19. package/core/System/Controls/Popover.nm +1 -1
  20. package/core/System/Controls/Progress.nm +1 -1
  21. package/core/System/Controls/Radio.nm +2 -2
  22. package/core/System/Controls/SearchField.nm +1 -1
  23. package/core/System/Controls/SecureField.nm +1 -1
  24. package/core/System/Controls/Segmented.nm +1 -1
  25. package/core/System/Controls/Select.nm +1 -1
  26. package/core/System/Controls/Separator.nm +1 -1
  27. package/core/System/Controls/Sheet.nm +1 -1
  28. package/core/System/Controls/Slider.nm +1 -1
  29. package/core/System/Controls/Spinner.nm +2 -2
  30. package/core/System/Controls/SplitView.nm +1 -1
  31. package/core/System/Controls/Stepper.nm +1 -1
  32. package/core/System/Controls/Switch.nm +1 -1
  33. package/core/System/Controls/Table.nm +1 -1
  34. package/core/System/Controls/Tabs.nm +1 -1
  35. package/core/System/Controls/Text.nm +1 -1
  36. package/core/System/Controls/TextBox.nm +2 -2
  37. package/core/System/Controls/TextView.nm +1 -1
  38. package/core/System/Controls/Toggle.nm +2 -2
  39. package/core/System/Controls/Toolbar.nm +1 -1
  40. package/core/System/Controls/Window.nm +1 -2
  41. package/core/System/LinkedList.nm +8 -1
  42. package/core/System/List.nm +68 -12
  43. package/core/System/Map.nm +174 -18
  44. package/core/System/Mutex.nm +4 -4
  45. package/core/System/Random.nm +3 -4
  46. package/core/System/Set.nm +94 -20
  47. package/core/System/StableList.nm +371 -0
  48. package/core/System/Stream/Directory.nm +2 -2
  49. package/core/System/Stream/File.nm +2 -2
  50. package/core/System/Stream/Http.nm +2 -2
  51. package/core/System/String.nm +10 -5
  52. package/core/System/StringBuilder.nm +213 -2
  53. package/core/System/Text/CharIndex.nm +9 -9
  54. package/core/System/Text/Json.nm +60 -46
  55. package/core/System/Text/JsonTree.nm +120 -92
  56. package/core/System/Text/Regex.nm +23 -18
  57. package/core/System/Text/Utf8.nm +5 -5
  58. package/core/System/float.nm +36 -22
  59. package/core/System/int.nm +80 -28
  60. package/core/System/int16.nm +11 -0
  61. package/core/System/int32.nm +11 -0
  62. package/core/System/int64.nm +11 -0
  63. package/core/System/int8.nm +11 -0
  64. package/core/System/uint.nm +11 -0
  65. package/core/System/uint16.nm +11 -0
  66. package/core/System/uint32.nm +11 -0
  67. package/core/System/uint64.nm +11 -0
  68. package/core/System/uint8.nm +11 -0
  69. package/core/docs/System/StringBuilder.md +6 -0
  70. package/core/docs/System/Text/JsonTree.md +3 -2
  71. package/dist/index.mjs +36575 -30747
  72. package/package.json +1 -1
  73. 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 (getenv("NOMEN_AUDIT_TRACE")) fprintf(stderr, "ALLOC %lu\n", (unsigned long)size);
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 (getenv("NOMEN_AUDIT_TRACE")) fprintf(stderr, "FREE %lu\n", (unsigned long)header->size);
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;
@@ -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
- self.values.store(index, value)
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)
@@ -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; load/store use memcpy so multi-word structs work
219
- // correctly. Classes route to ClassBuffer<T> (per-element destroy), so T
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, #8
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, #8
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, #8
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, #8
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
- pub func slot_ptr = (ref self, int i, out ptr T) {
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, #8
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