nomen-lang 0.7.1 → 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 (80) hide show
  1. package/audit_runtime.c +12 -2
  2. package/core/System/Arena.nm +8 -1
  3. package/core/System/Buffer.nm +397 -8
  4. package/core/System/ClassBuffer.nm +156 -0
  5. package/core/System/Controls/Alert.nm +326 -0
  6. package/core/System/Controls/App.nm +147 -0
  7. package/core/System/Controls/Button.nm +119 -1
  8. package/core/System/Controls/CheckBox.nm +25 -1
  9. package/core/System/Controls/ColorPicker.nm +307 -0
  10. package/core/System/Controls/ComboBox.nm +430 -0
  11. package/core/System/Controls/Container.nm +843 -9
  12. package/core/System/Controls/Control.nm +443 -0
  13. package/core/System/Controls/DatePicker.nm +288 -0
  14. package/core/System/Controls/Form.nm +70 -0
  15. package/core/System/Controls/GroupBox.nm +267 -0
  16. package/core/System/Controls/ImageView.nm +311 -0
  17. package/core/System/Controls/LevelIndicator.nm +305 -0
  18. package/core/System/Controls/ListView.nm +633 -0
  19. package/core/System/Controls/Menu.nm +428 -0
  20. package/core/System/Controls/NumberInput.nm +56 -0
  21. package/core/System/Controls/OtpInput.nm +50 -0
  22. package/core/System/Controls/OutlineView.nm +598 -0
  23. package/core/System/Controls/Pagination.nm +142 -0
  24. package/core/System/Controls/Popover.nm +296 -0
  25. package/core/System/Controls/Progress.nm +348 -0
  26. package/core/System/Controls/Radio.nm +292 -0
  27. package/core/System/Controls/Scrollable.nm +584 -0
  28. package/core/System/Controls/SearchField.nm +326 -0
  29. package/core/System/Controls/SecureField.nm +344 -0
  30. package/core/System/Controls/Segmented.nm +380 -0
  31. package/core/System/Controls/Select.nm +340 -0
  32. package/core/System/Controls/Separator.nm +198 -0
  33. package/core/System/Controls/Sheet.nm +329 -0
  34. package/core/System/Controls/Slider.nm +315 -0
  35. package/core/System/Controls/Spinner.nm +287 -0
  36. package/core/System/Controls/SplitView.nm +509 -0
  37. package/core/System/Controls/Stepper.nm +349 -0
  38. package/core/System/Controls/Switch.nm +259 -0
  39. package/core/System/Controls/Table.nm +833 -0
  40. package/core/System/Controls/Tabs.nm +500 -0
  41. package/core/System/Controls/Text.nm +108 -11
  42. package/core/System/Controls/TextBox.nm +113 -2
  43. package/core/System/Controls/TextView.nm +329 -0
  44. package/core/System/Controls/Toggle.nm +346 -0
  45. package/core/System/Controls/Toolbar.nm +501 -0
  46. package/core/System/Controls/Tooltip.nm +76 -0
  47. package/core/System/Controls/Window.nm +1548 -91
  48. package/core/System/LinkedList.nm +8 -1
  49. package/core/System/List.nm +97 -7
  50. package/core/System/Map.nm +191 -18
  51. package/core/System/Mutex.nm +4 -4
  52. package/core/System/Random.nm +3 -4
  53. package/core/System/Set.nm +101 -13
  54. package/core/System/StableList.nm +371 -0
  55. package/core/System/Stream/Directory.nm +2 -2
  56. package/core/System/Stream/File.nm +2 -2
  57. package/core/System/Stream/Http.nm +2 -2
  58. package/core/System/String.nm +10 -5
  59. package/core/System/StringBuilder.nm +213 -2
  60. package/core/System/Text/CharIndex.nm +9 -9
  61. package/core/System/Text/Json.nm +60 -46
  62. package/core/System/Text/JsonTree.nm +120 -92
  63. package/core/System/Text/Regex.nm +23 -18
  64. package/core/System/Text/Utf8.nm +5 -5
  65. package/core/System/float.nm +36 -22
  66. package/core/System/int.nm +80 -28
  67. package/core/System/int16.nm +11 -0
  68. package/core/System/int32.nm +11 -0
  69. package/core/System/int64.nm +11 -0
  70. package/core/System/int8.nm +11 -0
  71. package/core/System/uint.nm +11 -0
  72. package/core/System/uint16.nm +11 -0
  73. package/core/System/uint32.nm +11 -0
  74. package/core/System/uint64.nm +11 -0
  75. package/core/System/uint8.nm +11 -0
  76. package/core/docs/System/StringBuilder.md +6 -0
  77. package/core/docs/System/Text/JsonTree.md +3 -2
  78. package/dist/index.mjs +38332 -30604
  79. package/package.json +1 -1
  80. package/src/bench_loop.nm +9 -9
