@amritk/nish-x86_64-linux 0.12.0 → 0.13.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/INSTALL.md +13 -13
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +20 -1
- package/runtime/nish.h +31 -11
- package/runtime/nish.mjs +3 -3
- package/runtime/{runtime_os.c → runtime-os.c} +1 -1
- package/runtime/{runtime_parallel.c → runtime-parallel.c} +112 -4
- package/runtime/{runtime_wasm.c → runtime-wasm.c} +6 -1
- package/runtime/runtime.c +5 -5
- package/runtime/shim.mjs +6 -6
- package/scripts/build.sh +14 -9
- package/std/README.md +2 -2
- package/std/collections.ts +194 -188
- package/std/json.ts +136 -136
- package/std/map.ts +9 -7
- package/std/pair.ts +2 -2
- package/std/testing.ts +67 -67
- package/std/text.ts +54 -54
- package/std/threads.ts +126 -38
package/std/collections.ts
CHANGED
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
* what the 24-bit index field holds as an index plus one. Node's own `Map` and
|
|
48
48
|
* `Set` hold one more, 2^24, and throw on the next insert.
|
|
49
49
|
*/
|
|
50
|
-
const INDEX_CAP: i32 = 16777215
|
|
50
|
+
const INDEX_CAP: i32 = 16777215
|
|
51
51
|
|
|
52
52
|
/** The first bucket count. A power of two, as every bucket count is. */
|
|
53
|
-
const INITIAL_SLOTS: i32 = 8
|
|
53
|
+
const INITIAL_SLOTS: i32 = 8
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* The key's hash, never 0: FNV-1a over a string's bytes, murmur3's `fmix32`
|
|
@@ -60,11 +60,11 @@ const INITIAL_SLOTS: i32 = 8;
|
|
|
60
60
|
* constants are public domain.
|
|
61
61
|
*
|
|
62
62
|
* The body is never emitted. Every call is lowered in place, per key type, by
|
|
63
|
-
* `
|
|
63
|
+
* `src/emit-map.ts`; the constant is what the checker and the whole-program
|
|
64
64
|
* facts see, and the facts of the function that calls it are what decide its
|
|
65
65
|
* attributes, because every call is inside a probe that reads the table.
|
|
66
66
|
*/
|
|
67
|
-
const hashKey = <K>(
|
|
67
|
+
const hashKey = <K>(_key: K): u32 => 1
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* JavaScript's key equality, SameValueZero: `===`, except that NaN equals NaN.
|
|
@@ -72,19 +72,19 @@ const hashKey = <K>(key: K): u32 => 1;
|
|
|
72
72
|
* `icmp` for an integer, a boolean, an enum and a class instance, and for a
|
|
73
73
|
* float `a == b` or both unordered.
|
|
74
74
|
*/
|
|
75
|
-
const sameKey = <K>(a: K, b: K): boolean => a === b
|
|
75
|
+
const sameKey = <K>(a: K, b: K): boolean => a === b
|
|
76
76
|
|
|
77
77
|
/** The first bucket for `h`: its low bits, folded with the high half. */
|
|
78
|
-
const homeBucket = (h: u32, mask: i32): i32 => toI32(h ^ (h >>> 16)) & mask
|
|
78
|
+
const homeBucket = (h: u32, mask: i32): i32 => toI32(h ^ (h >>> 16)) & mask
|
|
79
79
|
|
|
80
80
|
/** The bucket word for entry `index` of hash `h`. */
|
|
81
|
-
const slotWord = (h: u32, index: i32): u32 => ((h >>> 24) << 24) | toU32(index + 1)
|
|
81
|
+
const slotWord = (h: u32, index: i32): u32 => ((h >>> 24) << 24) | toU32(index + 1)
|
|
82
82
|
|
|
83
83
|
/** A probe that found entry `index`, pointed at by `bucket`. Never negative. */
|
|
84
|
-
const foundAt = (bucket: i32, index: i32): i64 => (toI64(bucket) << 32) | toI64(index)
|
|
84
|
+
const foundAt = (bucket: i32, index: i32): i64 => (toI64(bucket) << 32) | toI64(index)
|
|
85
85
|
|
|
86
86
|
/** A probe that stopped at the empty `bucket` for hash `h`. Always negative. */
|
|
87
|
-
const absentAt = (bucket: i32, h: u32): i64 => toI64(-1) - ((toI64(bucket) << 32) | toI64(h))
|
|
87
|
+
const absentAt = (bucket: i32, h: u32): i64 => toI64(-1) - ((toI64(bucket) << 32) | toI64(h))
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* The one probe, for both classes: linear probing from `h`'s home bucket to
|
|
@@ -94,26 +94,32 @@ const absentAt = (bucket: i32, h: u32): i64 => toI64(-1) - ((toI64(bucket) << 32
|
|
|
94
94
|
* empty, so the walk ends.
|
|
95
95
|
*/
|
|
96
96
|
const probeTable = <K>(slots: u32[], mask: i32, hashes: u32[], keys: K[], key: K): i64 => {
|
|
97
|
-
const h = hashKey(key)
|
|
98
|
-
const fingerprint = h >>> 24
|
|
99
|
-
let bucket = homeBucket(h, mask)
|
|
97
|
+
const h = hashKey(key)
|
|
98
|
+
const fingerprint = h >>> 24
|
|
99
|
+
let bucket = homeBucket(h, mask)
|
|
100
100
|
// The length is read in the condition rather than once: the key compare is
|
|
101
101
|
// a call, and a call ends every length fact the bounds proof holds.
|
|
102
102
|
while (bucket >= 0 && bucket < toI32(slots.length)) {
|
|
103
|
-
const word = slots[bucket]
|
|
103
|
+
const word = slots[bucket]
|
|
104
104
|
if (word === 0) {
|
|
105
|
-
return absentAt(bucket, h)
|
|
105
|
+
return absentAt(bucket, h)
|
|
106
106
|
}
|
|
107
107
|
if (word >>> 24 === fingerprint) {
|
|
108
|
-
const at = toI32(word & 16777215) - 1
|
|
109
|
-
if (
|
|
110
|
-
|
|
108
|
+
const at = toI32(word & 16777215) - 1
|
|
109
|
+
if (
|
|
110
|
+
at >= 0 &&
|
|
111
|
+
at < toI32(hashes.length) &&
|
|
112
|
+
hashes[at] === h &&
|
|
113
|
+
at < toI32(keys.length) &&
|
|
114
|
+
sameKey(keys[at], key)
|
|
115
|
+
) {
|
|
116
|
+
return foundAt(bucket, at)
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
|
-
bucket = (bucket + 1) & mask
|
|
119
|
+
bucket = (bucket + 1) & mask
|
|
114
120
|
}
|
|
115
|
-
panic("collections: a probe ran out of buckets")
|
|
116
|
-
}
|
|
121
|
+
panic("collections: a probe ran out of buckets")
|
|
122
|
+
}
|
|
117
123
|
|
|
118
124
|
/**
|
|
119
125
|
* Point the first empty bucket from `h`'s home at entry `index`. A rebuild's
|
|
@@ -121,47 +127,47 @@ const probeTable = <K>(slots: u32[], mask: i32, hashes: u32[], keys: K[], key: K
|
|
|
121
127
|
* compared, and the stored hash is all it needs.
|
|
122
128
|
*/
|
|
123
129
|
const fileEntry = (slots: u32[], mask: i32, h: u32, index: i32): void => {
|
|
124
|
-
const word = slotWord(h, index)
|
|
125
|
-
let bucket = homeBucket(h, mask)
|
|
130
|
+
const word = slotWord(h, index)
|
|
131
|
+
let bucket = homeBucket(h, mask)
|
|
126
132
|
while (bucket >= 0 && bucket < toI32(slots.length)) {
|
|
127
133
|
if (slots[bucket] === 0) {
|
|
128
|
-
slots[bucket] = word
|
|
129
|
-
return
|
|
134
|
+
slots[bucket] = word
|
|
135
|
+
return
|
|
130
136
|
}
|
|
131
|
-
bucket = (bucket + 1) & mask
|
|
137
|
+
bucket = (bucket + 1) & mask
|
|
132
138
|
}
|
|
133
|
-
}
|
|
139
|
+
}
|
|
134
140
|
|
|
135
141
|
/** Slide the live entries of `items` down over the dead ones, in order, and drop the tail. */
|
|
136
142
|
const compactEntries = <T>(items: T[], hashes: u32[]): void => {
|
|
137
|
-
const used = toI32(items.length)
|
|
138
|
-
let to: i32 = 0
|
|
143
|
+
const used = toI32(items.length)
|
|
144
|
+
let to: i32 = 0
|
|
139
145
|
for (let from: i32 = 0; from < used && from < toI32(hashes.length); from++) {
|
|
140
146
|
if (hashes[from] !== 0 && to >= 0 && to < used && from < toI32(items.length)) {
|
|
141
|
-
items[to] = items[from]
|
|
142
|
-
to
|
|
147
|
+
items[to] = items[from]
|
|
148
|
+
to++
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
151
|
while (toI32(items.length) > to) {
|
|
146
|
-
items.pop()
|
|
152
|
+
items.pop()
|
|
147
153
|
}
|
|
148
|
-
}
|
|
154
|
+
}
|
|
149
155
|
|
|
150
156
|
/** The stored hashes compacted the same way, last, since the two above read them. */
|
|
151
157
|
const compactHashes = (hashes: u32[]): void => {
|
|
152
|
-
const used = toI32(hashes.length)
|
|
153
|
-
let to: i32 = 0
|
|
158
|
+
const used = toI32(hashes.length)
|
|
159
|
+
let to: i32 = 0
|
|
154
160
|
for (let from: i32 = 0; from < used; from++) {
|
|
155
|
-
const h = hashes[from]
|
|
161
|
+
const h = hashes[from]
|
|
156
162
|
if (h !== 0 && to >= 0 && to < used) {
|
|
157
|
-
hashes[to] = h
|
|
158
|
-
to
|
|
163
|
+
hashes[to] = h
|
|
164
|
+
to++
|
|
159
165
|
}
|
|
160
166
|
}
|
|
161
167
|
while (toI32(hashes.length) > to) {
|
|
162
|
-
hashes.pop()
|
|
168
|
+
hashes.pop()
|
|
163
169
|
}
|
|
164
|
-
}
|
|
170
|
+
}
|
|
165
171
|
|
|
166
172
|
/**
|
|
167
173
|
* The bucket table after a rebuild of `live` entries out of `used`. More than
|
|
@@ -171,13 +177,13 @@ const compactHashes = (hashes: u32[]): void => {
|
|
|
171
177
|
* the table never shrinks.
|
|
172
178
|
*/
|
|
173
179
|
const rebuiltSlots = (slots: u32[], live: i32, used: i32): u32[] => {
|
|
174
|
-
const n = toI32(slots.length)
|
|
180
|
+
const n = toI32(slots.length)
|
|
175
181
|
if (live * 2 < used) {
|
|
176
|
-
clearSlots(slots)
|
|
177
|
-
return slots
|
|
182
|
+
clearSlots(slots)
|
|
183
|
+
return slots
|
|
178
184
|
}
|
|
179
|
-
return new Array<u32>(n * 2)
|
|
180
|
-
}
|
|
185
|
+
return new Array<u32>(n * 2)
|
|
186
|
+
}
|
|
181
187
|
|
|
182
188
|
/**
|
|
183
189
|
* Re-file every live entry from its stored hash. After a compaction none is
|
|
@@ -185,14 +191,14 @@ const rebuiltSlots = (slots: u32[], live: i32, used: i32): u32[] => {
|
|
|
185
191
|
* keeps its place and takes no bucket, since no probe can find it.
|
|
186
192
|
*/
|
|
187
193
|
const refile = (slots: u32[], hashes: u32[]): void => {
|
|
188
|
-
const mask = toI32(slots.length) - 1
|
|
194
|
+
const mask = toI32(slots.length) - 1
|
|
189
195
|
for (let i: i32 = 0; i < toI32(hashes.length); i++) {
|
|
190
|
-
const h = hashes[i]
|
|
196
|
+
const h = hashes[i]
|
|
191
197
|
if (h !== 0) {
|
|
192
|
-
fileEntry(slots, mask, h, i)
|
|
198
|
+
fileEntry(slots, mask, h, i)
|
|
193
199
|
}
|
|
194
200
|
}
|
|
195
|
-
}
|
|
201
|
+
}
|
|
196
202
|
|
|
197
203
|
/**
|
|
198
204
|
* `delete`'s write through a found probe result: the bucket becomes a
|
|
@@ -200,15 +206,15 @@ const refile = (slots: u32[], hashes: u32[]): void => {
|
|
|
200
206
|
* until a rebuild compacts them away (§6.1).
|
|
201
207
|
*/
|
|
202
208
|
const killEntry = (slots: u32[], hashes: u32[], found: i64): void => {
|
|
203
|
-
const at = toI32(found)
|
|
204
|
-
const bucket = toI32(found >> 32)
|
|
209
|
+
const at = toI32(found)
|
|
210
|
+
const bucket = toI32(found >> 32)
|
|
205
211
|
if (bucket >= 0 && bucket < toI32(slots.length)) {
|
|
206
|
-
slots[bucket] = 16777216
|
|
212
|
+
slots[bucket] = 16777216
|
|
207
213
|
}
|
|
208
214
|
if (at >= 0 && at < toI32(hashes.length)) {
|
|
209
|
-
hashes[at] = 0
|
|
215
|
+
hashes[at] = 0
|
|
210
216
|
}
|
|
211
|
-
}
|
|
217
|
+
}
|
|
212
218
|
|
|
213
219
|
/**
|
|
214
220
|
* Zero every element in place: the buckets, which emptying a table and
|
|
@@ -217,9 +223,9 @@ const killEntry = (slots: u32[], hashes: u32[], found: i64): void => {
|
|
|
217
223
|
*/
|
|
218
224
|
const clearSlots = (slots: u32[]): void => {
|
|
219
225
|
for (let i: i32 = 0; i < toI32(slots.length); i++) {
|
|
220
|
-
slots[i] = 0
|
|
226
|
+
slots[i] = 0
|
|
221
227
|
}
|
|
222
|
-
}
|
|
228
|
+
}
|
|
223
229
|
|
|
224
230
|
/**
|
|
225
231
|
* The index of the first live entry at `from` or after it, or -1 when there is
|
|
@@ -231,18 +237,18 @@ const clearSlots = (slots: u32[]): void => {
|
|
|
231
237
|
const nextLive = (hashes: u32[], from: i32): i32 => {
|
|
232
238
|
for (let i: i32 = from; i >= 0 && i < toI32(hashes.length); i++) {
|
|
233
239
|
if (hashes[i] !== 0) {
|
|
234
|
-
return i
|
|
240
|
+
return i
|
|
235
241
|
}
|
|
236
242
|
}
|
|
237
|
-
return -1
|
|
238
|
-
}
|
|
243
|
+
return -1
|
|
244
|
+
}
|
|
239
245
|
|
|
240
246
|
/** Drop every element of `items`, keeping its capacity. */
|
|
241
247
|
const truncate = <T>(items: T[]): void => {
|
|
242
248
|
while (toI32(items.length) > 0) {
|
|
243
|
-
items.pop()
|
|
249
|
+
items.pop()
|
|
244
250
|
}
|
|
245
|
-
}
|
|
251
|
+
}
|
|
246
252
|
|
|
247
253
|
/**
|
|
248
254
|
* Point bucket `bucket`, the empty one an absent probe stopped at, at the entry
|
|
@@ -251,11 +257,11 @@ const truncate = <T>(items: T[]): void => {
|
|
|
251
257
|
*/
|
|
252
258
|
const fileAppended = (slots: u32[], mask: i32, bucket: i32, h: u32, used: i32): void => {
|
|
253
259
|
if (bucket >= 0 && bucket < toI32(slots.length)) {
|
|
254
|
-
slots[bucket] = slotWord(h, used - 1)
|
|
260
|
+
slots[bucket] = slotWord(h, used - 1)
|
|
255
261
|
} else {
|
|
256
|
-
fileEntry(slots, mask, h, used - 1)
|
|
262
|
+
fileEntry(slots, mask, h, used - 1)
|
|
257
263
|
}
|
|
258
|
-
}
|
|
264
|
+
}
|
|
259
265
|
|
|
260
266
|
/**
|
|
261
267
|
* A key-value table in insertion order, with JavaScript's semantics: keys are
|
|
@@ -265,60 +271,60 @@ const fileAppended = (slots: u32[], mask: i32, bucket: i32, h: u32, used: i32):
|
|
|
265
271
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: this is the global `Map`, which the compiler loads for a program that names it
|
|
266
272
|
export class Map<K, V> {
|
|
267
273
|
/** How many entries are live. The one field a program may read, and it may not write it. */
|
|
268
|
-
size: number = 0
|
|
274
|
+
size: number = 0
|
|
269
275
|
/** Bucket -> fingerprint and entry index plus one; see the header. */
|
|
270
|
-
slots: u32[]
|
|
276
|
+
slots: u32[]
|
|
271
277
|
/** `slots.length - 1`: the table is a power of two. */
|
|
272
|
-
mask: i32 = 7
|
|
278
|
+
mask: i32 = 7
|
|
273
279
|
/** Live entries, as an `i32` for the load and compaction arithmetic. */
|
|
274
|
-
live: i32 = 0
|
|
275
|
-
entryKeys: K[]
|
|
276
|
-
entryValues: V[]
|
|
280
|
+
live: i32 = 0
|
|
281
|
+
entryKeys: K[]
|
|
282
|
+
entryValues: V[]
|
|
277
283
|
/** The full hash of each entry; 0 once the entry is deleted. */
|
|
278
|
-
entryHashes: u32[]
|
|
284
|
+
entryHashes: u32[]
|
|
279
285
|
/**
|
|
280
286
|
* How many `for...of` loops are walking the table now. While it is above 0
|
|
281
287
|
* a rebuild doubles rather than compacts, and `clear` marks entries dead
|
|
282
288
|
* rather than truncating, so no entry moves under a walk's cursor (§6.2).
|
|
283
289
|
*/
|
|
284
|
-
walks: i32 = 0
|
|
290
|
+
walks: i32 = 0
|
|
285
291
|
|
|
286
292
|
constructor() {
|
|
287
|
-
this.slots = new Array<u32>(INITIAL_SLOTS)
|
|
288
|
-
this.entryKeys = []
|
|
289
|
-
this.entryValues = []
|
|
290
|
-
this.entryHashes = []
|
|
293
|
+
this.slots = new Array<u32>(INITIAL_SLOTS)
|
|
294
|
+
this.entryKeys = []
|
|
295
|
+
this.entryValues = []
|
|
296
|
+
this.entryHashes = []
|
|
291
297
|
}
|
|
292
298
|
|
|
293
299
|
/** The packed probe result for `key`: see `probeTable`, `foundAt` and `absentAt`. */
|
|
294
300
|
probe(key: K): i64 {
|
|
295
|
-
return probeTable(this.slots, this.mask, this.entryHashes, this.entryKeys, key)
|
|
301
|
+
return probeTable(this.slots, this.mask, this.entryHashes, this.entryKeys, key)
|
|
296
302
|
}
|
|
297
303
|
|
|
298
304
|
has(key: K): boolean {
|
|
299
|
-
return this.probe(key) >= 0
|
|
305
|
+
return this.probe(key) >= 0
|
|
300
306
|
}
|
|
301
307
|
|
|
302
308
|
/** Set `key` to `value`, in place when it is there and at the end when it is not: one probe. */
|
|
303
309
|
set(key: K, value: V): Map<K, V> {
|
|
304
|
-
const found = this.probe(key)
|
|
310
|
+
const found = this.probe(key)
|
|
305
311
|
if (found >= 0) {
|
|
306
|
-
this.setValueAt(toI32(found), value)
|
|
312
|
+
this.setValueAt(toI32(found), value)
|
|
307
313
|
} else {
|
|
308
|
-
this.insertAt(found, key, value)
|
|
314
|
+
this.insertAt(found, key, value)
|
|
309
315
|
}
|
|
310
|
-
return this
|
|
316
|
+
return this
|
|
311
317
|
}
|
|
312
318
|
|
|
313
319
|
delete(key: K): boolean {
|
|
314
|
-
const found = this.probe(key)
|
|
320
|
+
const found = this.probe(key)
|
|
315
321
|
if (found < 0) {
|
|
316
|
-
return false
|
|
322
|
+
return false
|
|
317
323
|
}
|
|
318
|
-
killEntry(this.slots, this.entryHashes, found)
|
|
319
|
-
this.live = this.live - 1
|
|
320
|
-
this.size = this.size - 1
|
|
321
|
-
return true
|
|
324
|
+
killEntry(this.slots, this.entryHashes, found)
|
|
325
|
+
this.live = this.live - 1
|
|
326
|
+
this.size = this.size - 1
|
|
327
|
+
return true
|
|
322
328
|
}
|
|
323
329
|
|
|
324
330
|
/**
|
|
@@ -326,16 +332,16 @@ export class Map<K, V> {
|
|
|
326
332
|
* truncated, or, while a loop walks the table, marked dead and kept (§6.1).
|
|
327
333
|
*/
|
|
328
334
|
clear(): void {
|
|
329
|
-
clearSlots(this.slots)
|
|
335
|
+
clearSlots(this.slots)
|
|
330
336
|
if (this.walks > 0) {
|
|
331
|
-
clearSlots(this.entryHashes)
|
|
337
|
+
clearSlots(this.entryHashes) // every entry dead, and the count kept
|
|
332
338
|
} else {
|
|
333
|
-
truncate(this.entryKeys)
|
|
334
|
-
truncate(this.entryValues)
|
|
335
|
-
truncate(this.entryHashes)
|
|
339
|
+
truncate(this.entryKeys)
|
|
340
|
+
truncate(this.entryValues)
|
|
341
|
+
truncate(this.entryHashes)
|
|
336
342
|
}
|
|
337
|
-
this.live = 0
|
|
338
|
-
this.size = 0
|
|
343
|
+
this.live = 0
|
|
344
|
+
this.size = 0
|
|
339
345
|
}
|
|
340
346
|
|
|
341
347
|
/**
|
|
@@ -346,66 +352,66 @@ export class Map<K, V> {
|
|
|
346
352
|
* that leaves the loop. Two stores a loop, and none per entry.
|
|
347
353
|
*/
|
|
348
354
|
walkOpen(): void {
|
|
349
|
-
this.walks = this.walks + 1
|
|
355
|
+
this.walks = this.walks + 1
|
|
350
356
|
}
|
|
351
357
|
|
|
352
358
|
walkNext(from: i32): i32 {
|
|
353
|
-
return nextLive(this.entryHashes, from)
|
|
359
|
+
return nextLive(this.entryHashes, from)
|
|
354
360
|
}
|
|
355
361
|
|
|
356
362
|
walkClose(): void {
|
|
357
|
-
this.walks = this.walks - 1
|
|
363
|
+
this.walks = this.walks - 1
|
|
358
364
|
}
|
|
359
365
|
|
|
360
366
|
/** The key of entry `index`, which `walkNext` answered. */
|
|
361
367
|
keyAt(index: i32): K {
|
|
362
368
|
if (index < 0 || index >= toI32(this.entryKeys.length)) {
|
|
363
|
-
panic("Map: no entry at this index")
|
|
369
|
+
panic("Map: no entry at this index")
|
|
364
370
|
}
|
|
365
|
-
return this.entryKeys[index]
|
|
371
|
+
return this.entryKeys[index]
|
|
366
372
|
}
|
|
367
373
|
|
|
368
374
|
/** The value of the entry a probe found, or a walk reached. */
|
|
369
375
|
valueAt(index: i32): V {
|
|
370
376
|
if (index < 0 || index >= toI32(this.entryValues.length)) {
|
|
371
|
-
panic("Map: no entry at this index")
|
|
377
|
+
panic("Map: no entry at this index")
|
|
372
378
|
}
|
|
373
|
-
return this.entryValues[index]
|
|
379
|
+
return this.entryValues[index]
|
|
374
380
|
}
|
|
375
381
|
|
|
376
382
|
/** Overwrite the value of the entry a probe found; it keeps its place. */
|
|
377
383
|
setValueAt(index: i32, value: V): void {
|
|
378
384
|
if (index >= 0 && index < toI32(this.entryValues.length)) {
|
|
379
|
-
this.entryValues[index] = value
|
|
385
|
+
this.entryValues[index] = value
|
|
380
386
|
}
|
|
381
387
|
}
|
|
382
388
|
|
|
383
389
|
/** Append an entry for `key` at the empty bucket an absent probe result names, reusing its hash. */
|
|
384
390
|
insertAt(absent: i64, key: K, value: V): void {
|
|
385
|
-
const packed = -1 - absent
|
|
386
|
-
let bucket = toI32(packed >> 32)
|
|
387
|
-
const h = toU32(packed)
|
|
391
|
+
const packed = -1 - absent
|
|
392
|
+
let bucket = toI32(packed >> 32)
|
|
393
|
+
const h = toU32(packed)
|
|
388
394
|
if (toI32(this.entryKeys.length) >= INDEX_CAP) {
|
|
389
395
|
// Dead entries hold the cap: compact them away, then find the bucket
|
|
390
396
|
// again. A walk defers compaction, so under one the cap is full.
|
|
391
397
|
if (this.live >= INDEX_CAP || this.walks > 0) {
|
|
392
|
-
panic("Map maximum size exceeded")
|
|
398
|
+
panic("Map maximum size exceeded")
|
|
393
399
|
}
|
|
394
|
-
this.rebuild()
|
|
395
|
-
bucket = -1
|
|
400
|
+
this.rebuild()
|
|
401
|
+
bucket = -1
|
|
396
402
|
}
|
|
397
|
-
this.entryKeys.push(storedKey(key))
|
|
398
|
-
this.entryValues.push(value)
|
|
399
|
-
this.entryHashes.push(h)
|
|
400
|
-
this.live = this.live + 1
|
|
401
|
-
this.size = this.size + 1
|
|
403
|
+
this.entryKeys.push(storedKey(key))
|
|
404
|
+
this.entryValues.push(value)
|
|
405
|
+
this.entryHashes.push(h)
|
|
406
|
+
this.live = this.live + 1
|
|
407
|
+
this.size = this.size + 1
|
|
402
408
|
// Every entry takes a bucket, live or dead, until a rebuild, which files
|
|
403
409
|
// the new entry with the rest; otherwise it takes the bucket the probe found.
|
|
404
|
-
const used = toI32(this.entryKeys.length)
|
|
410
|
+
const used = toI32(this.entryKeys.length)
|
|
405
411
|
if (used * 4 > toI32(this.slots.length) * 3) {
|
|
406
|
-
this.rebuild()
|
|
412
|
+
this.rebuild()
|
|
407
413
|
} else {
|
|
408
|
-
fileAppended(this.slots, this.mask, bucket, h, used)
|
|
414
|
+
fileAppended(this.slots, this.mask, bucket, h, used)
|
|
409
415
|
}
|
|
410
416
|
}
|
|
411
417
|
|
|
@@ -415,17 +421,17 @@ export class Map<K, V> {
|
|
|
415
421
|
* first rebuild after the walk compacts (§6.2).
|
|
416
422
|
*/
|
|
417
423
|
rebuild(): void {
|
|
418
|
-
const used = toI32(this.entryKeys.length)
|
|
419
|
-
const walking = this.walks > 0
|
|
420
|
-
const slots = rebuiltSlots(this.slots, walking ? used : this.live, used)
|
|
424
|
+
const used = toI32(this.entryKeys.length)
|
|
425
|
+
const walking = this.walks > 0
|
|
426
|
+
const slots = rebuiltSlots(this.slots, walking ? used : this.live, used)
|
|
421
427
|
if (!walking && this.live < used) {
|
|
422
|
-
compactEntries(this.entryKeys, this.entryHashes)
|
|
423
|
-
compactEntries(this.entryValues, this.entryHashes)
|
|
424
|
-
compactHashes(this.entryHashes)
|
|
428
|
+
compactEntries(this.entryKeys, this.entryHashes)
|
|
429
|
+
compactEntries(this.entryValues, this.entryHashes)
|
|
430
|
+
compactHashes(this.entryHashes)
|
|
425
431
|
}
|
|
426
|
-
this.slots = slots
|
|
427
|
-
this.mask = toI32(slots.length) - 1
|
|
428
|
-
refile(slots, this.entryHashes)
|
|
432
|
+
this.slots = slots
|
|
433
|
+
this.mask = toI32(slots.length) - 1
|
|
434
|
+
refile(slots, this.entryHashes)
|
|
429
435
|
}
|
|
430
436
|
|
|
431
437
|
/**
|
|
@@ -439,19 +445,19 @@ export class Map<K, V> {
|
|
|
439
445
|
*/
|
|
440
446
|
reserveSlots(n: number): void {
|
|
441
447
|
if (!(n > 0)) {
|
|
442
|
-
return
|
|
448
|
+
return
|
|
443
449
|
}
|
|
444
|
-
const want: i32 = n >= 16777215 ? INDEX_CAP : toI32(n)
|
|
445
|
-
const have = toI32(this.slots.length)
|
|
446
|
-
let size = have
|
|
450
|
+
const want: i32 = n >= 16777215 ? INDEX_CAP : toI32(n)
|
|
451
|
+
const have = toI32(this.slots.length)
|
|
452
|
+
let size = have
|
|
447
453
|
while (want * 4 > size * 3) {
|
|
448
|
-
size = size * 2
|
|
454
|
+
size = size * 2
|
|
449
455
|
}
|
|
450
456
|
if (size > have) {
|
|
451
|
-
const slots = new Array<u32>(size)
|
|
452
|
-
this.slots = slots
|
|
453
|
-
this.mask = size - 1
|
|
454
|
-
refile(slots, this.entryHashes)
|
|
457
|
+
const slots = new Array<u32>(size)
|
|
458
|
+
this.slots = slots
|
|
459
|
+
this.mask = size - 1
|
|
460
|
+
refile(slots, this.entryHashes)
|
|
455
461
|
}
|
|
456
462
|
}
|
|
457
463
|
}
|
|
@@ -460,117 +466,117 @@ export class Map<K, V> {
|
|
|
460
466
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: this is the global `Set`, which the compiler loads for a program that names it
|
|
461
467
|
export class Set<T> {
|
|
462
468
|
/** How many elements are live. The one field a program may read, and it may not write it. */
|
|
463
|
-
size: number = 0
|
|
464
|
-
slots: u32[]
|
|
465
|
-
mask: i32 = 7
|
|
466
|
-
live: i32 = 0
|
|
467
|
-
entryKeys: T[]
|
|
468
|
-
entryHashes: u32[]
|
|
469
|
+
size: number = 0
|
|
470
|
+
slots: u32[]
|
|
471
|
+
mask: i32 = 7
|
|
472
|
+
live: i32 = 0
|
|
473
|
+
entryKeys: T[]
|
|
474
|
+
entryHashes: u32[]
|
|
469
475
|
/** `Map`'s walk count: how many `for...of` loops are walking the table now. */
|
|
470
|
-
walks: i32 = 0
|
|
476
|
+
walks: i32 = 0
|
|
471
477
|
|
|
472
478
|
constructor() {
|
|
473
|
-
this.slots = new Array<u32>(INITIAL_SLOTS)
|
|
474
|
-
this.entryKeys = []
|
|
475
|
-
this.entryHashes = []
|
|
479
|
+
this.slots = new Array<u32>(INITIAL_SLOTS)
|
|
480
|
+
this.entryKeys = []
|
|
481
|
+
this.entryHashes = []
|
|
476
482
|
}
|
|
477
483
|
|
|
478
484
|
probe(key: T): i64 {
|
|
479
|
-
return probeTable(this.slots, this.mask, this.entryHashes, this.entryKeys, key)
|
|
485
|
+
return probeTable(this.slots, this.mask, this.entryHashes, this.entryKeys, key)
|
|
480
486
|
}
|
|
481
487
|
|
|
482
488
|
has(key: T): boolean {
|
|
483
|
-
return this.probe(key) >= 0
|
|
489
|
+
return this.probe(key) >= 0
|
|
484
490
|
}
|
|
485
491
|
|
|
486
492
|
/** Add `key` at the end when it is not there already: one probe. */
|
|
487
493
|
add(key: T): Set<T> {
|
|
488
|
-
const found = this.probe(key)
|
|
494
|
+
const found = this.probe(key)
|
|
489
495
|
if (found < 0) {
|
|
490
|
-
this.insertAt(found, key)
|
|
496
|
+
this.insertAt(found, key)
|
|
491
497
|
}
|
|
492
|
-
return this
|
|
498
|
+
return this
|
|
493
499
|
}
|
|
494
500
|
|
|
495
501
|
delete(key: T): boolean {
|
|
496
|
-
const found = this.probe(key)
|
|
502
|
+
const found = this.probe(key)
|
|
497
503
|
if (found < 0) {
|
|
498
|
-
return false
|
|
504
|
+
return false
|
|
499
505
|
}
|
|
500
|
-
killEntry(this.slots, this.entryHashes, found)
|
|
501
|
-
this.live = this.live - 1
|
|
502
|
-
this.size = this.size - 1
|
|
503
|
-
return true
|
|
506
|
+
killEntry(this.slots, this.entryHashes, found)
|
|
507
|
+
this.live = this.live - 1
|
|
508
|
+
this.size = this.size - 1
|
|
509
|
+
return true
|
|
504
510
|
}
|
|
505
511
|
|
|
506
512
|
clear(): void {
|
|
507
|
-
clearSlots(this.slots)
|
|
513
|
+
clearSlots(this.slots)
|
|
508
514
|
if (this.walks > 0) {
|
|
509
|
-
clearSlots(this.entryHashes)
|
|
515
|
+
clearSlots(this.entryHashes) // every entry dead, and the count kept
|
|
510
516
|
} else {
|
|
511
|
-
truncate(this.entryKeys)
|
|
512
|
-
truncate(this.entryHashes)
|
|
517
|
+
truncate(this.entryKeys)
|
|
518
|
+
truncate(this.entryHashes)
|
|
513
519
|
}
|
|
514
|
-
this.live = 0
|
|
515
|
-
this.size = 0
|
|
520
|
+
this.live = 0
|
|
521
|
+
this.size = 0
|
|
516
522
|
}
|
|
517
523
|
|
|
518
524
|
/** `Map`'s walk: `for (const x of s)`, `s.keys()` and `s.values()` are all this one. */
|
|
519
525
|
walkOpen(): void {
|
|
520
|
-
this.walks = this.walks + 1
|
|
526
|
+
this.walks = this.walks + 1
|
|
521
527
|
}
|
|
522
528
|
|
|
523
529
|
walkNext(from: i32): i32 {
|
|
524
|
-
return nextLive(this.entryHashes, from)
|
|
530
|
+
return nextLive(this.entryHashes, from)
|
|
525
531
|
}
|
|
526
532
|
|
|
527
533
|
walkClose(): void {
|
|
528
|
-
this.walks = this.walks - 1
|
|
534
|
+
this.walks = this.walks - 1
|
|
529
535
|
}
|
|
530
536
|
|
|
531
537
|
keyAt(index: i32): T {
|
|
532
538
|
if (index < 0 || index >= toI32(this.entryKeys.length)) {
|
|
533
|
-
panic("Set: no entry at this index")
|
|
539
|
+
panic("Set: no entry at this index")
|
|
534
540
|
}
|
|
535
|
-
return this.entryKeys[index]
|
|
541
|
+
return this.entryKeys[index]
|
|
536
542
|
}
|
|
537
543
|
|
|
538
544
|
insertAt(absent: i64, key: T): void {
|
|
539
|
-
const packed = -1 - absent
|
|
540
|
-
let bucket = toI32(packed >> 32)
|
|
541
|
-
const h = toU32(packed)
|
|
545
|
+
const packed = -1 - absent
|
|
546
|
+
let bucket = toI32(packed >> 32)
|
|
547
|
+
const h = toU32(packed)
|
|
542
548
|
if (toI32(this.entryKeys.length) >= INDEX_CAP) {
|
|
543
549
|
if (this.live >= INDEX_CAP || this.walks > 0) {
|
|
544
|
-
panic("Set maximum size exceeded")
|
|
550
|
+
panic("Set maximum size exceeded")
|
|
545
551
|
}
|
|
546
|
-
this.rebuild()
|
|
547
|
-
bucket = -1
|
|
552
|
+
this.rebuild()
|
|
553
|
+
bucket = -1
|
|
548
554
|
}
|
|
549
|
-
this.entryKeys.push(storedKey(key))
|
|
550
|
-
this.entryHashes.push(h)
|
|
551
|
-
this.live = this.live + 1
|
|
552
|
-
this.size = this.size + 1
|
|
555
|
+
this.entryKeys.push(storedKey(key))
|
|
556
|
+
this.entryHashes.push(h)
|
|
557
|
+
this.live = this.live + 1
|
|
558
|
+
this.size = this.size + 1
|
|
553
559
|
// Every entry takes a bucket, live or dead, until a rebuild, which files
|
|
554
560
|
// the new entry with the rest; otherwise it takes the bucket the probe found.
|
|
555
|
-
const used = toI32(this.entryKeys.length)
|
|
561
|
+
const used = toI32(this.entryKeys.length)
|
|
556
562
|
if (used * 4 > toI32(this.slots.length) * 3) {
|
|
557
|
-
this.rebuild()
|
|
563
|
+
this.rebuild()
|
|
558
564
|
} else {
|
|
559
|
-
fileAppended(this.slots, this.mask, bucket, h, used)
|
|
565
|
+
fileAppended(this.slots, this.mask, bucket, h, used)
|
|
560
566
|
}
|
|
561
567
|
}
|
|
562
568
|
|
|
563
569
|
rebuild(): void {
|
|
564
|
-
const used = toI32(this.entryKeys.length)
|
|
565
|
-
const walking = this.walks > 0
|
|
566
|
-
const slots = rebuiltSlots(this.slots, walking ? used : this.live, used)
|
|
570
|
+
const used = toI32(this.entryKeys.length)
|
|
571
|
+
const walking = this.walks > 0
|
|
572
|
+
const slots = rebuiltSlots(this.slots, walking ? used : this.live, used)
|
|
567
573
|
if (!walking && this.live < used) {
|
|
568
|
-
compactEntries(this.entryKeys, this.entryHashes)
|
|
569
|
-
compactHashes(this.entryHashes)
|
|
574
|
+
compactEntries(this.entryKeys, this.entryHashes)
|
|
575
|
+
compactHashes(this.entryHashes)
|
|
570
576
|
}
|
|
571
|
-
this.slots = slots
|
|
572
|
-
this.mask = toI32(slots.length) - 1
|
|
573
|
-
refile(slots, this.entryHashes)
|
|
577
|
+
this.slots = slots
|
|
578
|
+
this.mask = toI32(slots.length) - 1
|
|
579
|
+
refile(slots, this.entryHashes)
|
|
574
580
|
}
|
|
575
581
|
}
|
|
576
582
|
|
|
@@ -582,4 +588,4 @@ export class Set<T> {
|
|
|
582
588
|
* key. It sits last in the file so that adding it moved no line a `-g` build
|
|
583
589
|
* of an earlier function records.
|
|
584
590
|
*/
|
|
585
|
-
const storedKey = <K>(key: K): K => key
|
|
591
|
+
const storedKey = <K>(key: K): K => key
|