nomen-lang 0.2.3 → 0.3.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.
@@ -0,0 +1,166 @@
1
+ // Arena<T>: a generational arena — one owner of every slot, with cheap
2
+ // copyable handles (`ArenaRef<T>`) instead of borrows. This is the single-ownership
3
+ // answer to "many places need to refer to the same node": the arena owns the
4
+ // values, everyone else holds `ArenaRef<T>` (a plain { index, generation } value
5
+ // that copies freely and never dangles undetected).
6
+ //
7
+ // Why not `List<T>` + raw `int` indices? An index silently aliases a recycled
8
+ // slot after a removal — the classic stale-index bug. An `ArenaRef<T>` carries the
9
+ // slot's generation, bumped on every `free`; a stale handle fails the
10
+ // generation check and `get` panics (`is_valid` / `get_or` test it without
11
+ // trapping), so use-after-free is DETECTED instead of silently reading
12
+ // another node.
13
+ //
14
+ // The arena is the sole owner: `free` relinquishes the slot's value (moved out
15
+ // and reclaimed by the ordinary ownership machinery) and bumps the generation;
16
+ // the arena's own #destroy reclaims every still-live slot at scope exit.
17
+
18
+ /**
19
+ * A copyable, non-owning handle to an `Arena<T>` slot. The generation makes a
20
+ * reused slot distinguishable from the one this handle was issued for.
21
+ **/
22
+ pub struct ArenaRef<T> {
23
+ var int index = -1
24
+ var int generation = -1
25
+
26
+ pub func is_null = (self, out bool) {
27
+ return self.index < 0
28
+ }
29
+ }
30
+
31
+ /**
32
+ * A generational arena: one owner of a set of `T` values, addressed by
33
+ * copyable `ArenaRef<T>` handles.
34
+ **/
35
+ pub struct Arena<T> {
36
+ // Slot payloads. T = value struct/primitive routes to Buffer<T>; T =
37
+ // class/trait routes to ClassBuffer<T> (monomorphize rewrites the field),
38
+ // so slot ownership follows the usual element rules.
39
+ var Buffer<T> values = Buffer<T>()
40
+ // Per-slot generation, bumped on every free. Slots are calloc'd, so a
41
+ // fresh slot's generation is 0.
42
+ var Buffer<int> generations = Buffer<int>()
43
+ // Freed slot indices available for reuse (LIFO free list). A raw Buffer
44
+ // plus an explicit count: a `List<int>` field would need its own
45
+ // monomorphization from inside a generic struct body.
46
+ var Buffer<int> free_indices = Buffer<int>()
47
+ var int free_count = 0
48
+ // High-water mark: every index < count has a slot (live or free).
49
+ var int count = 0
50
+
51
+ /**
52
+ * A fresh arena with no slots.
53
+ **/
54
+ pub func #init = (ref self) {
55
+ self.values = Buffer<T>()
56
+ self.generations = Buffer<int>()
57
+ self.free_indices = Buffer<int>()
58
+ self.free_count = 0
59
+ self.count = 0
60
+ }
61
+
62
+ /**
63
+ * Store `value` in a slot (reusing a freed slot when one is available) and
64
+ * return a handle to it.
65
+ **/
66
+ pub func alloc = (ref self, move T value, out ArenaRef<T>) {
67
+ var int index = -1
68
+ if self.free_count > 0 {
69
+ // Reuse a freed slot. Its generation was already bumped by `free`,
70
+ // so stale handles to it are already invalid.
71
+ self.free_count = self.free_count - 1
72
+ index = self.free_indices.load_int(self.free_count)
73
+ } else {
74
+ index = self.count
75
+ self.count = index + 1
76
+ self.generations.grow_int(index + 1)
77
+ self.generations.store_int(index, 0)
78
+ }
79
+ self.values.grow_T(index + 1)
80
+ self.values.store_T(index, value)
81
+ var ArenaRef<T> handle = ArenaRef<T>()
82
+ handle.index = index
83
+ handle.generation = self.generations.load_int(index)
84
+ return handle
85
+ }
86
+
87
+ /**
88
+ * Relinquish the slot `handle` points at (its value is reclaimed), bumping the
89
+ * generation so every outstanding handle to that slot becomes stale.
90
+ **/
91
+ pub func free = (ref self, ArenaRef<T> handle) {
92
+ if !self.is_valid(handle) {
93
+ panic("arena: free of a stale handle")
94
+ }
95
+ var T discarded = self.values.move_T(handle.index)
96
+ self.generations.store_int(handle.index, handle.generation + 1)
97
+ self.free_indices.grow_int(self.free_count + 1)
98
+ self.free_indices.store_int(self.free_count, handle.index)
99
+ self.free_count = self.free_count + 1
100
+ }
101
+
102
+ /**
103
+ * The current generation of a slot index (raw; for `ArenaRef` bookkeeping).
104
+ **/
105
+ pub func generation_of = (self, int index: index >= 0 && index < self.count, out int) {
106
+ return self.generations.load_int(index)
107
+ }
108
+
109
+ /**
110
+ * Whether `handle` still names a live slot.
111
+ **/
112
+ pub func is_valid = (self, ArenaRef<T> handle, out bool) {
113
+ if handle.index < 0 || handle.index >= self.count {
114
+ return false
115
+ }
116
+ return self.generations.load_int(handle.index) == handle.generation
117
+ }
118
+
119
+ /**
120
+ * The value `handle` names. Panics on a stale handle (use-after-free is a
121
+ * bug, not a case — use `is_valid` / `get_or` when absence is expected).
122
+ **/
123
+ pub func get = (self, ArenaRef<T> handle, out T) {
124
+ if !self.is_valid(handle) {
125
+ panic("arena: get of a stale handle")
126
+ }
127
+ return self.values.load_T(handle.index)
128
+ }
129
+
130
+ /**
131
+ * The value `handle` names, or `fallback` when the handle is stale.
132
+ **/
133
+ pub func get_or = (self, ArenaRef<T> handle, T fallback, out T) {
134
+ if !self.is_valid(handle) {
135
+ return fallback
136
+ }
137
+ return self.values.load_T(handle.index)
138
+ }
139
+
140
+ /**
141
+ * Replace the value `handle` names (same slot, same handle).
142
+ **/
143
+ pub func set = (ref self, ArenaRef<T> handle, move T value) {
144
+ if !self.is_valid(handle) {
145
+ panic("arena: set through a stale handle")
146
+ }
147
+ self.values.replace_T(handle.index, value)
148
+ }
149
+
150
+ /**
151
+ * The number of slots ever allocated (live + freed). Indices `0 .. count()`
152
+ * can be walked with `get_index`; freed slots return their stale value, so
153
+ * prefer `is_valid` + `get` when iterating handles.
154
+ **/
155
+ pub func count_all = (self, out int) {
156
+ return self.count
157
+ }
158
+
159
+ /**
160
+ * Raw slot access by index — for iteration over the index space. Does not
161
+ * generation-check; callers own the index bookkeeping.
162
+ **/
163
+ pub func get_index = (self, int index: index >= 0 && index < self.count, out T) {
164
+ return self.values.load_T(index)
165
+ }
166
+ }
@@ -30,134 +30,40 @@ pub struct Array<T>: Viewable {
30
30
  ```
31
31
  }
32
32
 
33
+ // Inline-storage element access — `unsafe` Nomen single-sourced for both
34
+ // backends (docs/CORE_RAW.md roadmap item 2). `self[index]` lowers to the
35
+ // element base each backend already uses for Array receivers (C skips the
36
+ // struct header; aarch64's receiver IS the first element).
33
37
  pub inline func at = (self, int index: index >= 0 && index < self.length, out T) {
34
- ```
35
- #arch: c
36
- T* _data = (T*)((char*)self + sizeof(*self));
37
- return _data[index];
38
- ```
39
- ```
40
- #arch: aarch64
41
- // x19 = self (first elem), x1 = index, x8 = return buf for structs
42
- mov x2, #T_SIZE
43
- mul x1, x1, x2
44
- cmp x2, #8
45
- b.eq .L_at_8
46
- b.gt .L_at_copy
47
- cmp x2, #1
48
- b.eq .L_at_1
49
- cmp x2, #2
50
- b.eq .L_at_2
51
- ldr w0, [x19, x1]
52
- b .L_at_end
53
- .L_at_8:
54
- ldr x0, [x19, x1]
55
- b .L_at_end
56
- .L_at_1:
57
- ldrb w0, [x19, x1]
58
- b .L_at_end
59
- .L_at_2:
60
- ldrh w0, [x19, x1]
61
- b .L_at_end
62
- // Struct element (T_SIZE > 8): copy the slot bytes into the x8
63
- // sret buffer and return its address (Buffer.load_T convention).
64
- .L_at_copy:
65
- mov x0, x8
66
- add x1, x19, x1
67
- bl _memcpy
68
- mov x0, x8
69
- .L_at_end:
70
- ```
38
+ unsafe {
39
+ return self[index]
40
+ }
71
41
  }
72
42
 
73
43
  pub func first = (self: self.length > 0, out T) {
74
- ```
75
- #arch: c
76
- T* _data = (T*)((char*)self + sizeof(*self));
77
- return _data[0];
78
- ```
79
- ```
80
- #arch: aarch64
81
- // x19 = self (first elem), x8 = return buf for structs
82
- mov x3, #T_SIZE
83
- cmp x3, #8
84
- b.eq .L_first_8
85
- b.gt .L_first_copy
86
- cmp x3, #1
87
- b.eq .L_first_1
88
- cmp x3, #2
89
- b.eq .L_first_2
90
- ldr w0, [x19]
91
- b .L_first_end
92
- .L_first_8:
93
- ldr x0, [x19]
94
- b .L_first_end
95
- .L_first_1:
96
- ldrb w0, [x19]
97
- b .L_first_end
98
- .L_first_2:
99
- ldrh w0, [x19]
100
- b .L_first_end
101
- // Struct element (T_SIZE > 8): copy the first slot's bytes into
102
- // the x8 sret buffer and return its address.
103
- .L_first_copy:
104
- mov x0, x8
105
- mov x1, x19
106
- mov x2, x3
107
- bl _memcpy
108
- mov x0, x8
109
- .L_first_end:
110
- ```
44
+ unsafe {
45
+ return self[0]
46
+ }
111
47
  }
112
48
 
49
+ // The string-slot specialization the raw `#if T_NEEDS_STRDUP` blocks used
50
+ // to express: T_NEEDS_STRDUP is a per-instantiation constant the
51
+ // monomorphizer substitutes (true for string, false otherwise) and folds,
52
+ // so each mono body keeps only its arm.
113
53
  pub func set = (ref self, int index: index >= 0 && index < self.length, T value) {
114
- ```
115
- #arch: c
116
- T* _data = (T*)((char*)self + sizeof(*self));
117
- // T_NEEDS_STRDUP is 1 when T is string (a fat value whose backing
118
- // bytes must be heap-copied per slot — otherwise the slot would share
119
- // the incoming pointer and auto_free would double-free it). 0 for all
120
- // other T (plain assignment).
121
- #if T_NEEDS_STRDUP
122
- free(_data[index].ptr);
123
- _data[index] = nomen_str_dup(value);
124
- #else
125
- _data[index] = value;
126
- #endif
127
- ```
128
- ```
129
- #arch: aarch64
130
- // x19 = self (first elem), x1 = index, x2 = value (address if struct)
131
- mov x3, #T_SIZE
132
- mul x1, x1, x3
133
- cmp x3, #8
134
- b.eq .L_set_8
135
- b.gt .L_set_copy
136
- cmp x3, #1
137
- b.eq .L_set_1
138
- cmp x3, #2
139
- b.eq .L_set_2
140
- str w2, [x19, x1]
141
- b .L_set_done
142
- .L_set_8:
143
- str x2, [x19, x1]
144
- b .L_set_done
145
- .L_set_1:
146
- strb w2, [x19, x1]
147
- b .L_set_done
148
- .L_set_2:
149
- strh w2, [x19, x1]
150
- b .L_set_done
151
- // Struct element (T_SIZE > 8): x2 holds the ADDRESS of the value
152
- // bytes — memcpy them into the slot (Buffer.store_T convention).
153
- .L_set_copy:
154
- mov x0, x19
155
- add x0, x0, x1
156
- mov x1, x2
157
- mov x2, x3
158
- bl _memcpy
159
- .L_set_done:
160
- ```
54
+ unsafe {
55
+ if T_NEEDS_STRDUP {
56
+ // T is string: a fat value whose backing bytes must be
57
+ // heap-copied per slot — otherwise the slot would share the
58
+ // incoming pointer and auto_free would double-free it. Free
59
+ // the displaced slot's bytes (no local: a named copy would be
60
+ // auto-freed too), then store a fresh deep copy.
61
+ free((self[index] as ptr char) as uint64)
62
+ self[index] = strdup(value)
63
+ } else {
64
+ self[index] = value
65
+ }
66
+ }
161
67
  }
162
68
 
163
69
  // Bounds-checked accessors — the runtime-checked escape hatch for
@@ -181,46 +87,9 @@ pub struct Array<T>: Viewable {
181
87
  }
182
88
 
183
89
  pub func at_end = (self, out T) {
184
- ```
185
- #arch: c
186
- T* _data = (T*)((char*)self + sizeof(*self));
187
- return _data[self->length - 1];
188
- ```
189
- ```
190
- #arch: aarch64
191
- // x19 = self (first elem), length at [x19 - 8], x8 = return buf for structs
192
- ldr x0, [x19, #-8]
193
- sub x0, x0, #1
194
- mov x1, #T_SIZE
195
- mul x0, x0, x1
196
- cmp x1, #8
197
- b.eq .L_at_end_8
198
- b.gt .L_at_end_copy
199
- cmp x1, #1
200
- b.eq .L_at_end_1
201
- cmp x1, #2
202
- b.eq .L_at_end_2
203
- ldr w0, [x19, x0]
204
- b .L_at_end_done
205
- .L_at_end_8:
206
- ldr x0, [x19, x0]
207
- b .L_at_end_done
208
- .L_at_end_1:
209
- ldrb w0, [x19, x0]
210
- b .L_at_end_done
211
- .L_at_end_2:
212
- ldrh w0, [x19, x0]
213
- b .L_at_end_done
214
- // Struct element (T_SIZE > 8): x0 = byte offset of the last slot —
215
- // copy its bytes into the x8 sret buffer and return its address.
216
- .L_at_end_copy:
217
- mov x2, x1
218
- add x1, x19, x0
219
- mov x0, x8
220
- bl _memcpy
221
- mov x0, x8
222
- .L_at_end_done:
223
- ```
90
+ unsafe {
91
+ return self[self.length - 1]
92
+ }
224
93
  }
225
94
 
226
95
  // A non-owning (ptr, len) slice [start, end) over self's inline element
@@ -20,65 +20,54 @@ pub struct BigInt {
20
20
  return self
21
21
  }
22
22
 
23
+ // Per-limb get/set stay raw: they are `inline` and splice into the limb
24
+ // loops, where the raw bodies are 1-2 instructions with no param
25
+ // marshalling. The unsafe-Nomen forms measured a 3x pidigits regression
26
+ // on aarch64 (the inline-method ABI parks self and spills locals per
27
+ // splice); the C backend (single TU, -O2) showed no difference either
28
+ // way, so the raw blocks win on single-sourcing grounds being equal.
29
+ // See docs/CORE_RAW.md "unsafe outcomes".
23
30
  inline func get = (self, int i, out uint64) {
24
- ```
25
- #arch: c
26
- return ((unsigned long long*)self->digits.data)[i];
27
- ```
28
- ```
29
- #arch: aarch64
30
- ldr x0, [x0, #32]
31
- ldr x0, [x0, x1, lsl #3]
32
- ```
31
+ unsafe {
32
+ var ptr uint64 limbs = self.digits.data as ptr uint64
33
+ return limbs[i]
34
+ }
33
35
  }
34
36
 
35
37
  inline func set = (ref self, int i, uint64 val) {
36
- ```
37
- #arch: c
38
- ((unsigned long long*)self->digits.data)[i] = val;
39
- ```
40
- ```
41
- #arch: aarch64
42
- ldr x0, [x19, #32]
43
- str x2, [x0, x1, lsl #3]
44
- ```
38
+ unsafe {
39
+ var ptr uint64 limbs = self.digits.data as ptr uint64
40
+ limbs[i] = val
41
+ }
45
42
  }
46
43
 
47
44
  // Raw data-pointer accessors. The limb loops below hoist `digits.data`
48
45
  // into a local once (before the loop) and index through it via get_at /
49
46
  // set_at, so each limb access is a single strided load/store instead of
50
47
  // re-reading self->digits.data every iteration. ptr is held in a uint64.
48
+ // All five stay raw: measured on aarch64, even the loop-hoisted
49
+ // data_ptr as Nomen cost pidigits ~30% (the inline splice perturbs the
50
+ // whole-function asm plan); the C backend was identical either way.
51
+ // See docs/CORE_RAW.md "unsafe outcomes".
51
52
  inline func data_ptr = (self, out uint64) {
52
- ```
53
- #arch: c
54
- return (unsigned long long)self->digits.data;
55
- ```
56
- ```
57
- #arch: aarch64
58
- ldr x0, [x0, #32]
59
- ```
53
+ return self.digits.data
60
54
  }
61
55
 
56
+ // Same story as get/set above: the hoisted-pointer limb accessors stay
57
+ // raw for the aarch64 splice path. `data_ptr` is called once per loop
58
+ // (to hoist the slab address), so its Nomen body is free.
62
59
  inline func get_at = (self, uint64 ptr, int i, out uint64) {
63
- ```
64
- #arch: c
65
- return ((unsigned long long*)ptr)[i];
66
- ```
67
- ```
68
- #arch: aarch64
69
- ldr x0, [x1, x2, lsl #3]
70
- ```
60
+ unsafe {
61
+ var ptr uint64 limbs = ptr as ptr uint64
62
+ return limbs[i]
63
+ }
71
64
  }
72
65
 
73
66
  inline func set_at = (ref self, uint64 ptr, int i, uint64 val) {
74
- ```
75
- #arch: c
76
- ((unsigned long long*)ptr)[i] = val;
77
- ```
78
- ```
79
- #arch: aarch64
80
- str x3, [x1, x2, lsl #3]
81
- ```
67
+ unsafe {
68
+ var ptr uint64 limbs = ptr as ptr uint64
69
+ limbs[i] = val
70
+ }
82
71
  }
83
72
 
84
73
  func ensure = (ref self, int needed) {