@@ -16,7 +16,14 @@ pub mv struct LinkedList<T>: Enumerable {
16
16
  pub func add = (ref self, mv T value) {
17
17
  var int idx = self.count
18
18
  self.values.grow(idx + 1)
19
- self.values.store(idx, value)
19
+ // The value arrives owned (`mv T value`): move a value-struct element
20
+ // into the fresh slot (the caller's storage zeroes, no deep copy);
21
+ // strings keep the deep-copying store (their pair arrives by value).
22
+ if T_IS_VALUE_STRUCT {
23
+ self.values.store_take(idx, value)
24
+ } else {
25
+ self.values.store(idx, value)
26
+ }
20
27
  self.nexts.grow_int(idx + 1)
21
28
  self.nexts.store_int(idx, -1)
22
29
  self.prevs.grow_int(idx + 1)
@@ -6,18 +6,52 @@ pub mv struct List<T>: Viewable {
6
6
  var items = Buffer<T>()
7
7
 
8
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
9
+ if self.length < self.items.cap {
10
+ // The value arrives owned (`mv T value`): a value-struct element
11
+ // moves into the fresh slot (store_take — the caller's storage
12
+ // zeroes, no deep copy); strings and scalars keep the
13
+ // deep-copying store.
14
+ if T_IS_VALUE_STRUCT {
15
+ self.items.store_take(self.length, value)
16
+ } else {
17
+ self.items.store(self.length, value)
14
18
  }
15
- self.items.grow(new_cap)
19
+ self.length += 1
20
+ return
21
+ }
22
+ self.reserve(1)
23
+ if T_IS_VALUE_STRUCT {
24
+ self.items.store_take(self.length, value)
25
+ } else {
26
+ self.items.store(self.length, value)
16
27
  }
17
- self.items.store(self.length, value)
18
28
  self.length += 1
19
29
  }
20
30
 
31
+ // Capacity for `extra` more elements without reallocation: the exact
32
+ // growth policy `push` uses (double from 4, so repeated single pushes
33
+ // amortize). Returns the resulting capacity. Splitting it out of `push`
34
+ // is what makes the compiler's in-place push lowering possible: the
35
+ // destination slot for a `push(parse(line))` argument must exist before
36
+ // the argument call runs, so the backend reserves first and constructs
37
+ // the callee's result directly into `items.data[length]` (the in-place
38
+ // #4). Sound for extra=1 by the same proof as push's old inline growth:
39
+ // length + 1 > cap grows to exactly the capacity push would have grown
40
+ // to.
41
+ internal func reserve = (ref self, int extra: extra >= 0, out int) {
42
+ if self.length + extra <= self.items.cap {
43
+ return self.items.cap
44
+ }
45
+ var int new_cap = 4
46
+ var int old_cap = self.items.cap
47
+ if old_cap >= 4 {
48
+ new_cap = old_cap * 2
49
+ }
50
+ while new_cap < self.length + extra; new_cap *= 2 { }
51
+ self.items.grow(new_cap)
52
+ return self.items.cap
53
+ }
54
+
21
55
  pub func pop = (ref self, mv out T) {
22
56
  if self.length <= 0 {
23
57
  return self.items.move(0)
@@ -73,6 +107,62 @@ pub mv struct List<T>: Viewable {
73
107
  panic("index out of range")
74
108
  }
75
109
 
110
+ // Address of the APPEND slot (element `length`) — the in-place push
111
+ // lowering's destination primitive, the in-place push contract). reserve must have
112
+ // run, so the slot exists. Offsets mirror the mono layout: length @8,
113
+ // items.data @24 (the Buffer mono is {_vt, data, cap} = 24 bytes).
114
+ internal func append_slot_ptr = (ref self, out ptr T) {
115
+ ```
116
+ #arch: c
117
+ // Byte math + void* — the body must compile for EVERY element kind
118
+ // (values, strings, class/trait handles), so it never names T.
119
+ // T_SIZE substitutes to the element's real stride.
120
+ return (void *)((unsigned long long)self->items.data +
121
+ (unsigned long long)self->length * T_SIZE);
122
+ ```
123
+ ```
124
+ #arch: aarch64
125
+ // x0 = self (stack-less raw: read from x0). Returns x0 = slot address.
126
+ ldr x9, [x0, #8]
127
+ ldr x10, [x0, #24]
128
+ mov x11, #T_SIZE
129
+ madd x0, x9, x11, x10
130
+ ```
131
+ }
132
+
133
+ // Address of element `i` (INTERNAL — the ELEMENT BORROW primitive):
134
+ // `ref T x = list.at(i)` (docs/HANDLERS.md Stage 4, dynamic-tree
135
+ // dispatch) lowers to this, binding the alias to the slot itself rather
136
+ // than to a bitwise copy. The pointer is valid while the list is
137
+ // exclusively borrowed and not resized; a `ref self` call on the list
138
+ // invalidates the borrow through the ordinary borrow-invalidation
139
+ // rules. The length constraint is the caller's proof obligation — the
140
+ // handler desugar's synthesized loops satisfy it from the loop
141
+ // condition, exactly like `.at`.
142
+ internal func slot_ptr = (ref self, int i: i >= 0 && i < self.length, out ptr T) {
143
+ return self.items.slot_ptr(i)
144
+ }
145
+
146
+ // Bounds-checked removal that traps when i is outside [0, self.length) —
147
+ // same contract as at_or_panic/set_or_panic, for indices that can't be
148
+ // proven at compile time. The element is moved out (later elements
149
+ // backward-shift into place) and returned: the caller owns it, so its
150
+ // resources are reclaimed when the binding's scope ends — binding the
151
+ // result in a nested block is the way to free an owning element at a
152
+ // chosen point (its #destroy runs at the block's closing brace).
153
+ pub func remove_at_or_panic = (ref self, int i, mv out T) {
154
+ if i < 0 || i >= self.length {
155
+ panic("index out of range")
156
+ }
157
+ var T v = self.items.move(i)
158
+ var int last = self.length - 1
159
+ if i < last {
160
+ self.items.move_range(i, i + 1, last - i)
161
+ }
162
+ self.length -= 1
163
+ return v
164
+ }
165
+
76
166
  // A deep copy: a fresh List<T> with an independent copy of every element.
77
167
  // For mv element types (strings, mv value structs) each slot in the
78
168
  // copy owns its own heap data (Buffer deep-copies on store), so the source
@@ -7,23 +7,35 @@
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).
19
28
  // Zero pairs yields an empty Map, so this also covers Map<K, V>().
20
- // The pair reads are temp-rooted (`pairs.at(i)` is a statement temp):
21
- // the element transfer is absorbed by set's mv store (store_T
22
- // deep-copies string-field elements), so no explicit mv is spelled.
29
+ // Each pair is read into a local and its fields are MV-DESTRUCTURED into
30
+ // bindings, so set's mv store receives an explicit transfer chain — no
31
+ // temp-rooted read exemption involved. (String bindings lower as the
32
+ // borrow-copy convention: the slot's owner reclaims heap bytes, and the
33
+ // callee materializes its own copy.)
23
34
  pub func #init = (self, ...[TK, TV] pairs) {
24
35
  var i = 0
25
36
  while i < pairs.length; i += 1 {
26
- self.set(pairs.at(i)._0, pairs.at(i)._1)
37
+ var [mv k, mv v] = pairs.at(i)
38
+ self.set(mv k, mv v)
27
39
  }
28
40
  }
29
41
 
@@ -36,28 +48,84 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
36
48
  cap = self.keys.alloc(8)
37
49
  self.values.alloc(8)
38
50
  self.used.alloc_u32(8)
51
+ self.hashes.alloc_u32(8)
39
52
  }
40
53
  if self.length * 4 >= cap * 3 {
41
54
  self.rehash(cap * 2)
42
55
  cap = self.keys.cap
43
56
  }
44
- 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)
45
59
  if self.used.load_u32(idx) == 0 {
46
60
  self.used.store_u32(idx, 1)
47
- self.keys.store(idx, key)
48
- self.values.store(idx, value)
61
+ // Both arrive owned (`mv TK key, mv TV value`): a value-struct
62
+ // key/value moves into its fresh slot (the caller's storage
63
+ // zeroes, no deep copy); strings keep the deep-copying store
64
+ // (their pair arrives by value).
65
+ if TK_IS_VALUE_STRUCT {
66
+ self.keys.store_take(idx, key)
67
+ } else {
68
+ self.keys.store(idx, key)
69
+ }
70
+ if TV_IS_VALUE_STRUCT {
71
+ self.values.store_take(idx, value)
72
+ } else {
73
+ self.values.store(idx, value)
74
+ }
75
+ self.hashes.store_u32(idx, h)
49
76
  self.length += 1
50
77
  } else {
51
78
  self.values.replace(idx, value)
52
79
  }
