nomen-lang 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/audit_runtime.c +12 -2
  2. package/core/System/Arena.nm +14 -5
  3. package/core/System/Array.nm +1 -1
  4. package/core/System/Atomic.nm +3 -3
  5. package/core/System/BigInt.nm +182 -23
  6. package/core/System/Buffer.nm +392 -33
  7. package/core/System/ClassBuffer.nm +145 -13
  8. package/core/System/Controls/Alert.nm +3 -3
  9. package/core/System/Controls/App.nm +17 -17
  10. package/core/System/Controls/Button.nm +2 -2
  11. package/core/System/Controls/CheckBox.nm +3 -3
  12. package/core/System/Controls/ColorPicker.nm +2 -2
  13. package/core/System/Controls/ComboBox.nm +2 -2
  14. package/core/System/Controls/Container.nm +39 -33
  15. package/core/System/Controls/DatePicker.nm +1 -1
  16. package/core/System/Controls/Form.nm +2 -2
  17. package/core/System/Controls/GroupBox.nm +2 -2
  18. package/core/System/Controls/ImageView.nm +2 -2
  19. package/core/System/Controls/Layout.nm +5 -5
  20. package/core/System/Controls/LevelIndicator.nm +2 -2
  21. package/core/System/Controls/ListView.nm +2 -2
  22. package/core/System/Controls/Menu.nm +2 -2
  23. package/core/System/Controls/NumberInput.nm +3 -3
  24. package/core/System/Controls/OtpInput.nm +2 -2
  25. package/core/System/Controls/OutlineView.nm +2 -2
  26. package/core/System/Controls/Pagination.nm +14 -14
  27. package/core/System/Controls/Popover.nm +2 -2
  28. package/core/System/Controls/Progress.nm +2 -2
  29. package/core/System/Controls/Radio.nm +3 -3
  30. package/core/System/Controls/Scrollable.nm +1 -1
  31. package/core/System/Controls/SearchField.nm +2 -2
  32. package/core/System/Controls/SecureField.nm +2 -2
  33. package/core/System/Controls/Segmented.nm +2 -2
  34. package/core/System/Controls/Select.nm +2 -2
  35. package/core/System/Controls/Separator.nm +2 -2
  36. package/core/System/Controls/Sheet.nm +2 -2
  37. package/core/System/Controls/Slider.nm +2 -2
  38. package/core/System/Controls/Spinner.nm +5 -5
  39. package/core/System/Controls/SplitView.nm +3 -3
  40. package/core/System/Controls/Stepper.nm +2 -2
  41. package/core/System/Controls/Switch.nm +2 -2
  42. package/core/System/Controls/Table.nm +2 -2
  43. package/core/System/Controls/Tabs.nm +2 -2
  44. package/core/System/Controls/Text.nm +2 -2
  45. package/core/System/Controls/TextBox.nm +3 -3
  46. package/core/System/Controls/TextView.nm +2 -2
  47. package/core/System/Controls/Toggle.nm +3 -3
  48. package/core/System/Controls/Toolbar.nm +2 -2
  49. package/core/System/Controls/Window.nm +12 -5
  50. package/core/System/Fiber.nm +16 -3
  51. package/core/System/Graph.nm +2 -2
  52. package/core/System/LinkedList.nm +13 -4
  53. package/core/System/List.nm +76 -17
  54. package/core/System/Map.nm +179 -21
  55. package/core/System/Mutex.nm +4 -4
  56. package/core/System/Random.nm +6 -7
  57. package/core/System/Set.nm +99 -23
  58. package/core/System/StableList.nm +371 -0
  59. package/core/System/Stream/Directory.nm +2 -2
  60. package/core/System/Stream/File.nm +18 -18
  61. package/core/System/Stream/Http.nm +5 -5
  62. package/core/System/Stream/Tcp.nm +5 -5
  63. package/core/System/String.nm +10 -5
  64. package/core/System/StringBuilder.nm +223 -12
  65. package/core/System/Task.nm +56 -64
  66. package/core/System/Test.nm +18 -18
  67. package/core/System/Text/CharIndex.nm +9 -9
  68. package/core/System/Text/Chars.nm +1 -1
  69. package/core/System/Text/Json.nm +72 -58
  70. package/core/System/Text/JsonTree.nm +126 -98
  71. package/core/System/Text/Regex.nm +43 -38
  72. package/core/System/Text/Utf8.nm +5 -5
  73. package/core/System/Thread.nm +26 -3
  74. package/core/System/Tree.nm +19 -3
  75. package/core/System/float.nm +36 -22
  76. package/core/System/int.nm +80 -28
  77. package/core/System/int16.nm +11 -0
  78. package/core/System/int32.nm +11 -0
  79. package/core/System/int64.nm +11 -0
  80. package/core/System/int8.nm +11 -0
  81. package/core/System/uint.nm +11 -0
  82. package/core/System/uint16.nm +11 -0
  83. package/core/System/uint32.nm +11 -0
  84. package/core/System/uint64.nm +11 -0
  85. package/core/System/uint8.nm +11 -0
  86. package/core/docs/System/StringBuilder.md +6 -0
  87. package/core/docs/System/Text/JsonTree.md +3 -2
  88. package/dist/index.mjs +37622 -30603
  89. package/package.json +1 -1
  90. package/src/bench_loop.nm +9 -9
