nomen-lang 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/audit_runtime.c +12 -2
- package/core/System/Arena.nm +8 -1
- package/core/System/Buffer.nm +366 -9
- package/core/System/ClassBuffer.nm +133 -1
- package/core/System/Controls/Alert.nm +2 -2
- package/core/System/Controls/App.nm +12 -12
- package/core/System/Controls/Button.nm +1 -1
- package/core/System/Controls/CheckBox.nm +2 -2
- package/core/System/Controls/ColorPicker.nm +1 -1
- package/core/System/Controls/ComboBox.nm +1 -1
- package/core/System/Controls/Container.nm +7 -1
- package/core/System/Controls/GroupBox.nm +1 -1
- package/core/System/Controls/ImageView.nm +1 -1
- package/core/System/Controls/LevelIndicator.nm +1 -1
- package/core/System/Controls/ListView.nm +1 -1
- package/core/System/Controls/Menu.nm +1 -1
- package/core/System/Controls/OutlineView.nm +1 -1
- package/core/System/Controls/Pagination.nm +8 -8
- package/core/System/Controls/Popover.nm +1 -1
- package/core/System/Controls/Progress.nm +1 -1
- package/core/System/Controls/Radio.nm +2 -2
- package/core/System/Controls/SearchField.nm +1 -1
- package/core/System/Controls/SecureField.nm +1 -1
- package/core/System/Controls/Segmented.nm +1 -1
- package/core/System/Controls/Select.nm +1 -1
- package/core/System/Controls/Separator.nm +1 -1
- package/core/System/Controls/Sheet.nm +1 -1
- package/core/System/Controls/Slider.nm +1 -1
- package/core/System/Controls/Spinner.nm +2 -2
- package/core/System/Controls/SplitView.nm +1 -1
- package/core/System/Controls/Stepper.nm +1 -1
- package/core/System/Controls/Switch.nm +1 -1
- package/core/System/Controls/Table.nm +1 -1
- package/core/System/Controls/Tabs.nm +1 -1
- package/core/System/Controls/Text.nm +1 -1
- package/core/System/Controls/TextBox.nm +2 -2
- package/core/System/Controls/TextView.nm +1 -1
- package/core/System/Controls/Toggle.nm +2 -2
- package/core/System/Controls/Toolbar.nm +1 -1
- package/core/System/Controls/Window.nm +1 -2
- package/core/System/LinkedList.nm +8 -1
- package/core/System/List.nm +68 -12
- package/core/System/Map.nm +174 -18
- package/core/System/Mutex.nm +4 -4
- package/core/System/Random.nm +3 -4
- package/core/System/Set.nm +94 -20
- package/core/System/StableList.nm +371 -0
- package/core/System/Stream/Directory.nm +2 -2
- package/core/System/Stream/File.nm +2 -2
- package/core/System/Stream/Http.nm +2 -2
- package/core/System/String.nm +10 -5
- package/core/System/StringBuilder.nm +213 -2
- package/core/System/Text/CharIndex.nm +9 -9
- package/core/System/Text/Json.nm +60 -46
- package/core/System/Text/JsonTree.nm +120 -92
- package/core/System/Text/Regex.nm +23 -18
- package/core/System/Text/Utf8.nm +5 -5
- package/core/System/float.nm +36 -22
- package/core/System/int.nm +80 -28
- package/core/System/int16.nm +11 -0
- package/core/System/int32.nm +11 -0
- package/core/System/int64.nm +11 -0
- package/core/System/int8.nm +11 -0
- package/core/System/uint.nm +11 -0
- package/core/System/uint16.nm +11 -0
- package/core/System/uint32.nm +11 -0
- package/core/System/uint64.nm +11 -0
- package/core/System/uint8.nm +11 -0
- package/core/docs/System/StringBuilder.md +6 -0
- package/core/docs/System/Text/JsonTree.md +3 -2
- package/dist/index.mjs +36575 -30747
- package/package.json +1 -1
- package/src/bench_loop.nm +9 -9
|
@@ -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
|
-
|
|
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)
|
package/core/System/List.nm
CHANGED
|
@@ -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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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,7 +107,30 @@ pub mv struct List<T>: Viewable {
|
|
|
73
107
|
panic("index out of range")
|
|
74
108
|
}
|
|
75
109
|
|
|
76
|
-
// Address of element `
|
|
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):
|
|
77
134
|
// `ref T x = list.at(i)` (docs/HANDLERS.md Stage 4, dynamic-tree
|
|
78
135
|
// dispatch) lowers to this, binding the alias to the slot itself rather
|
|
79
136
|
// than to a bitwise copy. The pointer is valid while the list is
|
|
@@ -82,7 +139,7 @@ pub mv struct List<T>: Viewable {
|
|
|
82
139
|
// rules. The length constraint is the caller's proof obligation — the
|
|
83
140
|
// handler desugar's synthesized loops satisfy it from the loop
|
|
84
141
|
// condition, exactly like `.at`.
|
|
85
|
-
|
|
142
|
+
internal func slot_ptr = (ref self, int i: i >= 0 && i < self.length, out ptr T) {
|
|
86
143
|
return self.items.slot_ptr(i)
|
|
87
144
|
}
|
|
88
145
|
|
|
@@ -99,9 +156,8 @@ pub mv struct List<T>: Viewable {
|
|
|
99
156
|
}
|
|
100
157
|
var T v = self.items.move(i)
|
|
101
158
|
var int last = self.length - 1
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self.items.shift(j, j + 1)
|
|
159
|
+
if i < last {
|
|
160
|
+
self.items.move_range(i, i + 1, last - i)
|
|
105
161
|
}
|
|
106
162
|
self.length -= 1
|
|
107
163
|
return v
|
package/core/System/Map.nm
CHANGED
|
@@ -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).
|
|
@@ -39,28 +48,84 @@ 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
|
|
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
|
-
|
|
51
|
-
|
|
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)
|
|
52
76
|
self.length += 1
|
|
53
77
|
} else {
|
|
54
78
|
self.values.replace(idx, value)
|
|
55
79
|
}
|
|
56
80
|
}
|
|
57
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
|
+
|
|
58
123
|
pub func get = (self, TK key, out TV) {
|
|
59
124
|
var int cap = self.keys.cap
|
|
60
125
|
if cap == 0 {
|
|
61
126
|
return 0
|
|
62
127
|
}
|
|
63
|
-
var int idx = self.find_slot(key, cap)
|
|
128
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
64
129
|
if self.used.load_u32(idx) == 0 {
|
|
65
130
|
return 0
|
|
66
131
|
}
|
|
@@ -74,7 +139,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
74
139
|
if cap == 0 {
|
|
75
140
|
return fallback
|
|
76
141
|
}
|
|
77
|
-
var int idx = self.find_slot(key, cap)
|
|
142
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
78
143
|
if self.used.load_u32(idx) == 0 {
|
|
79
144
|
return fallback
|
|
80
145
|
}
|
|
@@ -88,19 +153,45 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
88
153
|
if cap == 0 {
|
|
89
154
|
panic("key not found")
|
|
90
155
|
}
|
|
91
|
-
var int idx = self.find_slot(key, cap)
|
|
156
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
92
157
|
if self.used.load_u32(idx) == 0 {
|
|
93
158
|
panic("key not found")
|
|
94
159
|
}
|
|
95
160
|
return self.values.load(idx)
|
|
96
161
|
}
|
|
97
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
|
+
|
|
98
189
|
pub func has = (self, TK key, out bool) {
|
|
99
190
|
var int cap = self.keys.cap
|
|
100
191
|
if cap == 0 {
|
|
101
192
|
return false
|
|
102
193
|
}
|
|
103
|
-
var int idx = self.find_slot(key, cap)
|
|
194
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
104
195
|
return self.used.load_u32(idx) != 0
|
|
105
196
|
}
|
|
106
197
|
|
|
@@ -109,7 +200,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
109
200
|
if cap == 0 {
|
|
110
201
|
return
|
|
111
202
|
}
|
|
112
|
-
var int idx = self.find_slot(key, cap)
|
|
203
|
+
var int idx = self.find_slot(key, key.hash() as uint32, cap)
|
|
113
204
|
if self.used.load_u32(idx) == 0 {
|
|
114
205
|
return
|
|
115
206
|
}
|
|
@@ -122,7 +213,9 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
122
213
|
// so entries past a non-moveable one are not stranded behind an empty
|
|
123
214
|
// slot (which would make find_slot miss them). No tombstones, so
|
|
124
215
|
// 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.
|
|
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.
|
|
126
219
|
var int gap = idx
|
|
127
220
|
var int k = idx
|
|
128
221
|
while true {
|
|
@@ -133,7 +226,7 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
133
226
|
if self.used.load_u32(k) == 0 {
|
|
134
227
|
break
|
|
135
228
|
}
|
|
136
|
-
var int home = (self.
|
|
229
|
+
var int home = (self.hashes.load_u32(k) as int) & (cap - 1)
|
|
137
230
|
var int dgap = gap - home
|
|
138
231
|
if dgap < 0 {
|
|
139
232
|
dgap = dgap + cap
|
|
@@ -150,34 +243,77 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
150
243
|
// used.store marks it empty (the buffer destroy skips it).
|
|
151
244
|
self.keys.shift(gap, k)
|
|
152
245
|
self.values.shift(gap, k)
|
|
246
|
+
self.hashes.store_u32(gap, self.hashes.load_u32(k))
|
|
153
247
|
self.used.store_u32(gap, 1)
|
|
154
248
|
gap = k
|
|
155
249
|
}
|
|
156
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
|
+
}
|
|
157
280
|
self.used.store_u32(gap, 0)
|
|
158
281
|
self.length = self.length - 1
|
|
159
282
|
}
|
|
160
283
|
|
|
161
|
-
// An independent copy: a fresh Map whose every member slab is
|
|
162
|
-
//
|
|
163
|
-
// length and entries.
|
|
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
|
|
164
290
|
// member routes to ClassBuffer, which has no copy — the checker rejects
|
|
165
291
|
// the call; see the copy gate in check_access_node).
|
|
166
292
|
pub func copy = (self, out Map<TK, TV>) {
|
|
167
293
|
var dst = Map<TK, TV>()
|
|
168
294
|
dst.keys = self.keys.copy()
|
|
169
295
|
dst.values = self.values.copy()
|
|
170
|
-
dst.used
|
|
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
|
+
}
|
|
171
303
|
dst.length = self.length
|
|
172
304
|
return dst
|
|
173
305
|
}
|
|
174
306
|
|
|
175
|
-
func find_slot = (self, TK key, int cap, out int: out >= 0 && out < cap) {
|
|
307
|
+
func find_slot = (self, TK key, uint32 h, int cap, out int: out >= 0 && out < cap) {
|
|
176
308
|
// cap is a power of two, so this is `hash mod cap` via a mask —
|
|
177
309
|
// non-negative for any hash (two's complement), no adjustment.
|
|
178
|
-
var int idx = (
|
|
310
|
+
var int idx = (h as int) & (cap - 1)
|
|
179
311
|
while self.used.load_u32(idx) != 0 {
|
|
180
|
-
|
|
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 {
|
|
181
317
|
return idx
|
|
182
318
|
}
|
|
183
319
|
idx += 1
|
|
@@ -188,15 +324,23 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
188
324
|
return idx
|
|
189
325
|
}
|
|
190
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.
|
|
191
333
|
func rehash = (ref self, int new_cap) {
|
|
192
334
|
var Buffer<TK> old_keys = mv self.keys swap Buffer<TK>()
|
|
193
335
|
var Buffer<TV> old_values = mv self.values swap Buffer<TV>()
|
|
194
336
|
var Buffer<int> old_used = mv self.used swap Buffer<int>()
|
|
337
|
+
var Buffer<int> old_hashes = mv self.hashes swap Buffer<int>()
|
|
195
338
|
var int old_cap = old_keys.cap
|
|
196
339
|
|
|
197
340
|
self.keys.alloc(new_cap)
|
|
198
341
|
self.values.alloc(new_cap)
|
|
199
342
|
self.used.alloc_u32(new_cap)
|
|
343
|
+
self.hashes.alloc_u32(new_cap)
|
|
200
344
|
self.length = 0
|
|
201
345
|
|
|
202
346
|
var i = 0
|
|
@@ -213,7 +357,19 @@ pub mv struct Map<TK: Hashable + Equatable, TV> {
|
|
|
213
357
|
// move_T results.
|
|
214
358
|
var TK k = old_keys.move(i)
|
|
215
359
|
var TV v = old_values.move(i)
|
|
216
|
-
|
|
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
|
|
217
373
|
}
|
|
218
374
|
}
|
|
219
375
|
}
|
package/core/System/Mutex.nm
CHANGED
|
@@ -25,10 +25,10 @@ pub mv struct Mutex {
|
|
|
25
25
|
pub var uint64 handle = 0
|
|
26
26
|
|
|
27
27
|
pub func #init = (self) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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();
|
package/core/System/Random.nm
CHANGED
|
@@ -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
|
|
45
|
-
//
|
|
46
|
-
//
|
|
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
|