53
80
  }
54
81
 
82
+ // The IN-PLACE STORE primitive — the write-side sibling of `get_ptr`.
83
+ // Runs set's whole preamble (alloc-if-empty, rehash-at-load-factor) and
84
+ // probe with the KEY (which precedes the value in evaluation order at a
85
+ // `set(key, f(x))` site), then does everything set does EXCEPT write the
86
+ // value: the insert path marks the slot used, stores the key, and bumps
87
+ // length; the replace path reclaims the displaced value through
88
+ // `values.destroy_at` (the destroy half of replace, no copy). It returns
89
+ // the VALUE SLOT's address, so the compiler's in-place `set` lowering
90
+ // constructs `f(x)` straight into it — no `_param_N` temp home, no
91
+ // callee-side deep copy, no temp free. INTERNAL, like every slot
92
+ // primitive: the raw pointer must not cross the library boundary, and
93
+ // the caller must overwrite the slot before it is read or destroyed
94
+ // again (both paths leave it owning nothing).
95
+ internal func set_slot_ptr = (ref self, TK key, out ptr TV) {
96
+ var int cap = self.keys.cap
97
+ if cap == 0 {
98
+ // alloc_T: for Buffer<TK> it is T_SIZE-aware (string keys are
99
+ // 16-byte slots); for ClassBuffer<TV> it delegates to alloc_int
100
+ // (one pointer per slot). Both have it for generic parity.
101
+ cap = self.keys.alloc(8)
102
+ self.values.alloc(8)
103
+ self.used.alloc_u32(8)
104
+ self.hashes.alloc_u32(8)
105
+ }
106
+ if self.length * 4 >= cap * 3 {
107
+ self.rehash(cap * 2)
108
+ cap = self.keys.cap
109
+ }
110
+ var uint32 h = key.hash() as uint32
111
+ var int idx = self.find_slot(key, h, cap)
112
+ if self.used.load_u32(idx) == 0 {
113
+ self.used.store_u32(idx, 1)
114
+ self.keys.store(idx, key)
115
+ self.hashes.store_u32(idx, h)
116
+ self.length += 1
117
+ } else {
118
+ self.values.destroy_at(idx)
119
+ }
120
+ return self.values.slot_ptr(idx)
121
+ }
122
+
55
123
  pub func get = (self, TK key, out TV) {
56
124
  var int cap = self.keys.cap
57
125
  if cap == 0 {
58
126
  return 0
59
127
  }
60
- var int idx = self.find_slot(key, cap)
128
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
61
129
  if self.used.load_u32(idx) == 0 {
62
130
  return 0
63
131
  }
@@ -71,7 +139,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
71
139
  if cap == 0 {
72
140
  return fallback
73
141
  }
74
- var int idx = self.find_slot(key, cap)
142
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
75
143
  if self.used.load_u32(idx) == 0 {
76
144
  return fallback
77
145
  }
@@ -85,19 +153,45 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
85
153
  if cap == 0 {
86
154
  panic("key not found")
87
155
  }
88
- var int idx = self.find_slot(key, cap)
156
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
89
157
  if self.used.load_u32(idx) == 0 {
90
158
  panic("key not found")
91
159
  }
92
160
  return self.values.load(idx)
93
161
  }