@@ -5,20 +5,57 @@ pub mv struct List<T>: Viewable {
5
5
  var length = 0
6
6
  var items = Buffer<T>()
7
7
 
8
- pub func push = (ref self, mv T value) {
9
- if self.length >= self.items.cap {
10
- var new_cap = 4
11
- var int old_cap = self.items.cap
12
- if old_cap >= 4 {
13
- new_cap = old_cap * 2
8
+ pub func push = (ref self { items }, mv T value) {
9
+ if self.length < self.items.cap {
10
+ // The value arrives owned (`mv T value`): a value-struct element
11
+ // that OWNS resources moves into the fresh slot (store_take — the
12
+ // caller's storage zeroes, no deep copy). A non-owning value
13
+ // struct (a handle pair) is a legal bare argument (the checker
14
+ // does not demand `mv`), so taking it would silently zero the
15
+ // caller's copy — it keeps the copying store, like strings and
16
+ // scalars.
17
+ if T_TAKES_OWNERSHIP {
18
+ self.items.store_take(self.length, value)
19
+ } else {
20
+ self.items.store(self.length, value)
14
21
  }
15
- self.items.grow(new_cap)
22
+ self.length += 1
23
+ return
24
+ }
25
+ self.reserve(1)
26
+ if T_TAKES_OWNERSHIP {
27
+ self.items.store_take(self.length, value)
28
+ } else {
29
+ self.items.store(self.length, value)
16
30
  }
17
- self.items.store(self.length, value)
18
31
  self.length += 1
19
32
  }
20
33
 
21
- pub func pop = (ref self, mv out T) {
34
+ // Capacity for `extra` more elements without reallocation: the exact
35
+ // growth policy `push` uses (double from 4, so repeated single pushes
36
+ // amortize). Returns the resulting capacity. Splitting it out of `push`
37
+ // is what makes the compiler's in-place push lowering possible: the
38
+ // destination slot for a `push(parse(line))` argument must exist before
39
+ // the argument call runs, so the backend reserves first and constructs
40
+ // the callee's result directly into `items.data[length]` (the in-place
41
+ // #4). Sound for extra=1 by the same proof as push's old inline growth:
42
+ // length + 1 > cap grows to exactly the capacity push would have grown
43
+ // to.
44
+ internal func reserve = (ref self { items }, int extra: extra >= 0, out int) {
45
+ if self.length + extra <= self.items.cap {
46
+ return self.items.cap
47
+ }
48
+ var int new_cap = 4
49
+ var int old_cap = self.items.cap
50
+ if old_cap >= 4 {
51
+ new_cap = old_cap * 2
52
+ }
53
+ while new_cap < self.length + extra; new_cap *= 2 { }
54
+ self.items.grow(new_cap)
55
+ return self.items.cap
56
+ }
57
+
58
+ pub func pop = (ref self { items }, mv out T) {
22
59
  if self.length <= 0 {
23
60
  return self.items.move(0)
24
61
  }
@@ -57,7 +94,7 @@ pub mv struct List<T>: Viewable {
57
94
  return self.items.slice(start, end)
58
95
  }
59
96
 
60
- pub func set = (ref self, int i: i >= 0 && i < self.length, mv T value) {
97
+ pub func set = (ref self { items }, int i: i >= 0 && i < self.length, mv T value) {
61
98
  self.items.replace(i, value)
62
99
  }
63
100
 
@@ -65,7 +102,7 @@ pub mv struct List<T>: Viewable {
65
102
  // for indices that can't be proven at compile time (cross-module calls
66
103
  // can never discharge `set`'s parameter constraint) and out-of-range is
67
104
  // a bug, not a case.
68
- pub func set_or_panic = (ref self, int i, mv T value) {
105
+ pub func set_or_panic = (ref self { items }, int i, mv T value) {
69
106
  if i >= 0 && i < self.length {
70
107
  self.set(i, value)
71
108
  return
@@ -73,7 +110,30 @@ pub mv struct List<T>: Viewable {
73
110
  panic("index out of range")
74
111
  }
75
112
 
76
- // Address of element `i` (library-only): the ELEMENT BORROW primitive —
113
+ // Address of the APPEND slot (element `length`) — the in-place push
114
+ // lowering's destination primitive, the in-place push contract). reserve must have
115
+ // run, so the slot exists. Offsets mirror the mono layout: length @8,
116
+ // items.data @24 (the Buffer mono is {_vt, data, cap} = 24 bytes).
117
+ internal func append_slot_ptr = (ref self { items }, out ptr T) {
118
+ ```
119
+ #arch: c
120
+ // Byte math + void* — the body must compile for EVERY element kind
121
+ // (values, strings, class/trait handles), so it never names T.
122
+ // T_SIZE substitutes to the element's real stride.
123
+ return (void *)((unsigned long long)self->items.data +
124
+ (unsigned long long)self->length * T_SIZE);
125
+ ```
126
+ ```
127
+ #arch: aarch64
128
+ // x0 = self (stack-less raw: read from x0). Returns x0 = slot address.
129
+ ldr x9, [x0, #8]
130
+ ldr x10, [x0, #24]
131
+ mov x11, #T_SIZE
132
+ madd x0, x9, x11, x10
133
+ ```
134
+ }
135
+
136
+ // Address of element `i` (INTERNAL — the ELEMENT BORROW primitive):
77
137
  // `ref T x = list.at(i)` (docs/HANDLERS.md Stage 4, dynamic-tree
78
138
  // dispatch) lowers to this, binding the alias to the slot itself rather
79
139
  // than to a bitwise copy. The pointer is valid while the list is
@@ -82,7 +142,7 @@ pub mv struct List<T>: Viewable {
82
142
  // rules. The length constraint is the caller's proof obligation — the
83
143
  // handler desugar's synthesized loops satisfy it from the loop
84
144
  // condition, exactly like `.at`.
85
- pub func slot_ptr = (ref self, int i: i >= 0 && i < self.length, out ptr T) {
145
+ internal func slot_ptr = (ref self { items }, int i: i >= 0 && i < self.length, out ptr T) {
86
146
  return self.items.slot_ptr(i)
87
147
  }
88
148
 
@@ -93,15 +153,14 @@ pub mv struct List<T>: Viewable {
93
153
  // resources are reclaimed when the binding's scope ends — binding the
94
154
  // result in a nested block is the way to free an owning element at a
95
155
  // chosen point (its #destroy runs at the block's closing brace).
96
- pub func remove_at_or_panic = (ref self, int i, mv out T) {
156
+ pub func remove_at_or_panic = (ref self { items }, int i, mv out T) {
97
157
  if i < 0 || i >= self.length {
98
158
  panic("index out of range")
99
159
  }
100
160
  var T v = self.items.move(i)
101
161
  var int last = self.length - 1
102
- var int j = i
103
- while j < last; j += 1 {
104
- self.items.shift(j, j + 1)
162
+ if i < last {
163
+ self.items.move_range(i, i + 1, last - i)
105
164
  }
106
165
  self.length -= 1
107
166
  return v
@@ -7,12 +7,21 @@
7
7
  * bucket indexing is a mask (`hash & (cap - 1)`) instead of a division — and
8
8
  * it is correct for negative hashes too: two's-complement `&` already lands
9
9
  * in `[0, cap)`, which is why no negative adjustment is needed.
10
+ *
11
+ * Each slot carries its key's hash (truncated to 32 bits) in a parallel
12
+ * `hashes` buffer. Probes compare the stored hash before the key itself, so
13
+ * a probed slot whose hash differs skips the key load and `==` entirely (the
14
+ * Swiss-table short-circuit: the full key compare only runs on a hash match,
15
+ * and a false match still falls through to `==`, so truncation costs
16
+ * nothing); rehash re-inserts hash-free, reading the stored word instead of
17
+ * calling `key.hash()` again.
10
18
  **/
11
19
  pub mv struct Map<TK: Hashable + Equatable, TV> {
12
20
  var length = 0
13
21
  var keys = Buffer<TK>()
14
22
  var values = Buffer<TV>()
15
23
  var used = Buffer<int>()
24
+ var hashes = Buffer<int>()
16
25
 
17
26
  // Variadic-tuple constructor: Map<string, int>(["a", 1], ["b", 2]) or
18
27
  // Map(["a", 1], ["b", 2]) (type args inferred from the first pair).
@@ -30,7 +39,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
30
39
  }
31
40
  }
32
41
 
33
- pub func set = (ref self, mv TK key, mv TV value) {
42
+ pub func set = (ref self { hashes, keys, used, values }, mv TK key, mv TV value) {
34
43
  var int cap = self.keys.cap
35
44
  if cap == 0 {
36
45
  // alloc_T: for Buffer<TK> it is T_SIZE-aware (string keys are
@@ -39,28 +48,86 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
39
48
  cap = self.keys.alloc(8)
40
49
  self.values.alloc(8)
41
50
  self.used.alloc_u32(8)
51
+ self.hashes.alloc_u32(8)
42
52
  }
43
53
  if self.length * 4 >= cap * 3 {
44
54
  self.rehash(cap * 2)
45
55
  cap = self.keys.cap
46
56
  }
47
- var int idx = self.find_slot(key, cap)
57
+ var uint32 h = key.hash() as uint32
58
+ var int idx = self.find_slot(key, h, cap)
48
59
  if self.used.load_u32(idx) == 0 {
49
60
  self.used.store_u32(idx, 1)
50
- self.keys.store(idx, key)
51
- self.values.store(idx, value)
61
+ // Both arrive owned (`mv TK key, mv TV value`): a value-struct
62
+ // key/value that OWNS resources moves into its fresh slot
63
+ // (store_take — the caller's storage zeroes, no deep copy). A
64
+ // non-owning value struct is a legal bare argument and keeps the
65
+ // copying store; strings keep the deep-copying store too (their
66
+ // pair arrives by value).
67
+ if TK_TAKES_OWNERSHIP {
68
+ self.keys.store_take(idx, key)
69
+ } else {
70
+ self.keys.store(idx, key)
71
+ }
72
+ if TV_TAKES_OWNERSHIP {
73
+ self.values.store_take(idx, value)
74
+ } else {
75
+ self.values.store(idx, value)
76
+ }
77
+ self.hashes.store_u32(idx, h)
52
78
  self.length += 1
53
79
  } else {
54
80
  self.values.replace(idx, value)
55
81
  }
56
82
  }
57
83
 
84
+ // The IN-PLACE STORE primitive — the write-side sibling of `get_ptr`.
85
+ // Runs set's whole preamble (alloc-if-empty, rehash-at-load-factor) and
86
+ // probe with the KEY (which precedes the value in evaluation order at a
87
+ // `set(key, f(x))` site), then does everything set does EXCEPT write the
88
+ // value: the insert path marks the slot used, stores the key, and bumps
89
+ // length; the replace path reclaims the displaced value through
90
+ // `values.destroy_at` (the destroy half of replace, no copy). It returns
91
+ // the VALUE SLOT's address, so the compiler's in-place `set` lowering
92
+ // constructs `f(x)` straight into it — no `_param_N` temp home, no
93
+ // callee-side deep copy, no temp free. INTERNAL, like every slot
94
+ // primitive: the raw pointer must not cross the library boundary, and
95
+ // the caller must overwrite the slot before it is read or destroyed
96
+ // again (both paths leave it owning nothing).
97
+ internal func set_slot_ptr = (ref self { hashes, keys, used, values }, TK key, out ptr TV) {
98
+ var int cap = self.keys.cap
99
+ if cap == 0 {
100
+ // alloc_T: for Buffer<TK> it is T_SIZE-aware (string keys are
101
+ // 16-byte slots); for ClassBuffer<TV> it delegates to alloc_int
102
+ // (one pointer per slot). Both have it for generic parity.
103
+ cap = self.keys.alloc(8)
104
+ self.values.alloc(8)
105
+ self.used.alloc_u32(8)
106
+ self.hashes.alloc_u32(8)
107
+ }
108
+ if self.length * 4 >= cap * 3 {
109
+ self.rehash(cap * 2)
110
+ cap = self.keys.cap
111
+ }
112
+ var uint32 h = key.hash() as uint32
113
+ var int idx = self.find_slot(key, h, cap)
114
+ if self.used.load_u32(idx) == 0 {
115
+ self.used.store_u32(idx, 1)
116
+ self.keys.store(idx, key)
117
+ self.hashes.store_u32(idx, h)
118
+ self.length += 1
119
+ } else {
120
+ self.values.destroy_at(idx)
121
+ }
122
+ return self.values.slot_ptr(idx)
123
+ }
124
+
58
125
  pub func get = (self, TK key, out TV) {
59
126
  var int cap = self.keys.cap
60
127
  if cap == 0 {
61
128
  return 0
62
129
  }
63
- var int idx = self.find_slot(key, cap)
130
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
64
131
  if self.used.load_u32(idx) == 0 {
65
132
  return 0
66
133
  }
@@ -74,7 +141,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
74
141
  if cap == 0 {
75
142
  return fallback
76
143
  }
77
- var int idx = self.find_slot(key, cap)
144
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
78
145
  if self.used.load_u32(idx) == 0 {
79
146
  return fallback
80
147
  }
@@ -88,28 +155,54 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
88
155
  if cap == 0 {
89
156
  panic("key not found")
90
157
  }
91
- var int idx = self.find_slot(key, cap)
158
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
92
159
  if self.used.load_u32(idx) == 0 {
93
160
  panic("key not found")
94
161
  }
95
162
  return self.values.load(idx)
96
163
  }
97
164
 
165
+ // The VALUE SLOT BORROW — `Buffer.slot_ptr` for Map values. One probe
166
+ // (a `has` + `get` pair collapses into it) and the result aliases the
167
+ // slot instead of copying it, so container-bearing values stop paying
168
+ // the owning-specialized deep copy on every lookup. Traps when the key
169
+ // is absent — a missing key is a bug at a slot-borrow site (miss-as-case
170
+ // lookups use `get_or`). INTERNAL, like every slot-borrow primitive: the
171
+ // raw pointer must not cross the library boundary, so user code reaches
172
+ // it only through the element borrow, `ref TV v = map.get_or_panic(k)`,
173
+ // which the checker rewrites to this — the same lowering `ref T x =
174
+ // list.at(i)` gets from `slot_ptr`. The borrow is unpoliced inside the
175
+ // library: rehash/remove move or free slots, so do not hold it across
176
+ // one. This is also the anchor for in-place `set(key, f(x))` (probe
177
+ // first, construct into the slot).
178
+ internal func get_ptr = (self, TK key, out ptr TV) {
179
+ unsafe {
180
+ var int cap = self.keys.cap
181
+ if cap > 0 {
182
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
183
+ if self.used.load_u32(idx) != 0 {
184
+ return self.values.slot_ptr(idx)
185
+ }
186
+ }
187
+ panic("key not found")
188
+ }
189
+ }
190
+
98
191
  pub func has = (self, TK key, out bool) {
99
192
  var int cap = self.keys.cap
100
193
  if cap == 0 {
101
194
  return false
102
195
  }
103
- var int idx = self.find_slot(key, cap)
196
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
104
197
  return self.used.load_u32(idx) != 0
105
198
  }
106
199
 
107
- pub func remove = (ref self, TK key) {
200
+ pub func remove = (ref self { hashes, keys, used, values }, TK key) {
108
201
  var int cap = self.keys.cap
109
202
  if cap == 0 {
110
203
  return
111
204
  }
112
- var int idx = self.find_slot(key, cap)
205
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
113
206
  if self.used.load_u32(idx) == 0 {
114
207
  return
115
208
  }
@@ -122,7 +215,9 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
122
215
  // so entries past a non-moveable one are not stranded behind an empty
123
216
  // slot (which would make find_slot miss them). No tombstones, so
124
217
  // find_slot/get/has/set are unchanged, and the cost is the local cluster
125
- // length — not a full O(cap) rehash on every delete.
218
+ // length — not a full O(cap) rehash on every delete. `home` reads the
219
+ // stored hash word (no re-hash per shifted entry), and the word moves
220
+ // with the entry — find_slot's hash short-circuit trusts it.
126
221
  var int gap = idx
127
222
  var int k = idx
128
223
  while true {
@@ -133,7 +228,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
133
228
  if self.used.load_u32(k) == 0 {
134
229
  break
135
230
  }
136
- var int home = (self.keys.load(k).hash() as int) & (cap - 1)
231
+ var int home = (self.hashes.load_u32(k) as int) & (cap - 1)
137
232
  var int dgap = gap - home
138
233
  if dgap < 0 {
139
234
  dgap = dgap + cap
@@ -150,34 +245,77 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
150
245
  // used.store marks it empty (the buffer destroy skips it).
151
246
  self.keys.shift(gap, k)
152
247
  self.values.shift(gap, k)
248
+ self.hashes.store_u32(gap, self.hashes.load_u32(k))
153
249
  self.used.store_u32(gap, 1)
154
250
  gap = k
155
251
  }
156
252
  }
253
+ if TK_NEEDS_STRDUP {
254
+ if gap == idx {
255
+ // No entry shifted: the removed entry still owns its strdup'd
256
+ // bytes in its slot (a shift's first move would have
257
+ // reclaimed them — see shift_T above). destroy_at frees the
258
+ // buffer and zeroes the slot: a later set() that lands in
259
+ // this hole stores over the bytes assuming a fresh slot, and
260
+ // a stranded-then-overwritten entry leaks.
261
+ self.keys.destroy_at(idx)
262
+ }
263
+ }
264
+ if TK_IS_VALUE_STRUCT {
265
+ if gap == idx {
266
+ // Same hole for a value-struct key: reclaim the slot's owned
267
+ // fields (destroy_at frees and zeroes; scalars-only structs
268
+ // reclaim nothing and the call is a no-op).
269
+ self.keys.destroy_at(idx)
270
+ }
271
+ }
272
+ if TV_NEEDS_STRDUP {
273
+ if gap == idx {
274
+ self.values.destroy_at(idx)
275
+ }
276
+ }
277
+ if TV_IS_VALUE_STRUCT {
278
+ if gap == idx {
279
+ self.values.destroy_at(idx)
280
+ }
281
+ }
157
282
  self.used.store_u32(gap, 0)
158
283
  self.length = self.length - 1
159
284
  }
160
285
 
161
- // An independent copy: a fresh Map whose every member slab is a fresh
162
- // `Buffer.copy` (which deep-copies mv key/value elements), with the same
163
- // length and entries. Not available for class/trait keys or values (the
286
+ // An independent copy: a fresh Map whose every member slab is fresh
287
+ // (keys/values deep-copied per element, `used`/`hashes` copied word by
288
+ // word), with the same length and entries. The u32-word buffers are
289
+ // copied through their own primitives — `Buffer.copy` is T_SIZE-aware
290
+ // (8-byte loads for `int`), which would read past a 4-byte-per-slot
291
+ // `alloc_u32` slab. Not available for class/trait keys or values (the
164
292
  // member routes to ClassBuffer, which has no copy — the checker rejects
165
293
  // the call; see the copy gate in check_access_node).
166
294
  pub func copy = (self, out Map<TK, TV>) {
167
295
  var dst = Map<TK, TV>()
168
296
  dst.keys = self.keys.copy()
169
297
  dst.values = self.values.copy()
170
- dst.used = self.used.copy()
298
+ dst.used.alloc_u32(self.keys.cap)
299
+ dst.hashes.alloc_u32(self.keys.cap)
300
+ var i = 0
301
+ while i < self.keys.cap; i += 1 {
302
+ dst.used.store_u32(i, self.used.load_u32(i))
303
+ dst.hashes.store_u32(i, self.hashes.load_u32(i))
304
+ }
171
305
  dst.length = self.length
172
306
  return dst
173
307
  }
174
308
 
175
- func find_slot = (self, TK key, int cap, out int: out >= 0 && out < cap) {
309
+ func find_slot = (self, TK key, uint32 h, int cap, out int: out >= 0 && out < cap) {
176
310
  // cap is a power of two, so this is `hash mod cap` via a mask —
177
311
  // non-negative for any hash (two's complement), no adjustment.
178
- var int idx = (key.hash() as int) & (cap - 1)
312
+ var int idx = (h as int) & (cap - 1)
179
313
  while self.used.load_u32(idx) != 0 {
180
- if self.keys.load(idx) == key {
314
+ // The stored-hash word short-circuits the key compare: a probed
315
+ // slot whose hash differs (every step of a collision walk past
316
+ // the home slot) skips the key load and `==`. A hash match still
317
+ // falls through to `==`, so the 32-bit truncation costs nothing.
318
+ if self.hashes.load_u32(idx) == h && self.keys.load(idx) == key {
181
319
  return idx
182
320
  }
183
321
  idx += 1
@@ -188,15 +326,23 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
188
326
  return idx
189
327
  }
190
328
 
191
- func rehash = (ref self, int new_cap) {
329
+ // Rehash hash-free: the stored per-slot hash words move with the entries,
330
+ // so no `key.hash()` runs here and the insertion probe needs no key
331
+ // compares (keys arrive unique — they came from a valid table). This is
332
+ // the other half of the Swiss-table trick; the old form re-inserted via
333
+ // `self.set(mv k, mv v)`, re-hashing every key and re-comparing keys on
334
+ // every probed slot.
335
+ func rehash = (ref self { hashes, keys, used, values }, int new_cap) {
192
336
  var Buffer<TK> old_keys = mv self.keys swap Buffer<TK>()
193
337
  var Buffer<TV> old_values = mv self.values swap Buffer<TV>()
194
338
  var Buffer<int> old_used = mv self.used swap Buffer<int>()
339
+ var Buffer<int> old_hashes = mv self.hashes swap Buffer<int>()
195
340
  var int old_cap = old_keys.cap
196
341
 
197
342
  self.keys.alloc(new_cap)
198
343
  self.values.alloc(new_cap)
199
344
  self.used.alloc_u32(new_cap)
345
+ self.hashes.alloc_u32(new_cap)
200
346
  self.length = 0
201
347
 
202
348
  var i = 0
@@ -213,7 +359,19 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
213
359
  // move_T results.
214
360
  var TK k = old_keys.move(i)
215
361
  var TV v = old_values.move(i)
216
- self.set(mv k, mv v)
362
+ var uint32 h = old_hashes.load_u32(i)
363
+ var int idx = (h as int) & (new_cap - 1)
364
+ while self.used.load_u32(idx) != 0 {
365
+ idx += 1
366
+ if idx >= new_cap {
367
+ idx = 0
368
+ }
369
+ }
370
+ self.used.store_u32(idx, 1)
371
+ self.keys.store(idx, k)
372
+ self.values.store(idx, v)
373
+ self.hashes.store_u32(idx, h)
374
+ self.length += 1
217
375
  }
218
376
  }
219
377
  }
@@ -25,10 +25,10 @@ pub mv struct Mutex {
25
25
  pub var uint64 handle = 0
26
26
 
27
27
  pub func #init = (self) {
28
- // The constructor's `self` is the INLINE instance (a local value in the
29
- // C lowering — methods take the pointer form, the ctor does not), so the
30
- // C body uses the `.` accessor; the aarch64 body writes through x19 =
31
- // &instance as always.
28
+ // The constructor's `self` is the INLINE instance (a local value in the
29
+ // C lowering — methods take the pointer form, the ctor does not), so the
30
+ // C body uses the `.` accessor; the aarch64 body writes through x19 =
31
+ // &instance as always.
32
32
  ```
33
33
  #arch: c
34
34
  self.handle = (unsigned long long)__nomen_mutex_create();
@@ -41,11 +41,10 @@ pub mv struct Random {
41
41
 
42
42
  // The next 64-bit value (splitmix64: golden-ratio step, then two
43
43
  // multiply-xor rounds). Pure Nomen — the 64-bit arithmetic, bitwise
44
- // operators, and hex literals all lower directly. The aarch64 codegen is
45
- // a little looser than a hand-written `#arch` body would be (constant
46
- // materialization, field reloads, non-immediate shift counts); see
47
- // FOLLOWUP.md if this ever needs to be hot.
48
- pub func next = (ref self, out uint64) {
44
+ // operators, and hex literals all lower directly; the aarch64 64-bit
45
+ // arithmetic folds (PERF_PLAN item 15) keep the emission within a few
46
+ // instructions of a hand-written `#arch` body.
47
+ pub func next = (ref self {}, out uint64) {
49
48
  self.state = self.state + 0x9E3779B97F4A7C15
50
49
  var uint64 z = self.state
51
50
  z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9
@@ -55,7 +54,7 @@ pub mv struct Random {
55
54
 
56
55
  // The next value in [0, bound). A bound of 0 returns 0. Slight modulo
57
56
  // bias for very large bounds — irrelevant at demo scale.
58
- pub func below = (ref self, uint64 bound, out uint64) {
57
+ pub func below = (ref self {}, uint64 bound, out uint64) {
59
58
  if bound == 0 {
60
59
  return 0
61
60
  }
@@ -64,7 +63,7 @@ pub mv struct Random {
64
63
 
65
64
  // The next value in [lo, hi], both ends inclusive. A hi <= lo
66
65
  // returns lo.
67
- pub func range = (ref self, uint64 lo, uint64 hi, out uint64) {
66
+ pub func range = (ref self {}, uint64 lo, uint64 hi, out uint64) {
68
67
  if hi <= lo {
69
68
  return lo
70
69
  }