@nxtedition/slice 1.0.1

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/lib/index.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ import util from 'node:util';
2
+ export declare class Slice {
3
+ buffer: Buffer;
4
+ byteOffset: number;
5
+ byteLength: number;
6
+ maxByteLength: number;
7
+ static EMPTY_BUF: Buffer;
8
+ constructor(buffer?: Buffer<ArrayBufferLike>, byteOffset?: number, byteLength?: number, maxByteLength?: number);
9
+ reset(): void;
10
+ get length(): number;
11
+ copy(target: Uint8Array | Slice, targetStart?: number, sourceStart?: number, sourceEnd?: number): number;
12
+ compare(target: Uint8Array | Slice, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): -1 | 0 | 1;
13
+ write(string: string, offset?: number, length?: number, encoding?: BufferEncoding): number;
14
+ set(source: Buffer | Slice | null | undefined, offset?: number): void;
15
+ at(index: number): number;
16
+ test(expr: {
17
+ test: (buffer: Buffer, byteOffset: number, byteLength: number) => boolean;
18
+ }): boolean;
19
+ toString(encoding?: BufferEncoding, start?: number, end?: number): string;
20
+ toBuffer(start?: number, end?: number): Buffer;
21
+ [Symbol.toStringTag](): string;
22
+ [util.inspect.custom](depth: number, options: util.InspectOptionsStylized, inspect: typeof util.inspect): string;
23
+ }
24
+ /**
25
+ * A buddy allocator using XOR bitmap for efficient coalescing.
26
+ * Each bit tracks the XOR of a buddy pair's allocation state.
27
+ * When a bit is 0, both buddies have the same state (both free or both allocated).
28
+ * When a bit is 1, the buddies have different states.
29
+ */
30
+ export declare class BuddyAllocator {
31
+ #private;
32
+ constructor(poolTotal?: number);
33
+ realloc(slice: Slice, byteLength: number): Slice;
34
+ get stats(): {
35
+ size: number;
36
+ padding: number;
37
+ ratio: number;
38
+ poolTotal: number;
39
+ poolUsed: number;
40
+ poolSize: number;
41
+ poolCount: number;
42
+ poolList: number[];
43
+ };
44
+ isFromPool(slice: Slice | null | undefined): boolean;
45
+ }
package/lib/index.js ADDED
@@ -0,0 +1,462 @@
1
+ import util from 'node:util'
2
+
3
+ const EMPTY_BUF = Buffer.alloc(0)
4
+
5
+ export class Slice {
6
+ buffer = EMPTY_BUF
7
+ byteOffset = 0
8
+ byteLength = 0
9
+ maxByteLength = 0
10
+
11
+ static EMPTY_BUF = EMPTY_BUF
12
+
13
+ constructor(
14
+ buffer = Slice.EMPTY_BUF,
15
+ byteOffset = 0,
16
+ byteLength = buffer.byteLength,
17
+ maxByteLength = byteLength,
18
+ ) {
19
+ if (!(buffer instanceof Buffer)) {
20
+ throw new TypeError('buffer must be a Buffer')
21
+ }
22
+
23
+ if (byteOffset < 0 || !Number.isInteger(byteOffset)) {
24
+ throw new RangeError(`Invalid byteOffset: ${byteOffset}`)
25
+ }
26
+
27
+ if (byteLength < 0 || !Number.isInteger(byteLength)) {
28
+ throw new RangeError(`Invalid byteLength: ${byteLength}`)
29
+ }
30
+
31
+ if (
32
+ maxByteLength < byteLength ||
33
+ maxByteLength > buffer.byteLength ||
34
+ !Number.isInteger(maxByteLength)
35
+ ) {
36
+ throw new RangeError(`Invalid maxByteLength: ${maxByteLength}`)
37
+ }
38
+
39
+ this.buffer = buffer
40
+ this.byteOffset = byteOffset
41
+ this.byteLength = byteLength
42
+ this.maxByteLength = maxByteLength
43
+ }
44
+
45
+ reset() {
46
+ this.buffer = Slice.EMPTY_BUF
47
+ this.byteOffset = 0
48
+ this.byteLength = 0
49
+ this.maxByteLength = 0
50
+ }
51
+
52
+ get length() {
53
+ return this.byteLength
54
+ }
55
+
56
+ copy(
57
+ target ,
58
+ targetStart ,
59
+ sourceStart ,
60
+ sourceEnd ,
61
+ ) {
62
+ if (target instanceof Slice) {
63
+ if (targetStart === undefined) {
64
+ targetStart = target.byteOffset
65
+ } else {
66
+ targetStart += target.byteOffset
67
+ }
68
+
69
+ target = target.buffer
70
+ }
71
+
72
+ if (sourceStart === undefined) {
73
+ sourceStart = this.byteOffset
74
+ } else {
75
+ sourceStart += this.byteOffset
76
+ }
77
+
78
+ if (sourceEnd === undefined) {
79
+ sourceEnd = this.byteLength + this.byteOffset
80
+ } else {
81
+ sourceEnd += this.byteOffset
82
+ }
83
+
84
+ if (this.buffer === target && sourceStart === targetStart) {
85
+ return sourceEnd - sourceStart
86
+ }
87
+
88
+ return this.buffer.copy(target, targetStart, sourceStart, sourceEnd)
89
+ }
90
+
91
+ compare(
92
+ target ,
93
+ targetStart ,
94
+ targetEnd ,
95
+ sourceStart ,
96
+ sourceEnd ,
97
+ ) {
98
+ if (target instanceof Slice) {
99
+ if (targetStart === undefined) {
100
+ targetStart = target.byteOffset
101
+ } else {
102
+ targetStart += target.byteOffset
103
+ }
104
+
105
+ if (targetEnd === undefined) {
106
+ targetEnd = target.byteLength + target.byteOffset
107
+ } else {
108
+ targetEnd += target.byteOffset
109
+ }
110
+
111
+ target = target.buffer
112
+ }
113
+
114
+ if (sourceStart === undefined) {
115
+ sourceStart = this.byteOffset
116
+ } else {
117
+ sourceStart += this.byteOffset
118
+ }
119
+
120
+ if (sourceEnd === undefined) {
121
+ sourceEnd = this.byteLength + this.byteOffset
122
+ } else {
123
+ sourceEnd += this.byteOffset
124
+ }
125
+
126
+ return this.buffer.compare(target, targetStart, targetEnd, sourceStart, sourceEnd)
127
+ }
128
+
129
+ write(string , offset , length , encoding ) {
130
+ if (offset === undefined) {
131
+ offset = this.byteOffset
132
+ } else {
133
+ offset += this.byteOffset
134
+ }
135
+
136
+ if (length === undefined) {
137
+ length = this.byteLength + this.byteOffset - offset
138
+ }
139
+
140
+ return this.buffer.write(string, offset, length, encoding)
141
+ }
142
+
143
+ set(source , offset ) {
144
+ if (source == null) {
145
+ return
146
+ }
147
+
148
+ if (offset === undefined) {
149
+ offset = this.byteOffset
150
+ } else {
151
+ offset += this.byteOffset
152
+ }
153
+
154
+ source.copy(this.buffer, offset)
155
+ }
156
+
157
+ at(index ) {
158
+ return index >= 0
159
+ ? this.buffer[this.byteOffset + index]
160
+ : this.buffer[this.byteOffset + this.byteLength + index]
161
+ }
162
+
163
+ test(expr
164
+
165
+ ) {
166
+ return expr.test(this.buffer, this.byteOffset, this.byteLength)
167
+ }
168
+
169
+ toString(encoding , start , end ) {
170
+ if (start === undefined) {
171
+ start = this.byteOffset
172
+ } else {
173
+ start += this.byteOffset
174
+ }
175
+
176
+ if (end === undefined) {
177
+ end = this.byteLength + this.byteOffset
178
+ } else {
179
+ end += this.byteOffset
180
+ }
181
+
182
+ return this.buffer.toString(encoding, start, end)
183
+ }
184
+
185
+ toBuffer(start , end ) {
186
+ if (start === undefined) {
187
+ start = this.byteOffset
188
+ } else {
189
+ start += this.byteOffset
190
+ }
191
+
192
+ if (end === undefined) {
193
+ end = this.byteLength + this.byteOffset
194
+ } else {
195
+ end += this.byteOffset
196
+ }
197
+
198
+ return start === 0 && end === this.buffer.byteLength
199
+ ? this.buffer
200
+ : this.buffer.subarray(start, end)
201
+ }
202
+
203
+ [Symbol.toStringTag]() {
204
+ return this.toString()
205
+ }
206
+
207
+ [util.inspect.custom](
208
+ depth ,
209
+ options ,
210
+ inspect ,
211
+ ) {
212
+ const bytes = []
213
+ for (let i = 0; i < this.byteLength; i++) {
214
+ bytes.push(this.buffer[this.byteOffset + i].toString(16).padStart(2, '0'))
215
+ }
216
+ return `Slice: "${this.toString()}" <${bytes.join(' ')}>`
217
+ }
218
+ }
219
+
220
+ /**
221
+ * A buddy allocator using XOR bitmap for efficient coalescing.
222
+ * Each bit tracks the XOR of a buddy pair's allocation state.
223
+ * When a bit is 0, both buddies have the same state (both free or both allocated).
224
+ * When a bit is 1, the buddies have different states.
225
+ */
226
+ export class BuddyAllocator {
227
+ #size = 0
228
+ #padding = 0
229
+ #poolCount = 0
230
+
231
+ #poolBuffer
232
+ #poolTotal
233
+ #minOrder
234
+ #maxOrder
235
+
236
+ // Free lists indexed by order - simple arrays with push/pop
237
+ #freeLists = []
238
+
239
+ // XOR bitmap for coalescing - single contiguous buffer for cache locality
240
+ // Each order has a view into this buffer
241
+ // Bit = 0: both buddies same state, Bit = 1: different states
242
+ #bitmaps = []
243
+ #bitmapBuffer = null
244
+
245
+ constructor(poolTotal = 128 * 1024 * 1024) {
246
+ // Handle invalid pool sizes gracefully - fall back to no pool
247
+ if (!Number.isFinite(poolTotal) || poolTotal <= 0) {
248
+ this.#poolBuffer = Buffer.allocUnsafe(0)
249
+ this.#poolTotal = 0
250
+ this.#minOrder = 3
251
+ this.#maxOrder = 0
252
+ return
253
+ }
254
+
255
+ // Round up to nearest power of 2
256
+ this.#maxOrder = Math.ceil(Math.log2(poolTotal))
257
+ this.#poolTotal = 1 << this.#maxOrder
258
+
259
+ // Minimum block size is 8 bytes (order 3)
260
+ this.#minOrder = 3
261
+
262
+ this.#poolBuffer = Buffer.allocUnsafe(this.#poolTotal)
263
+
264
+ // Calculate total bitmap size and offsets for each order
265
+ // Total pairs = 2^(maxOrder-minOrder) - 1, total words ≈ that / 32
266
+ let totalWords = 0
267
+ const offsets = []
268
+ for (let order = 0; order <= this.#maxOrder; order++) {
269
+ offsets.push(totalWords)
270
+ const numPairs = 1 << Math.max(0, this.#maxOrder - order - 1)
271
+ totalWords += Math.ceil(numPairs / 32)
272
+ }
273
+
274
+ // Allocate single contiguous buffer for all bitmaps
275
+ this.#bitmapBuffer = new ArrayBuffer(totalWords * 4)
276
+
277
+ // Create views into the buffer for each order
278
+ for (let order = 0; order <= this.#maxOrder; order++) {
279
+ this.#freeLists.push([])
280
+
281
+ const numPairs = 1 << Math.max(0, this.#maxOrder - order - 1)
282
+ const numWords = Math.ceil(numPairs / 32)
283
+ this.#bitmaps.push(new Uint32Array(this.#bitmapBuffer, offsets[order] * 4, numWords))
284
+ }
285
+
286
+ // Initially, the entire buffer is one free block at max order
287
+ this.#freeLists[this.#maxOrder].push(0)
288
+ }
289
+
290
+ realloc(slice , byteLength ) {
291
+ if (!Number.isInteger(byteLength) || byteLength < 0) {
292
+ throw new TypeError(`Invalid byteLength: ${byteLength}`)
293
+ }
294
+
295
+ if (slice == null) {
296
+ slice = new Slice()
297
+ } else if (!(slice instanceof Slice)) {
298
+ throw new TypeError('slice must be a Slice instance')
299
+ }
300
+
301
+ if (slice.byteLength === byteLength) {
302
+ return slice
303
+ }
304
+
305
+ const dstOrder = byteLength === 0 ? 0 : this.#getOrder(byteLength)
306
+
307
+ if (this.isFromPool(slice)) {
308
+ // Derive order from maxByteLength
309
+ const srcOrder = 31 - Math.clz32(slice.maxByteLength)
310
+
311
+ this.#size -= slice.byteLength
312
+ this.#padding -= slice.maxByteLength - slice.byteLength
313
+
314
+ if (srcOrder === dstOrder) {
315
+ slice.byteLength = byteLength
316
+ this.#size += slice.byteLength
317
+ this.#padding += slice.maxByteLength - slice.byteLength
318
+ return slice
319
+ }
320
+
321
+ // Free the old block
322
+ this.#freeBlock(slice.byteOffset, srcOrder)
323
+ this.#poolCount--
324
+ }
325
+
326
+ slice.reset()
327
+
328
+ if (byteLength === 0) {
329
+ return slice
330
+ }
331
+
332
+ const offset = this.#allocBlock(dstOrder)
333
+ if (offset == null) {
334
+ // Fall back to regular allocation
335
+ slice.buffer = Buffer.allocUnsafeSlow(byteLength)
336
+ slice.byteOffset = 0
337
+ slice.byteLength = byteLength
338
+ slice.maxByteLength = byteLength
339
+ } else {
340
+ slice.buffer = this.#poolBuffer
341
+ slice.byteOffset = offset
342
+ slice.byteLength = byteLength
343
+ slice.maxByteLength = 1 << dstOrder
344
+
345
+ this.#poolCount++
346
+ this.#size += byteLength
347
+ this.#padding += slice.maxByteLength - slice.byteLength
348
+ }
349
+
350
+ return slice
351
+ }
352
+
353
+ get stats() {
354
+ // Calculate free space from all free lists
355
+ let freeSpace = 0
356
+ for (let order = this.#minOrder; order <= this.#maxOrder; order++) {
357
+ freeSpace += this.#freeLists[order].length << order
358
+ }
359
+
360
+ const poolUsed = this.#poolTotal - freeSpace
361
+
362
+ return {
363
+ size: this.#size,
364
+ padding: this.#padding,
365
+ ratio: this.#size > 0 ? (this.#size + this.#padding) / this.#size : 1,
366
+ poolTotal: this.#poolTotal,
367
+ poolUsed,
368
+ poolSize: poolUsed,
369
+ poolCount: this.#poolCount,
370
+ poolList: this.#freeLists.map((list) => list.length),
371
+ }
372
+ }
373
+
374
+ isFromPool(slice ) {
375
+ return slice != null && slice.buffer === this.#poolBuffer
376
+ }
377
+
378
+ #toggleBit(offset , order ) {
379
+ if (order >= this.#maxOrder) {
380
+ return false
381
+ }
382
+ const pairIndex = offset >>> (order + 1)
383
+ const wordIndex = pairIndex >>> 5
384
+ const bitMask = 1 << (pairIndex & 31)
385
+ const bitmap = this.#bitmaps[order]
386
+ bitmap[wordIndex] ^= bitMask
387
+ return (bitmap[wordIndex] & bitMask) !== 0
388
+ }
389
+
390
+ #allocBlock(order ) {
391
+ // Try to allocate from pool
392
+ if (order > this.#maxOrder || this.#poolTotal === 0) {
393
+ return null
394
+ }
395
+
396
+ // Find the smallest order with a free block
397
+ let currentOrder = order
398
+ while (currentOrder <= this.#maxOrder && this.#freeLists[currentOrder].length === 0) {
399
+ currentOrder++
400
+ }
401
+
402
+ if (currentOrder > this.#maxOrder) {
403
+ return null
404
+ }
405
+
406
+ // Pop the free block
407
+ const offset = this.#freeLists[currentOrder].pop()
408
+
409
+ // Found block at exact order - just toggle and return
410
+ if (currentOrder === order) {
411
+ this.#toggleBit(offset, order)
412
+ return offset
413
+ }
414
+
415
+ // Split down to the requested order
416
+ while (currentOrder > order) {
417
+ currentOrder--
418
+ // Add buddy to free list
419
+ const buddyOffset = offset ^ (1 << currentOrder)
420
+ this.#freeLists[currentOrder].push(buddyOffset)
421
+ // Toggle bit - one buddy free, one allocated/kept
422
+ this.#toggleBit(offset, currentOrder)
423
+ }
424
+
425
+ return offset
426
+ }
427
+
428
+ #freeBlock(offset , order ) {
429
+ while (order < this.#maxOrder) {
430
+ // Toggle bit and check if we can coalesce
431
+ const isDifferent = this.#toggleBit(offset, order)
432
+
433
+ if (isDifferent) {
434
+ // Bit is now 1: buddy is allocated, can't coalesce
435
+ this.#freeLists[order].push(offset)
436
+ return
437
+ }
438
+
439
+ // Bit is now 0: both buddies are free, coalesce
440
+ // Remove buddy from free list
441
+ const buddyOffset = offset ^ (1 << order)
442
+ const idx = this.#freeLists[order].indexOf(buddyOffset)
443
+ if (idx !== -1) {
444
+ // Swap with last and pop for O(1) removal
445
+ const last = this.#freeLists[order].length - 1
446
+ this.#freeLists[order][idx] = this.#freeLists[order][last]
447
+ this.#freeLists[order].pop()
448
+ }
449
+
450
+ // Move up to parent level
451
+ offset = offset & ~(1 << order)
452
+ order++
453
+ }
454
+
455
+ // Reached max order
456
+ this.#freeLists[order].push(offset)
457
+ }
458
+
459
+ #getOrder(byteLength ) {
460
+ return byteLength <= 1 << this.#minOrder ? this.#minOrder : 32 - Math.clz32(byteLength - 1)
461
+ }
462
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@nxtedition/slice",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "license": "UNLICENSED",
11
+ "scripts": {
12
+ "build": "rimraf lib && tsc && amaroc ./src/index.ts && mv src/index.js lib/",
13
+ "prepublishOnly": "yarn build",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "node --test",
16
+ "test:ci": "node --test"
17
+ },
18
+ "devDependencies": {
19
+ "amaroc": "^1.0.1",
20
+ "rimraf": "^6.1.2",
21
+ "typescript": "^5.9.3"
22
+ },
23
+ "gitHead": "2517ca631e010f17f7ba8be4e1b854ad18cbd746"
24
+ }