94
162
 
163
+ // The VALUE SLOT BORROW — `Buffer.slot_ptr` for Map values. One probe
164
+ // (a `has` + `get` pair collapses into it) and the result aliases the
165
+ // slot instead of copying it, so container-bearing values stop paying
166
+ // the owning-specialized deep copy on every lookup. Traps when the key
167
+ // is absent — a missing key is a bug at a slot-borrow site (miss-as-case
168
+ // lookups use `get_or`). INTERNAL, like every slot-borrow primitive: the
169
+ // raw pointer must not cross the library boundary, so user code reaches
170
+ // it only through the element borrow, `ref TV v = map.get_or_panic(k)`,
171
+ // which the checker rewrites to this — the same lowering `ref T x =
172
+ // list.at(i)` gets from `slot_ptr`. The borrow is unpoliced inside the
173
+ // library: rehash/remove move or free slots, so do not hold it across
174
+ // one. This is also the anchor for in-place `set(key, f(x))` (probe
175
+ // first, construct into the slot).
176
+ internal func get_ptr = (self, TK key, out ptr TV) {
177
+ unsafe {
178
+ var int cap = self.keys.cap
179
+ if cap > 0 {
180
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
181
+ if self.used.load_u32(idx) != 0 {
182
+ return self.values.slot_ptr(idx)
183
+ }
184
+ }
185
+ panic("key not found")
186
+ }
187
+ }
188
+
95
189
  pub func has = (self, TK key, out bool) {
96
190
  var int cap = self.keys.cap
97
191
  if cap == 0 {
98
192
  return false
99
193
  }
100
- var int idx = self.find_slot(key, cap)
194
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
101
195
  return self.used.load_u32(idx) != 0
102
196
  }
