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.
- package/audit_runtime.c +12 -2
- package/core/System/Arena.nm +8 -1
- package/core/System/Buffer.nm +397 -8
- package/core/System/ClassBuffer.nm +156 -0
- package/core/System/Controls/Alert.nm +326 -0
- package/core/System/Controls/App.nm +147 -0
- package/core/System/Controls/Button.nm +119 -1
- package/core/System/Controls/CheckBox.nm +25 -1
- package/core/System/Controls/ColorPicker.nm +307 -0
- package/core/System/Controls/ComboBox.nm +430 -0
- package/core/System/Controls/Container.nm +843 -9
- package/core/System/Controls/Control.nm +443 -0
- package/core/System/Controls/DatePicker.nm +288 -0
- package/core/System/Controls/Form.nm +70 -0
- package/core/System/Controls/GroupBox.nm +267 -0
- package/core/System/Controls/ImageView.nm +311 -0
- package/core/System/Controls/LevelIndicator.nm +305 -0
- package/core/System/Controls/ListView.nm +633 -0
- package/core/System/Controls/Menu.nm +428 -0
- package/core/System/Controls/NumberInput.nm +56 -0
- package/core/System/Controls/OtpInput.nm +50 -0
- package/core/System/Controls/OutlineView.nm +598 -0
- package/core/System/Controls/Pagination.nm +142 -0
- package/core/System/Controls/Popover.nm +296 -0
- package/core/System/Controls/Progress.nm +348 -0
- package/core/System/Controls/Radio.nm +292 -0
- package/core/System/Controls/Scrollable.nm +584 -0
- package/core/System/Controls/SearchField.nm +326 -0
- package/core/System/Controls/SecureField.nm +344 -0
- package/core/System/Controls/Segmented.nm +380 -0
- package/core/System/Controls/Select.nm +340 -0
- package/core/System/Controls/Separator.nm +198 -0
- package/core/System/Controls/Sheet.nm +329 -0
- package/core/System/Controls/Slider.nm +315 -0
- package/core/System/Controls/Spinner.nm +287 -0
- package/core/System/Controls/SplitView.nm +509 -0
- package/core/System/Controls/Stepper.nm +349 -0
- package/core/System/Controls/Switch.nm +259 -0
- package/core/System/Controls/Table.nm +833 -0
- package/core/System/Controls/Tabs.nm +500 -0
- package/core/System/Controls/Text.nm +108 -11
- package/core/System/Controls/TextBox.nm +113 -2
- package/core/System/Controls/TextView.nm +329 -0
- package/core/System/Controls/Toggle.nm +346 -0
- package/core/System/Controls/Toolbar.nm +501 -0
- package/core/System/Controls/Tooltip.nm +76 -0
- package/core/System/Controls/Window.nm +1548 -91
- package/core/System/LinkedList.nm +8 -1
- package/core/System/List.nm +97 -7
- package/core/System/Map.nm +191 -18
- package/core/System/Mutex.nm +4 -4
- package/core/System/Random.nm +3 -4
- package/core/System/Set.nm +101 -13
- 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 +38332 -30604
- 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,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
|
package/core/System/Map.nm
CHANGED
|
@@ -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
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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.
|
|
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
|
-
|
|
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 = (
|
|
310
|
+
var int idx = (h as int) & (cap - 1)
|
|
162
311
|
while self.used.load_u32(idx) != 0 {
|
|
163
|
-
|
|
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
|
-
|
|
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
|
}
|
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
|