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.
- package/audit_runtime.c +12 -2
- package/core/System/Arena.nm +14 -5
- package/core/System/Array.nm +1 -1
- package/core/System/Atomic.nm +3 -3
- package/core/System/BigInt.nm +182 -23
- package/core/System/Buffer.nm +392 -33
- package/core/System/ClassBuffer.nm +145 -13
- package/core/System/Controls/Alert.nm +3 -3
- package/core/System/Controls/App.nm +17 -17
- package/core/System/Controls/Button.nm +2 -2
- package/core/System/Controls/CheckBox.nm +3 -3
- package/core/System/Controls/ColorPicker.nm +2 -2
- package/core/System/Controls/ComboBox.nm +2 -2
- package/core/System/Controls/Container.nm +39 -33
- package/core/System/Controls/DatePicker.nm +1 -1
- package/core/System/Controls/Form.nm +2 -2
- package/core/System/Controls/GroupBox.nm +2 -2
- package/core/System/Controls/ImageView.nm +2 -2
- package/core/System/Controls/Layout.nm +5 -5
- package/core/System/Controls/LevelIndicator.nm +2 -2
- package/core/System/Controls/ListView.nm +2 -2
- package/core/System/Controls/Menu.nm +2 -2
- package/core/System/Controls/NumberInput.nm +3 -3
- package/core/System/Controls/OtpInput.nm +2 -2
- package/core/System/Controls/OutlineView.nm +2 -2
- package/core/System/Controls/Pagination.nm +14 -14
- package/core/System/Controls/Popover.nm +2 -2
- package/core/System/Controls/Progress.nm +2 -2
- package/core/System/Controls/Radio.nm +3 -3
- package/core/System/Controls/Scrollable.nm +1 -1
- package/core/System/Controls/SearchField.nm +2 -2
- package/core/System/Controls/SecureField.nm +2 -2
- package/core/System/Controls/Segmented.nm +2 -2
- package/core/System/Controls/Select.nm +2 -2
- package/core/System/Controls/Separator.nm +2 -2
- package/core/System/Controls/Sheet.nm +2 -2
- package/core/System/Controls/Slider.nm +2 -2
- package/core/System/Controls/Spinner.nm +5 -5
- package/core/System/Controls/SplitView.nm +3 -3
- package/core/System/Controls/Stepper.nm +2 -2
- package/core/System/Controls/Switch.nm +2 -2
- package/core/System/Controls/Table.nm +2 -2
- package/core/System/Controls/Tabs.nm +2 -2
- package/core/System/Controls/Text.nm +2 -2
- package/core/System/Controls/TextBox.nm +3 -3
- package/core/System/Controls/TextView.nm +2 -2
- package/core/System/Controls/Toggle.nm +3 -3
- package/core/System/Controls/Toolbar.nm +2 -2
- package/core/System/Controls/Window.nm +12 -5
- package/core/System/Fiber.nm +16 -3
- package/core/System/Graph.nm +2 -2
- package/core/System/LinkedList.nm +13 -4
- package/core/System/List.nm +76 -17
- package/core/System/Map.nm +179 -21
- package/core/System/Mutex.nm +4 -4
- package/core/System/Random.nm +6 -7
- package/core/System/Set.nm +99 -23
- package/core/System/StableList.nm +371 -0
- package/core/System/Stream/Directory.nm +2 -2
- package/core/System/Stream/File.nm +18 -18
- package/core/System/Stream/Http.nm +5 -5
- package/core/System/Stream/Tcp.nm +5 -5
- package/core/System/String.nm +10 -5
- package/core/System/StringBuilder.nm +223 -12
- package/core/System/Task.nm +56 -64
- package/core/System/Test.nm +18 -18
- package/core/System/Text/CharIndex.nm +9 -9
- package/core/System/Text/Chars.nm +1 -1
- package/core/System/Text/Json.nm +72 -58
- package/core/System/Text/JsonTree.nm +126 -98
- package/core/System/Text/Regex.nm +43 -38
- package/core/System/Text/Utf8.nm +5 -5
- package/core/System/Thread.nm +26 -3
- package/core/System/Tree.nm +19 -3
- 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 +37622 -30603
- package/package.json +1 -1
- package/src/bench_loop.nm +9 -9
package/core/System/List.nm
CHANGED
|
@@ -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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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
|
-
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
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).
|
|
@@ -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
|
|
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 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.
|
|
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
|
|
162
|
-
//
|
|
163
|
-
// length and entries.
|
|
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
|
|
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 = (
|
|
312
|
+
var int idx = (h as int) & (cap - 1)
|
|
179
313
|
while self.used.load_u32(idx) != 0 {
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
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,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
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
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
|
}
|