103
197
 
@@ -106,7 +200,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
106
200
  if cap == 0 {
107
201
  return
108
202
  }
109
- var int idx = self.find_slot(key, cap)
203
+ var int idx = self.find_slot(key, key.hash() as uint32, cap)
110
204
  if self.used.load_u32(idx) == 0 {
111
205
  return
112
206
  }
@@ -119,7 +213,9 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
119
213
  // so entries past a non-moveable one are not stranded behind an empty
120
214
  // slot (which would make find_slot miss them). No tombstones, so
121
215
  // find_slot/get/has/set are unchanged, and the cost is the local cluster
122
- // length — not a full O(cap) rehash on every delete.
216
+ // length — not a full O(cap) rehash on every delete. `home` reads the
217
+ // stored hash word (no re-hash per shifted entry), and the word moves
218
+ // with the entry — find_slot's hash short-circuit trusts it.
123
219
  var int gap = idx
124
220
  var int k = idx
125
221
  while true {
@@ -130,7 +226,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
130
226
  if self.used.load_u32(k) == 0 {
131
227
  break
132
228
  }
133
- var int home = (self.keys.load(k).hash() as int) & (cap - 1)
229
+ var int home = (self.hashes.load_u32(k) as int) & (cap - 1)
134
230
  var int dgap = gap - home
135
231
  if dgap < 0 {
136
232
  dgap = dgap + cap
@@ -147,20 +243,77 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
147
243
  // used.store marks it empty (the buffer destroy skips it).
148
244
  self.keys.shift(gap, k)
149
245
  self.values.shift(gap, k)
246
+ self.hashes.store_u32(gap, self.hashes.load_u32(k))
150
247
  self.used.store_u32(gap, 1)
151
248
  gap = k
152
249
  }
153
250
  }
251
+ if TK_NEEDS_STRDUP {
252
+ if gap == idx {
253
+ // No entry shifted: the removed entry still owns its strdup'd
254
+ // bytes in its slot (a shift's first move would have
255
+ // reclaimed them — see shift_T above). destroy_at frees the
256
+ // buffer and zeroes the slot: a later set() that lands in
257
+ // this hole stores over the bytes assuming a fresh slot, and
258
+ // a stranded-then-overwritten entry leaks.
259
+ self.keys.destroy_at(idx)
260
+ }
261
+ }
262
+ if TK_IS_VALUE_STRUCT {
263
+ if gap == idx {
264
+ // Same hole for a value-struct key: reclaim the slot's owned
265
+ // fields (destroy_at frees and zeroes; scalars-only structs
266
+ // reclaim nothing and the call is a no-op).
267
+ self.keys.destroy_at(idx)
268
+ }
269
+ }
270
+ if TV_NEEDS_STRDUP {
271
+ if gap == idx {
272
+ self.values.destroy_at(idx)
273
+ }
274
+ }
275
+ if TV_IS_VALUE_STRUCT {
276
+ if gap == idx {
277
+ self.values.destroy_at(idx)
278
+ }
279
+ }
154
280
  self.used.store_u32(gap, 0)
155
281
  self.length = self.length - 1
156
282
  }
157
283
 
