bumparena 0.0.5 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bumparena",
3
- "version":"0.0.5",
3
+ "version":"0.0.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/arena.js",
@@ -9,7 +9,8 @@
9
9
  "license":"MIT",
10
10
  "scripts": {
11
11
  "build": "tsc",
12
- "test": "node ./dist/arena.js"
12
+ "test": "node ./dist/test/test.test.js",
13
+ "pub": "tsc; npm publish"
13
14
  },
14
15
  "repository": {
15
16
  "type": "git",
package/arena.ts DELETED
@@ -1,277 +0,0 @@
1
- export type ArenaLocation = bigint & { readonly __data_pointer: unique symbol };
2
- export interface ArenaOptions {
3
- initalSize?: number
4
- littleEndian?: boolean
5
- allignment?: 8 | 16 | 32 | 64
6
- bucketOffsets?: number[];
7
- bucketCapacities?: number[];
8
- }
9
- export interface ArenaCustomHeaders {
10
- header0: number,
11
- header1: number,
12
- header2: number
13
- }
14
- export interface ArenaHeaders {
15
- totalLength: number
16
- payloadlength: number
17
- deleted: boolean
18
- header0: number
19
- header1: number
20
- header2: number
21
- }
22
- const enum HEADERS {
23
- TOTAL_LENGTH_0_32 = 0,
24
- PAYLOAD_LENGTH_0_32 = 1,
25
- GENERATION_BYTE_0_32 = 2,
26
- DELETED_8 = 12,
27
- USER_STATUS_0_8 = 13,
28
- USER_STATUS_1_8 = 14,
29
- USER_STATUS_2_8 = 15,
30
- }
31
-
32
- export class Arena {
33
- private HEADER_SIZE_BYTES = 16
34
- private _buffer: ArrayBuffer
35
- private _view8: Uint8Array
36
- private _view32: Uint32Array
37
- private _offset: number
38
- private _emptySpots: Uint32Array
39
- private _allignMask: 7 | 15 | 31 | 63
40
- private _allignShift: number
41
- private _bucketOffsets: number[];
42
- private _bucketCapacities: number[];
43
- private _bucketcount: number;
44
-
45
- constructor(options?: ArenaOptions) {
46
- this._buffer = new ArrayBuffer(options?.initalSize || 64 * 1024)
47
- this._view8 = new Uint8Array(this._buffer);
48
- this._view32 = new Uint32Array(this._buffer)
49
- this._offset = 0;
50
- //@ts-ignore
51
- this._allignMask = ((options?.allignment || 8) - 1!) as number
52
- this._allignShift = Math.log2(this._allignMask + 1);
53
- this._bucketCapacities = options?.bucketCapacities || [1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024]
54
- this._bucketOffsets = [];
55
- this._bucketcount = this._bucketCapacities.length;
56
- let currentOffset = 0;
57
- for (const cap of this._bucketCapacities) {
58
- this._bucketOffsets.push(currentOffset)
59
- currentOffset += (cap + 1)
60
- }
61
- this._emptySpots = new Uint32Array(currentOffset)
62
- }
63
-
64
- private _u(n: number): number {
65
- return n >>> 0;
66
- }
67
-
68
- private _idx32(byteOffset: number): number {
69
- return (byteOffset >>> 0) >> 2;
70
- }
71
-
72
- private _makePtr(offset: number, gen: number | bigint): ArenaLocation {
73
- const bOffset = BigInt(offset >>> 0);
74
- const bGen = BigInt(gen ?? 0);
75
- return ((bOffset << 32n) | (bGen & 0xFFFFFFFFn)) as ArenaLocation;
76
- }
77
-
78
- private _getOffset(ptr: ArenaLocation): number {
79
- return Number(BigInt(ptr) >> 32n) >>> 0;
80
- }
81
-
82
- private _getBucketCount(bucketIdx: number): number {
83
- return this._emptySpots[this._bucketOffsets[bucketIdx]!]!;
84
- }
85
-
86
- private _setBucketCount(bucketIdx: number, count: number): void {
87
- this._emptySpots[this._bucketOffsets[bucketIdx]!] = count;
88
- }
89
-
90
- private _getBucketOffset(bucketIdx: number, slotIdx: number): number {
91
- return this._emptySpots[this._bucketOffsets[bucketIdx]! + slotIdx + 1]!;
92
- }
93
-
94
- private _setBucketOffset(bucketIdx: number, slotIdx: number, offset: number): void {
95
- this._emptySpots[this._bucketOffsets[bucketIdx]! + slotIdx + 1] = offset;
96
- }
97
-
98
- private _initBlock(start: number, dataLength: number, headers: { header0: number, header1: number, header2: number }) {
99
- const idx = this._idx32(start)
100
- const { header0: h0, header1: h1, header2: h2 } = headers;
101
-
102
- this._view32[idx + HEADERS.TOTAL_LENGTH_0_32] = this._u((dataLength + this.HEADER_SIZE_BYTES + this._allignMask) & ~this._allignMask);
103
- this._view32[idx + HEADERS.PAYLOAD_LENGTH_0_32] = dataLength;
104
-
105
- this._view32[idx + (HEADERS.DELETED_8 >> 2)] = ((h2 || 0) << 24) | ((h1 || 0) << 16) | ((h0 || 0) << 8);
106
- this._view8[start + HEADERS.DELETED_8] = 0;
107
- }
108
-
109
- public alloc(data: Uint8Array, headers?: ArenaCustomHeaders): ArenaLocation {
110
- headers ||= { header0: 0, header1: 0, header2: 0 };
111
-
112
- const needed = (data.byteLength + this.HEADER_SIZE_BYTES + this._allignMask) & ~this._allignMask;
113
- const bucketIdx = (needed >> this._allignShift) - 1;
114
- if (bucketIdx >= 0 && bucketIdx < this._bucketcount) {
115
- const count = this._getBucketCount(bucketIdx);
116
- if (count > 0) {
117
- const recycledOffset = this._u(this._getBucketOffset(bucketIdx, count - 1));
118
- this._setBucketCount(bucketIdx, count - 1);
119
- this._initBlock(recycledOffset, data.length, headers);
120
-
121
- const gen = this._view32[this._idx32(recycledOffset) + HEADERS.GENERATION_BYTE_0_32]!;
122
- return this._makePtr(recycledOffset, gen)
123
- }
124
- }
125
- const start = this._u(this._offset)
126
- if (this._checkForSpace(needed)) this._resize()
127
- this._initBlock(start, data.byteLength, headers)
128
-
129
- this._view8.set(data, start + this.HEADER_SIZE_BYTES)
130
- this._offset = this._u((start + needed) & ~this._allignMask);
131
-
132
- const gen = this._view32[this._idx32(start) + HEADERS.GENERATION_BYTE_0_32]!
133
- return this._makePtr(start, gen)
134
- }
135
- public read(location: ArenaLocation): Uint8Array | null {
136
- const { start, generation } = this.translate(location);
137
- const idx = this._idx32(start)
138
- const currgen = BigInt(this._view32[idx + HEADERS.GENERATION_BYTE_0_32]!)
139
-
140
- if (generation !== currgen) return null
141
- if (this._view8[start + HEADERS.DELETED_8] === 1) return null
142
-
143
- const dataLength = this._view32[idx + HEADERS.PAYLOAD_LENGTH_0_32]!;
144
- return this._view8.subarray(start + this.HEADER_SIZE_BYTES, start + this.HEADER_SIZE_BYTES + dataLength);
145
- }
146
- public free(location: ArenaLocation): ArenaLocation {
147
- const { start, generation: _ } = this.translate(location)
148
- const idx = this._idx32(start)
149
- const currgen = this._view32[idx + HEADERS.GENERATION_BYTE_0_32]!
150
-
151
- this._view32[idx + HEADERS.GENERATION_BYTE_0_32] = currgen + 1
152
- this._view8[this._u(start) + HEADERS.DELETED_8] = 1
153
-
154
- const totalBlockSize = this._view32[this._idx32(start) + HEADERS.TOTAL_LENGTH_0_32]!
155
- const bucketIdx = (totalBlockSize >> this._allignShift) - 1
156
- if (bucketIdx >= 0 && bucketIdx < this._bucketcount) {
157
- const count = this._getBucketCount(bucketIdx)
158
- const capacity = this._bucketCapacities[bucketIdx]!
159
- if (count < capacity) {
160
- this._setBucketOffset(bucketIdx, count, start);
161
- this._setBucketCount(bucketIdx, count + 1);
162
- }
163
- }
164
- return this._makePtr(0, 0) as ArenaLocation
165
- }
166
-
167
- private _checkForSpace(size: number): boolean {
168
- if (this._buffer.byteLength >= (this._offset + size)) return false
169
- return true
170
- }
171
-
172
- private _resize() {
173
- //@ts-ignore
174
- this._buffer = this._buffer.transfer(this._buffer.byteLength * 2)
175
- this._view8 = new Uint8Array(this._buffer)
176
- this._view32 = new Uint32Array(this._buffer)
177
- }
178
-
179
- public size(): number {
180
- return this._buffer.byteLength
181
- }
182
-
183
- public getBuffer(): Uint8Array {
184
- return this._view8.subarray(0, this._u(this._offset))
185
- }
186
- public reserve(size: number): Uint8Array {
187
- const start = this._u(this._offset)
188
- this._checkForSpace(start + this.HEADER_SIZE_BYTES + size + this._allignMask & ~this._allignMask) && this._resize()
189
- this._initBlock(start, size, { header0: 0, header1: 0, header2: 0 })
190
- this._offset = (this._offset + size + this.HEADER_SIZE_BYTES + this._allignMask & ~this._allignMask) >>> 0
191
- return this._view8.subarray(start + this.HEADER_SIZE_BYTES, start + this.HEADER_SIZE_BYTES + size)
192
- }
193
- public translate(ptr: ArenaLocation) {
194
- const start = this._getOffset(ptr)
195
- const generation = ptr & 0xFFFFFFFFn;
196
- return { start, generation }
197
- }
198
- public readWithHeaders(ptr: ArenaLocation): Uint8Array | null {
199
- const { start, generation } = this.translate(ptr)
200
- let idx32 = this._idx32(start)
201
- let length = this._view32[idx32 + HEADERS.PAYLOAD_LENGTH_0_32]!
202
- if (BigInt(this._view32[idx32 + HEADERS.GENERATION_BYTE_0_32]!) !== generation) return null
203
- return this._view8.subarray(start + 12, start + this.HEADER_SIZE_BYTES + Number(length))
204
- }
205
- public label(): Array<ArenaLocation> {
206
- const ptrArray = new Array<ArenaLocation>()
207
- const limit = this._offset;
208
- let pos = 0
209
- while (this._u(pos) < this._u(limit)) {
210
- const totalLength = this._view32[this._idx32(pos)]!;
211
- if (totalLength === 0) {
212
- pos = this._idx32((pos + 16) & ~15)
213
- continue;
214
- }
215
- const isDeleted = this._view8[this._u(pos) + HEADERS.DELETED_8] === 1
216
- if (!isDeleted) {
217
- ptrArray.push(
218
- this._makePtr(pos, this._view32[this._idx32(pos) + HEADERS.GENERATION_BYTE_0_32]!)
219
- );
220
- }
221
- pos += totalLength
222
- if (totalLength === 0) break;
223
- }
224
- return ptrArray
225
- }
226
- public getHeaders(ptr: ArenaLocation): ArenaHeaders {
227
- const { start, generation: _ } = this.translate(ptr)
228
- const idx = this._u(start)
229
- return {
230
- totalLength: Number(this._view32[this._idx32(start) + HEADERS.TOTAL_LENGTH_0_32]!.toString()),
231
- payloadlength: Number(this._view32[this._idx32(start) + HEADERS.PAYLOAD_LENGTH_0_32]!.toString()),
232
- deleted: this._view8[idx + HEADERS.DELETED_8] === 1,
233
- header0: Number(this._view8[idx + HEADERS.USER_STATUS_0_8]!.toString()),
234
- header1: Number(this._view8[idx + HEADERS.USER_STATUS_1_8]!.toString()),
235
- header2: Number(this._view8[idx + HEADERS.USER_STATUS_2_8]!.toString()),
236
- }
237
- }
238
- public estimate(size: number, amnt: number): number {
239
- return (((size + this._allignMask) & ~this._allignMask) + this.HEADER_SIZE_BYTES) * amnt
240
- }
241
- public directAlloc(source: Uint8Array, startn: number, endn: number) {
242
- const start = this._u(this._offset)
243
- const len = endn - startn;
244
- const needed = (len + this.HEADER_SIZE_BYTES + this._allignMask) & ~this._allignMask;
245
- const bucketIdx = (needed >> this._allignShift) - 1;
246
- if (bucketIdx >= 0 && bucketIdx < this._bucketcount) {
247
- const count = this._getBucketCount(bucketIdx);
248
- if (count > 0) {
249
- const recycledOffset = this._u(this._getBucketOffset(bucketIdx, count - 1));
250
- this._setBucketCount(bucketIdx, count - 1);
251
- this._initBlock(recycledOffset, needed, { header0: 0, header1: 0, header2: 0 });
252
- const gen = this._view32[this._idx32(recycledOffset) + HEADERS.GENERATION_BYTE_0_32]!;
253
- this._view8.set(source.subarray(startn, endn), recycledOffset + this.HEADER_SIZE_BYTES)
254
- return this._makePtr(recycledOffset, gen)
255
- }
256
- }
257
- if (this._checkForSpace(needed)) this._resize()
258
- this._initBlock(start, len, { header0: 0, header1: 0, header2: 0 });
259
- this._offset = this._u((start + needed) & ~this._allignMask);
260
- this._view8.set(source.subarray(startn, endn), start + this.HEADER_SIZE_BYTES)
261
- const gen = this._view32[this._idx32(start) + HEADERS.GENERATION_BYTE_0_32]!
262
- return this._makePtr(start, gen)
263
-
264
- // if (this._checkForSpace(needed)) this._resize()
265
- // this._initBlock(start, needed, { header0: 0, header1: 0, header2: 0 })
266
- // this._view8.set(source.subarray(startn, endn), start + this.HEADER_SIZE_BYTES)
267
- // this._offset = this._u((start + needed) & ~this._allignMask);
268
- //
269
- // const gen = this._view32[this._idx32(start) + HEADERS.GENERATION_BYTE_0_32]!
270
- // return this._makePtr(start, gen)
271
-
272
- }
273
- public clear() {
274
- this._offset = 0
275
- this._emptySpots.fill(0)
276
- }
277
- }
package/bench/arena.ts DELETED
@@ -1,75 +0,0 @@
1
- import { pipeline } from "node:stream/promises";
2
- import { Arena } from "../arena";
3
- import { stream } from "./init.ts"
4
-
5
-
6
- let tmp: Uint8Array | null = null
7
- function getNewArenaTransform() {
8
- return new WritableStream<Uint8Array>({
9
- write(chunk) {
10
- let sourceIdx = 0;
11
- const chunkLen = chunk.byteLength;
12
-
13
- if (tmp) {
14
- const nextNewline = chunk.indexOf(10);
15
- if (nextNewline !== -1) {
16
- const totalLen = tmp.byteLength + nextNewline;
17
- const target = new Uint8Array(totalLen)
18
- target.set(tmp, 0);
19
- target.set(chunk.subarray(0, nextNewline), tmp.byteLength);
20
- arena.directAlloc(target, 0, target.length)
21
- sourceIdx = nextNewline + 1;
22
- tmp = null;
23
- } else {
24
- const newTmp = new Uint8Array(tmp.byteLength + chunkLen);
25
- newTmp.set(tmp);
26
- newTmp.set(chunk);
27
- tmp = newTmp;
28
- return;
29
- }
30
- }
31
-
32
- // 2. Schnelle Schleife für den Rest des Chunks
33
- while (true) {
34
- const idx = chunk.indexOf(10, sourceIdx);
35
- if (idx === -1) break;
36
-
37
- const lineLength = idx - sourceIdx;
38
- if (lineLength > 0) {
39
- arena.directAlloc(chunk, 0, lineLength);
40
- }
41
- sourceIdx = idx + 1;
42
- }
43
-
44
- if (sourceIdx < chunkLen) {
45
- tmp = chunk.slice(sourceIdx); // slice kopiert hier einmalig den Rest
46
- }
47
- },
48
- close() {
49
- if (tmp) {
50
- arena.directAlloc(tmp, 0, tmp.length)
51
- tmp = null
52
- }
53
- }
54
- })
55
- };
56
- const arena = new Arena();
57
- (async () => {
58
- const start = performance.now()
59
- for (let i = 0; i < 10; i++) {
60
- tmp = null
61
- const arenatransform = getNewArenaTransform()
62
- const reader = stream()
63
-
64
- await pipeline(
65
- reader,
66
- arenatransform
67
- )
68
- const _ptr = arena.label()
69
- arena.clear()
70
- }
71
- const end = performance.now()
72
- console.log(`${(end - start).toFixed(2)} ms`)
73
- console.log(process.memoryUsage())
74
- console.log(`arenasize: ${arena.size() / 1024} KB`)
75
- })();
package/bench/array.ts DELETED
@@ -1,61 +0,0 @@
1
- import { pipeline } from "node:stream/promises";
2
- import { noopWriter, stream } from "./init.ts"
3
-
4
- const array: Array<string> = [];
5
- const decode = new TextDecoder()
6
- let tmp: Uint8Array | null = null // Rest vom vorherigen Chunk
7
- function getNewArrayTransformStream() {
8
- return new WritableStream<Uint8Array>({
9
- write(chunk) {
10
- if (tmp) {
11
- const combined = new Uint8Array(tmp.byteLength + chunk.byteLength)
12
- combined.set(tmp, 0)
13
- combined.set(chunk, tmp.byteLength)
14
- chunk = combined
15
- tmp = null
16
- }
17
-
18
- let firstIdx = 0
19
- let idx: number
20
-
21
- while ((idx = chunk.indexOf(10, firstIdx)) !== -1) {
22
- const lineLength = idx - firstIdx
23
- if (lineLength > 0) {
24
- array.push(decode.decode(chunk.subarray(firstIdx, idx)!)!)
25
- }
26
- firstIdx = idx + 1
27
- }
28
-
29
- if (firstIdx < chunk.byteLength) {
30
- tmp = chunk.subarray(firstIdx)
31
- }
32
- },
33
- close() {
34
- if (tmp) {
35
- //@ts-ignore
36
- array.push(tmp)
37
- tmp = null
38
- }
39
- }
40
- })
41
- };
42
-
43
-
44
- await (async () => {
45
- const start = performance.now()
46
- for (let i = 0; i < 100; i++) {
47
- array.length = 0
48
- tmp = null
49
- const arraytransform = getNewArrayTransformStream()
50
- const reader = stream()
51
- await pipeline(
52
- reader,
53
- arraytransform,
54
- )
55
- for (const iter of array) noopWriter(iter)
56
- }
57
- const end = performance.now()
58
- console.log(`${(end - start).toFixed(2)} ms`)
59
- console.log(`Count: ${array.length}`)
60
- console.log(process.memoryUsage())
61
- })();
@@ -1,3 +0,0 @@
1
- import { generateData } from "./init.ts"
2
-
3
- generateData(12345, 10_000_000)
package/bench/init.ts DELETED
@@ -1,38 +0,0 @@
1
- import { createWriteStream, createReadStream } from "node:fs";
2
- const testfile = "test3.txt"
3
- function splitmix32(a: number) {
4
- return function() {
5
- a |= 0; a = a + 0x9e3779b9 | 0;
6
- let t = a ^ a >>> 16; t = Math.imul(t, 0x21f0aaad);
7
- t = t ^ t >>> 15; t = Math.imul(t, 0x735a2d97);
8
- return ((t = t ^ t >>> 15) >>> 0) / 4294967296;
9
- }
10
- };
11
-
12
- export function noopWriter(_data: any) { return true; };
13
-
14
- async function generateTestData(seed: number, count: number) {
15
- const writer = createWriteStream(testfile, { highWaterMark: 16 * 1024 * 1024 })
16
- const random = splitmix32(seed)
17
- const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
18
- for (let i = 0; i < count; i++) {
19
- let str = '';
20
- const len = Math.floor(random() * 5) + 10;
21
- for (let j = 0; j < len; j++) {
22
- str += characters.charAt(Math.floor(Math.random() * characters.length));
23
- }
24
- if (!writer.write(str + "\n")) {
25
- await new Promise((res, _rej) => {
26
- writer.once("drain", res)
27
- })
28
- }
29
- }
30
- };
31
-
32
- export const stream = () => {
33
- return createReadStream(testfile)
34
- };
35
-
36
- export function generateData(seed: number, count: number) {
37
- generateTestData(seed, count);
38
- }
package/dist/arena.d.ts DELETED
@@ -1,58 +0,0 @@
1
- export type ArenaLocation = bigint & {
2
- readonly __data_pointer: unique symbol;
3
- };
4
- export interface ArenaOptions {
5
- initalSize?: number;
6
- littleEndian?: boolean;
7
- allignment?: 8 | 16 | 32 | 64;
8
- bucketOffsets?: number[];
9
- bucketCapacities?: number[];
10
- }
11
- export interface ArenaCustomHeaders {
12
- header0: number;
13
- header1: number;
14
- header2: number;
15
- }
16
- export interface ArenaHeaders {
17
- totalLength: number;
18
- payloadlength: number;
19
- deleted: boolean;
20
- header0: number;
21
- header1: number;
22
- header2: number;
23
- }
24
- export declare class Arena {
25
- private HEADER_SIZE_BYTES;
26
- private _buffer;
27
- private _view8;
28
- private _view32;
29
- private _offset;
30
- private _emptySpots;
31
- private _allignMask;
32
- private _allignShift;
33
- private _bucketOffsets;
34
- private _bucketCapacities;
35
- private _bucketcount;
36
- constructor(options?: ArenaOptions);
37
- private _getBucketCount;
38
- private _setBucketCount;
39
- private _getBucketOffset;
40
- private _setBucketOffset;
41
- private _initBlock;
42
- alloc(data: Uint8Array, headers?: ArenaCustomHeaders): ArenaLocation;
43
- read(location: ArenaLocation): Uint8Array | null;
44
- free(location: ArenaLocation): ArenaLocation;
45
- private _checkForSpace;
46
- private _resize;
47
- size(): number;
48
- getBuffer(): Uint8Array;
49
- reserve(size: number): Uint8Array;
50
- translate(ptr: ArenaLocation): {
51
- start: number;
52
- generation: bigint;
53
- };
54
- readWithHeaders(ptr: ArenaLocation): Uint8Array | null;
55
- label(): Array<ArenaLocation>;
56
- getHeaders(ptr: ArenaLocation): ArenaHeaders;
57
- estimate(size: number, amnt: number): number;
58
- }
package/dist/arena.js DELETED
@@ -1,178 +0,0 @@
1
- var SHIFTOFFSETS;
2
- (function (SHIFTOFFSETS) {
3
- SHIFTOFFSETS[SHIFTOFFSETS["BYTE_8"] = 0] = "BYTE_8";
4
- SHIFTOFFSETS[SHIFTOFFSETS["BYTE_16"] = 1] = "BYTE_16";
5
- SHIFTOFFSETS[SHIFTOFFSETS["BYTE_32"] = 2] = "BYTE_32";
6
- SHIFTOFFSETS[SHIFTOFFSETS["BYTE_64"] = 3] = "BYTE_64";
7
- })(SHIFTOFFSETS || (SHIFTOFFSETS = {}));
8
- var HEADERS;
9
- (function (HEADERS) {
10
- HEADERS[HEADERS["TOTAL_LENGTH_0_32"] = 0] = "TOTAL_LENGTH_0_32";
11
- HEADERS[HEADERS["PAYLOAD_LENGTH_0_32"] = 1] = "PAYLOAD_LENGTH_0_32";
12
- HEADERS[HEADERS["GENERATION_BYTE_0_32"] = 2] = "GENERATION_BYTE_0_32";
13
- HEADERS[HEADERS["DELETED_8"] = 12] = "DELETED_8";
14
- HEADERS[HEADERS["USER_STATUS_0_8"] = 13] = "USER_STATUS_0_8";
15
- HEADERS[HEADERS["USER_STATUS_1_8"] = 14] = "USER_STATUS_1_8";
16
- HEADERS[HEADERS["USER_STATUS_2_8"] = 15] = "USER_STATUS_2_8";
17
- })(HEADERS || (HEADERS = {}));
18
- export class Arena {
19
- constructor(options) {
20
- this.HEADER_SIZE_BYTES = 16;
21
- this._buffer = new ArrayBuffer(options?.initalSize || 64 * 1024);
22
- this._view8 = new Uint8Array(this._buffer);
23
- this._view32 = new Uint32Array(this._buffer);
24
- this._offset = 0;
25
- //@ts-ignore
26
- this._allignMask = ((options?.allignment || 8) - 1);
27
- this._allignShift = Math.log2(this._allignMask + 1);
28
- this._bucketCapacities = options?.bucketCapacities || [1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024];
29
- this._bucketOffsets = [];
30
- this._bucketcount = this._bucketCapacities.length;
31
- let currentOffset = 0;
32
- for (const cap of this._bucketCapacities) {
33
- this._bucketOffsets.push(currentOffset);
34
- currentOffset += (cap + 1);
35
- }
36
- this._emptySpots = new Uint32Array(currentOffset);
37
- }
38
- _getBucketCount(bucketIdx) {
39
- return this._emptySpots[this._bucketOffsets[bucketIdx]];
40
- }
41
- _setBucketCount(bucketIdx, count) {
42
- this._emptySpots[this._bucketOffsets[bucketIdx]] = count;
43
- }
44
- _getBucketOffset(bucketIdx, slotIdx) {
45
- return this._emptySpots[this._bucketOffsets[bucketIdx] + slotIdx + 1];
46
- }
47
- _setBucketOffset(bucketIdx, slotIdx, offset) {
48
- this._emptySpots[this._bucketOffsets[bucketIdx] + slotIdx + 1] = offset;
49
- }
50
- _initBlock(start, dataLength, { header0, header1, header2 }) {
51
- const h0 = (header0 || 0) & 0xFF;
52
- const h1 = (header1 || 0) & 0xFF;
53
- const h2 = (header2 || 0) & 0xFF;
54
- const shift32 = start >> SHIFTOFFSETS.BYTE_32;
55
- this._view32[shift32 + HEADERS.TOTAL_LENGTH_0_32] = (dataLength + this.HEADER_SIZE_BYTES + this._allignMask) & ~this._allignMask;
56
- this._view32[shift32 + HEADERS.PAYLOAD_LENGTH_0_32] = dataLength;
57
- this._view32[shift32 + (HEADERS.DELETED_8 >> SHIFTOFFSETS.BYTE_32)] = (h2 << 24) | (h1 << 16) | (h0 << 8) | 0;
58
- }
59
- alloc(data, headers) {
60
- if (headers == undefined)
61
- headers = { header0: 0, header1: 0, header2: 0 };
62
- const totalBlockSize = (data.byteLength + this.HEADER_SIZE_BYTES + this._allignMask) & ~this._allignMask;
63
- const bucketIdx = (totalBlockSize >> this._allignShift) - 1;
64
- if (bucketIdx >= 0 && bucketIdx < this._bucketcount) {
65
- const count = this._getBucketCount(bucketIdx);
66
- if (count > 0) {
67
- const recycledOffset = this._getBucketOffset(bucketIdx, count - 1);
68
- this._setBucketCount(bucketIdx, count - 1);
69
- this._initBlock(recycledOffset, data.length, headers);
70
- return (BigInt(recycledOffset) << 32n | BigInt(this._view32[(recycledOffset >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32]));
71
- }
72
- }
73
- const start = this._offset;
74
- this._checkForSpace(data.byteLength + this.HEADER_SIZE_BYTES) && this._resize();
75
- this._initBlock(start, data.byteLength, headers || {});
76
- this._view8.set(data, start + this.HEADER_SIZE_BYTES);
77
- this._offset = (data.byteLength + this.HEADER_SIZE_BYTES + this._offset + this._allignMask) & ~this._allignMask;
78
- return (BigInt(start) << 32n | BigInt(this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32]));
79
- }
80
- read(location) {
81
- let start = Number(BigInt(location) >> 32n);
82
- const generation = BigInt(location) & 0xffffffffn;
83
- let length = this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.PAYLOAD_LENGTH_0_32];
84
- const diff = generation ^ BigInt(this._view32[((start >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32)]);
85
- if (diff !== 0n)
86
- return null;
87
- return this._view8.subarray(start + this.HEADER_SIZE_BYTES, start + this.HEADER_SIZE_BYTES + Number(length));
88
- }
89
- free(location) {
90
- let start = Number(BigInt(location) >> 32n);
91
- const currgen = this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32];
92
- this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32] = currgen + 1; // this part can wrap around
93
- this._view8[start + HEADERS.DELETED_8] = 1;
94
- const totalBlockSize = this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.TOTAL_LENGTH_0_32];
95
- const bucketIdx = (totalBlockSize >> this._allignShift) - 1;
96
- if (bucketIdx >= 0 && bucketIdx < this._bucketcount) {
97
- const count = this._getBucketCount(bucketIdx);
98
- const capacity = this._bucketCapacities[bucketIdx];
99
- if (count < capacity) {
100
- this._setBucketOffset(bucketIdx, count, start);
101
- this._setBucketCount(bucketIdx, count + 1);
102
- }
103
- }
104
- return 0n;
105
- }
106
- _checkForSpace(size) {
107
- if (this._buffer.byteLength >= this._offset + size)
108
- return false;
109
- return true;
110
- }
111
- _resize() {
112
- //@ts-ignore
113
- this._buffer = this._buffer.transfer(this._buffer.byteLength * 2);
114
- this._view8 = new Uint8Array(this._buffer);
115
- this._view32 = new Uint32Array(this._buffer);
116
- }
117
- size() {
118
- return this._buffer.byteLength;
119
- }
120
- getBuffer() {
121
- return this._view8.subarray(0, this._offset);
122
- }
123
- reserve(size) {
124
- const start = this._offset;
125
- this._checkForSpace(start + this.HEADER_SIZE_BYTES + size + this._allignMask & ~this._allignMask) && this._resize();
126
- this._initBlock(start, size, {});
127
- this._offset = this._offset + size + this.HEADER_SIZE_BYTES + this._allignMask & ~this._allignMask;
128
- return this._view8.subarray(start + this.HEADER_SIZE_BYTES, start + this.HEADER_SIZE_BYTES + size);
129
- }
130
- translate(ptr) {
131
- let start = Number(BigInt(ptr) >> 32n);
132
- const generation = BigInt(this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32]);
133
- return { start, generation };
134
- }
135
- readWithHeaders(ptr) {
136
- let start = Number(BigInt(ptr) >> 32n);
137
- const generation = BigInt(ptr) & 0xffffffffn;
138
- let length = this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.PAYLOAD_LENGTH_0_32];
139
- const diff = generation ^ BigInt(this._view32[((start >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32)]);
140
- if (diff !== 0n)
141
- return null;
142
- return this._view8.subarray(start + 12, start + this.HEADER_SIZE_BYTES + Number(length));
143
- }
144
- label() {
145
- const ptrArray = new Array();
146
- const limit = this._offset;
147
- let pos = 0;
148
- while (pos < limit) {
149
- const totalLength = this._view32[pos >> SHIFTOFFSETS.BYTE_32];
150
- if (totalLength === 0) {
151
- pos = (pos + 16) & ~15;
152
- continue;
153
- }
154
- const isDeleted = this._view8[pos + HEADERS.DELETED_8] === 1;
155
- if (!isDeleted) {
156
- ptrArray.push(((BigInt(pos) << 32n) | BigInt(this._view32[(pos >> SHIFTOFFSETS.BYTE_32) + HEADERS.GENERATION_BYTE_0_32])));
157
- }
158
- pos += totalLength;
159
- if (totalLength === 0)
160
- break;
161
- }
162
- return ptrArray;
163
- }
164
- getHeaders(ptr) {
165
- let start = Number(BigInt(ptr) >> 32n);
166
- return {
167
- totalLength: Number(this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.TOTAL_LENGTH_0_32].toString()),
168
- payloadlength: Number(this._view32[(start >> SHIFTOFFSETS.BYTE_32) + HEADERS.PAYLOAD_LENGTH_0_32].toString()),
169
- deleted: this._view8[start + HEADERS.DELETED_8] === 1,
170
- header0: Number(this._view8[start + HEADERS.USER_STATUS_0_8].toString()),
171
- header1: Number(this._view8[start + HEADERS.USER_STATUS_1_8].toString()),
172
- header2: Number(this._view8[start + HEADERS.USER_STATUS_2_8].toString()),
173
- };
174
- }
175
- estimate(size, amnt) {
176
- return (((size + this._allignMask) & ~this._allignMask) + this.HEADER_SIZE_BYTES) * amnt;
177
- }
178
- }
@@ -1,28 +0,0 @@
1
- import { Arena } from "../arena"
2
-
3
- export function TestDirectAlloc(): boolean {
4
- //Init
5
- const count = 255
6
- const a = new Arena()
7
- const testdata = new Array<Uint8Array>()
8
- for (let i = 0; i < count; i++) {
9
- const data = new Uint8Array([i, 1, 2, 3, 4, 5, 6, 7])
10
- testdata.push(data)
11
-
12
- a.directAlloc(data, 0, data.length)
13
- }
14
-
15
- const ptrs = a.label()
16
- if (ptrs.length !== count) throw new Error(`Allocated wrong amount of items!: needed: ${count}, got: ${ptrs.length}`)
17
-
18
- for (let i = 0; i < ptrs.length; i++) {
19
- const buf = a.read(ptrs[i])
20
- if (!buf) throw new Error(`data at index ${i} is Empty.`)
21
-
22
- for (let j = 0; j < testdata.length; j++) {
23
- if (buf[j] !== testdata[i][j]) throw new Error(`dataset ${i} at ${j} is broken!`)
24
- }
25
- }
26
- return true
27
- }
28
-
package/test/ptrs.ts DELETED
@@ -1,37 +0,0 @@
1
- import { Arena } from "../arena"
2
-
3
-
4
- export function TestCheckPtrAccess(): boolean {
5
- //Init
6
- const a = new Arena()
7
- const testdata = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])
8
- const headers = { header0: 42, header1: 24, header2: 240 };
9
-
10
- // free Slot reuse
11
- const ptr = a.alloc(testdata, headers)
12
- const res = a.read(ptr)
13
- if (res == null) throw new Error(`${testdata} Could not read data`)
14
- const resultheaders = a.getHeaders(ptr)
15
-
16
- if (resultheaders.totalLength !== testdata.byteLength + 16) throw new Error(`totalLength not right: got: ${resultheaders.totalLength} needed: ${testdata.byteLength}`)
17
- if (resultheaders.payloadlength !== testdata.byteLength) throw new Error(`payloadLength not right: got: ${resultheaders.payloadlength} needed: ${testdata.byteLength}`)
18
- if (resultheaders.header0 !== headers.header0) throw new Error(`header0: got: ${resultheaders.header0} needed: ${headers.header0}`)
19
- if (resultheaders.header1 !== headers.header1) throw new Error(`header1: got: ${resultheaders.header1} needed: ${headers.header1}`)
20
- if (resultheaders.header2 !== headers.header2) throw new Error(`header2: got: ${resultheaders.header2} needed: ${headers.header2}`)
21
- if (res.toString() !== testdata.toString()) throw new Error(`${testdata} had a problem with data integrety`)
22
-
23
- return true
24
- }
25
- export function TestUseAfterFree(): boolean {
26
- //Init
27
- const a = new Arena()
28
- const testdata = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])
29
-
30
- // USE-AFTER-FREE init
31
- const useAfterFreePtr = a.alloc(testdata)
32
- a.free(useAfterFreePtr)
33
- //final use after free with Generation this should never be possible
34
- if (a.read(useAfterFreePtr) !== null) throw new Error(`Use after Free is Possible, this is a big Problem`)
35
-
36
- return true
37
- }
package/test/reserve.ts DELETED
@@ -1,28 +0,0 @@
1
- import { Arena } from "../arena"
2
-
3
-
4
- export function TestCheckReserve(): boolean {
5
- //Init
6
- const count = 1_000
7
- const a = new Arena()
8
- const testdata = new Array<Uint8Array>()
9
- for (let i = 0; i < count; i++) {
10
- const data = new Uint8Array([i, 1, 2, 3, 4, 5, 6, 7])
11
- testdata.push(data)
12
-
13
- const buf = a.reserve(data.byteLength)
14
- buf.set(data)
15
- }
16
-
17
- const ptrs = a.label()
18
-
19
- for (let i = 0; i < ptrs.length; i++) {
20
- const buf = a.read(ptrs[i])
21
- if (!buf) throw new Error(`reservation failed at item: ${i}`)
22
- for (let j = 0; j < ptrs.length; j++) {
23
- if (buf[j] !== testdata[i][j]) throw new Error(`reservation failed at item ${i}:${j}`)
24
- }
25
- }
26
-
27
- return true
28
- }
package/test/test.test.ts DELETED
@@ -1,12 +0,0 @@
1
- import { describe, test } from "node:test"
2
- import { equal } from "node:assert"
3
- import { TestCheckPtrAccess, TestUseAfterFree } from "./ptrs.ts"
4
- import { TestCheckReserve } from "./reserve.ts"
5
- import { TestDirectAlloc } from "./directAccess.ts"
6
-
7
- describe("Arena Checks", () => {
8
- test("Pointer Access", () => equal(TestCheckPtrAccess(), true, "Data corruption is possible!"))
9
- test("Use after free", () => equal(TestUseAfterFree(), true, "Use after free is possible!"))
10
- test("Test Reserving Data", () => equal(TestCheckReserve(), true, "Something with the reservation is not working properly"))
11
- test("Test allocating data directly", () => equal(TestDirectAlloc(), true, "Direct alloc isnt working properly"))
12
- })
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": ["ES2020"],
4
- "target": "ES2020",
5
- "module": "ESNext",
6
- "moduleResolution": "bundler",
7
- "allowJs": true,
8
- "verbatimModuleSyntax": true,
9
- "strict": true,
10
- "skipLibCheck": true,
11
- "noFallthroughCasesInSwitch": true,
12
- "noUncheckedIndexedAccess": true,
13
- "noImplicitOverride": true,
14
-
15
- // für Build / .d.ts:
16
- "declaration": true,
17
- "emitDeclarationOnly": false,
18
- "outDir": "dist"
19
- },
20
- "include": ["arena.ts"]
21
- }