158
- func find_slot = (self, TK key, int cap, out int: out >= 0 && out < cap) {
284
+ // An independent copy: a fresh Map whose every member slab is fresh
285
+ // (keys/values deep-copied per element, `used`/`hashes` copied word by
286
+ // word), with the same length and entries. The u32-word buffers are
287
+ // copied through their own primitives — `Buffer.copy` is T_SIZE-aware
288
+ // (8-byte loads for `int`), which would read past a 4-byte-per-slot
289
+ // `alloc_u32` slab. Not available for class/trait keys or values (the
290
+ // member routes to ClassBuffer, which has no copy — the checker rejects
291
+ // the call; see the copy gate in check_access_node).
292
+ pub func copy = (self, out Map<TK, TV>) {
293
+ var dst = Map<TK, TV>()
294
+ dst.keys = self.keys.copy()
295
+ dst.values = self.values.copy()
296
+ dst.used.alloc_u32(self.keys.cap)
297
+ dst.hashes.alloc_u32(self.keys.cap)
298
+ var i = 0
299
+ while i < self.keys.cap; i += 1 {
300
+ dst.used.store_u32(i, self.used.load_u32(i))
301
+ dst.hashes.store_u32(i, self.hashes.load_u32(i))
302
+ }
303
+ dst.length = self.length
304
+ return dst
305
+ }
306
+
307
+ func find_slot = (self, TK key, uint32 h, int cap, out int: out >= 0 && out < cap) {
159
308
  // cap is a power of two, so this is `hash mod cap` via a mask —
160
309
  // non-negative for any hash (two's complement), no adjustment.
161
- var int idx = (key.hash() as int) & (cap - 1)
310
+ var int idx = (h as int) & (cap - 1)
162
311
  while self.used.load_u32(idx) != 0 {
163
- if self.keys.load(idx) == key {
312
+ // The stored-hash word short-circuits the key compare: a probed
313
+ // slot whose hash differs (every step of a collision walk past
314
+ // the home slot) skips the key load and `==`. A hash match still
315
+ // falls through to `==`, so the 32-bit truncation costs nothing.
316
+ if self.hashes.load_u32(idx) == h && self.keys.load(idx) == key {
164
317
  return idx
165
318
  }
166
319
  idx += 1
@@ -171,15 +324,23 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
171
324
  return idx
172
325
  }
173
326
 
327
+ // Rehash hash-free: the stored per-slot hash words move with the entries,
328
+ // so no `key.hash()` runs here and the insertion probe needs no key
329
+ // compares (keys arrive unique — they came from a valid table). This is
330
+ // the other half of the Swiss-table trick; the old form re-inserted via
331
+ // `self.set(mv k, mv v)`, re-hashing every key and re-comparing keys on
332
+ // every probed slot.
174
333
  func rehash = (ref self, int new_cap) {
175
334
  var Buffer<TK> old_keys = mv self.keys swap Buffer<TK>()
176
335
  var Buffer<TV> old_values = mv self.values swap Buffer<TV>()
177
336
  var Buffer<int> old_used = mv self.used swap Buffer<int>()
337
+ var Buffer<int> old_hashes = mv self.hashes swap Buffer<int>()
178
338
  var int old_cap = old_keys.cap
179
339
 
180
340
  self.keys.alloc(new_cap)
181
341
  self.values.alloc(new_cap)
182
342
  self.used.alloc_u32(new_cap)
343
+ self.hashes.alloc_u32(new_cap)
183
344
  self.length = 0
184
345
 
185
346
  var i = 0
@@ -196,7 +357,19 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
196
357
  // move_T results.
197
358
  var TK k = old_keys.move(i)
198
359
  var TV v = old_values.move(i)
199
- self.set(mv k, mv v)
360
+ var uint32 h = old_hashes.load_u32(i)
361
+ var int idx = (h as int) & (new_cap - 1)
362
+ while self.used.load_u32(idx) != 0 {
363
+ idx += 1
364
+ if idx >= new_cap {
365
+ idx = 0
366
+ }
367
+ }
368
+ self.used.store_u32(idx, 1)
369
+ self.keys.store(idx, k)
370
+ self.values.store(idx, v)
371
+ self.hashes.store_u32(idx, h)
372
+ self.length += 1
200
373
  }
201
374
  }
202
375
  }
@@ -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,10 +41,9 @@ 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.
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.
48
47
  pub func next = (ref self, out uint64) {
49
48
  self.state = self.state + 0x9E3779B97F4A7C15
50
49
  var uint64 z = self.state