Skip to content

Commit ea39211

Browse files
committed
added generation tracking version
1 parent 2535931 commit ea39211

File tree

7 files changed

+470
-96
lines changed

7 files changed

+470
-96
lines changed

Plugins/PackageToJS/Templates/runtime.d.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ type ref = number;
22
type pointer = number;
33

44
declare class JSObjectSpace {
5-
private _valueRefMap;
5+
private _slotByValue;
66
private _values;
7-
private _refCounts;
7+
private _stateBySlot;
88
private _freeSlotStack;
99
constructor();
1010
retain(value: any): number;
11-
retainByRef(ref: ref): number;
12-
release(ref: ref): void;
13-
getObject(ref: ref): any;
11+
retainByRef(reference: ref): number;
12+
release(reference: ref): void;
13+
getObject(reference: ref): any;
14+
private _getValidatedSlotState;
1415
}
1516

1617
/**

Plugins/PackageToJS/Templates/runtime.mjs

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -238,59 +238,90 @@ function deserializeError(error) {
238238

239239
const globalVariable = globalThis;
240240

241+
const SLOT_BITS = 22;
242+
const SLOT_MASK = (1 << SLOT_BITS) - 1;
243+
const GEN_MASK = (1 << (32 - SLOT_BITS)) - 1;
241244
class JSObjectSpace {
242245
constructor() {
246+
this._slotByValue = new Map();
243247
this._values = [];
248+
this._stateBySlot = [];
249+
this._freeSlotStack = [];
244250
this._values[0] = undefined;
245251
this._values[1] = globalVariable;
246-
this._valueRefMap = new Map();
247-
this._valueRefMap.set(globalVariable, 1);
248-
this._refCounts = [];
249-
this._refCounts[0] = 0;
250-
this._refCounts[1] = 1;
251-
this._freeSlotStack = [];
252+
this._slotByValue.set(globalVariable, 1);
253+
this._stateBySlot[1] = 1; // gen=0, rc=1
252254
}
253255
retain(value) {
254-
const id = this._valueRefMap.get(value);
255-
if (id !== undefined) {
256-
this._refCounts[id]++;
257-
return id;
258-
}
259-
const newId = this._freeSlotStack.length > 0
260-
? this._freeSlotStack.pop()
261-
: this._values.length;
262-
this._values[newId] = value;
263-
this._refCounts[newId] = 1;
264-
this._valueRefMap.set(value, newId);
265-
return newId;
266-
}
267-
retainByRef(ref) {
268-
if (this._refCounts[ref] === 0) {
269-
throw new ReferenceError("Attempted to retain invalid reference " + ref);
270-
}
271-
this._refCounts[ref]++;
272-
return ref;
273-
}
274-
release(ref) {
275-
if (--this._refCounts[ref] !== 0)
276-
return;
277-
const value = this._values[ref];
278-
this._valueRefMap.delete(value);
279-
if (ref === this._values.length - 1) {
280-
this._values.length = ref;
281-
this._refCounts.length = ref;
256+
const slot = this._slotByValue.get(value);
257+
if (slot !== undefined) {
258+
const state = this._stateBySlot[slot];
259+
const nextState = (state + 1) >>> 0;
260+
if ((nextState & SLOT_MASK) === 0) {
261+
throw new RangeError(`Reference count overflow at slot ${slot}`);
262+
}
263+
this._stateBySlot[slot] = nextState;
264+
return ((nextState & ~SLOT_MASK) | slot) >>> 0;
282265
}
283-
else {
284-
this._values[ref] = undefined;
285-
this._freeSlotStack.push(ref);
266+
let newSlot;
267+
let state;
268+
if (this._freeSlotStack.length > 0) {
269+
newSlot = this._freeSlotStack.pop();
270+
const gen = this._stateBySlot[newSlot] >>> SLOT_BITS;
271+
state = ((gen << SLOT_BITS) | 1) >>> 0;
286272
}
287-
}
288-
getObject(ref) {
289-
const value = this._values[ref];
290-
if (value === undefined) {
291-
throw new ReferenceError("Attempted to read invalid reference " + ref);
273+
else {
274+
newSlot = this._values.length;
275+
if (newSlot > SLOT_MASK) {
276+
throw new RangeError(`Reference slot overflow: ${newSlot} exceeds ${SLOT_MASK}`);
277+
}
278+
state = 1;
279+
}
280+
this._stateBySlot[newSlot] = state;
281+
this._values[newSlot] = value;
282+
this._slotByValue.set(value, newSlot);
283+
return ((state & ~SLOT_MASK) | newSlot) >>> 0;
284+
}
285+
retainByRef(reference) {
286+
const state = this._getValidatedSlotState(reference);
287+
const slot = reference & SLOT_MASK;
288+
const nextState = (state + 1) >>> 0;
289+
if ((nextState & SLOT_MASK) === 0) {
290+
throw new RangeError(`Reference count overflow at slot ${slot}`);
291+
}
292+
this._stateBySlot[slot] = nextState;
293+
return reference;
294+
}
295+
release(reference) {
296+
const state = this._getValidatedSlotState(reference);
297+
const slot = reference & SLOT_MASK;
298+
if ((state & SLOT_MASK) > 1) {
299+
this._stateBySlot[slot] = (state - 1) >>> 0;
300+
return;
292301
}
293-
return value;
302+
this._slotByValue.delete(this._values[slot]);
303+
this._values[slot] = undefined;
304+
const nextGen = ((state >>> SLOT_BITS) + 1) & GEN_MASK;
305+
this._stateBySlot[slot] = (nextGen << SLOT_BITS) >>> 0;
306+
this._freeSlotStack.push(slot);
307+
}
308+
getObject(reference) {
309+
this._getValidatedSlotState(reference);
310+
return this._values[reference & SLOT_MASK];
311+
}
312+
// Returns the packed state for the slot, after validating the reference.
313+
_getValidatedSlotState(reference) {
314+
const slot = reference & SLOT_MASK;
315+
if (slot === 0)
316+
throw new ReferenceError("Attempted to use invalid reference " + reference);
317+
const state = this._stateBySlot[slot];
318+
if (state === undefined || (state & SLOT_MASK) === 0) {
319+
throw new ReferenceError("Attempted to use invalid reference " + reference);
320+
}
321+
if ((state >>> SLOT_BITS) !== (reference >>> SLOT_BITS)) {
322+
throw new ReferenceError("Attempted to use stale reference " + reference);
323+
}
324+
return state;
294325
}
295326
}
296327

Runtime/bench/_version1.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { globalVariable } from "../src/find-global.js";
2+
import { ref } from "../src/types.js";
3+
4+
export class JSObjectSpace_v1 {
5+
private _valueRefMap: Map<any, number>;
6+
private _values: (any | undefined)[];
7+
private _refCounts: number[];
8+
private _freeSlotStack: number[];
9+
10+
constructor() {
11+
this._values = [];
12+
this._values[0] = undefined;
13+
this._values[1] = globalVariable;
14+
15+
this._valueRefMap = new Map();
16+
this._valueRefMap.set(globalVariable, 1);
17+
18+
this._refCounts = [];
19+
this._refCounts[0] = 0;
20+
this._refCounts[1] = 1;
21+
22+
this._freeSlotStack = [];
23+
}
24+
25+
retain(value: any) {
26+
const id = this._valueRefMap.get(value);
27+
if (id !== undefined) {
28+
this._refCounts[id]++;
29+
return id;
30+
}
31+
32+
const newId =
33+
this._freeSlotStack.length > 0
34+
? this._freeSlotStack.pop()!
35+
: this._values.length;
36+
this._values[newId] = value;
37+
this._refCounts[newId] = 1;
38+
this._valueRefMap.set(value, newId);
39+
return newId;
40+
}
41+
42+
retainByRef(ref: ref) {
43+
if (this._refCounts[ref] === 0) {
44+
throw new ReferenceError(
45+
"Attempted to retain invalid reference " + ref,
46+
);
47+
}
48+
49+
this._refCounts[ref]++;
50+
return ref;
51+
}
52+
53+
release(ref: ref) {
54+
if (--this._refCounts[ref] !== 0) return;
55+
56+
const value = this._values[ref];
57+
this._valueRefMap.delete(value);
58+
if (ref === this._values.length - 1) {
59+
this._values.length = ref;
60+
this._refCounts.length = ref;
61+
} else {
62+
this._values[ref] = undefined;
63+
this._freeSlotStack.push(ref);
64+
}
65+
}
66+
67+
getObject(ref: ref) {
68+
const value = this._values[ref];
69+
if (value === undefined) {
70+
throw new ReferenceError(
71+
"Attempted to read invalid reference " + ref,
72+
);
73+
}
74+
return value;
75+
}
76+
}

Runtime/bench/_version4.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { globalVariable } from "../src/find-global.js";
2+
import { ref } from "../src/types.js";
3+
4+
const SLOT_BITS = 22;
5+
const SLOT_MASK = (1 << SLOT_BITS) - 1;
6+
const GEN_MASK = (1 << (32 - SLOT_BITS)) - 1;
7+
8+
export class JSObjectSpace_v4 {
9+
private _slotByValue: Map<any, number>;
10+
private _values: (any | undefined)[];
11+
private _refCounts: number[];
12+
private _generations: number[];
13+
private _freeSlotStack: number[];
14+
15+
constructor() {
16+
this._values = [];
17+
this._values[0] = undefined;
18+
this._values[1] = globalVariable;
19+
20+
this._slotByValue = new Map();
21+
this._slotByValue.set(globalVariable, 1);
22+
23+
this._refCounts = [];
24+
this._refCounts[0] = 0;
25+
this._refCounts[1] = 1;
26+
27+
// Generation 0 for initial slots.
28+
this._generations = [];
29+
this._generations[0] = 0;
30+
this._generations[1] = 0;
31+
32+
this._freeSlotStack = [];
33+
}
34+
35+
private _encodeRef(slot: number): ref {
36+
const generation = this._generations[slot] & GEN_MASK;
37+
return ((generation << SLOT_BITS) | slot) >>> 0;
38+
}
39+
40+
private _expectValidSlot(reference: ref): number {
41+
const slot = reference & SLOT_MASK;
42+
if (slot === 0) {
43+
throw new ReferenceError(
44+
"Attempted to use invalid reference " + reference,
45+
);
46+
}
47+
const generation = reference >>> SLOT_BITS;
48+
if ((this._generations[slot]! & GEN_MASK) !== generation) {
49+
throw new ReferenceError(
50+
"Attempted to use stale reference " + reference,
51+
);
52+
}
53+
const rc = this._refCounts[slot];
54+
if (rc === undefined || rc === 0) {
55+
throw new ReferenceError(
56+
"Attempted to use invalid reference " + reference,
57+
);
58+
}
59+
return slot;
60+
}
61+
62+
retain(value: any) {
63+
const slot = this._slotByValue.get(value);
64+
if (slot !== undefined) {
65+
this._refCounts[slot]++;
66+
return this._encodeRef(slot);
67+
}
68+
69+
let newSlot: number;
70+
if (this._freeSlotStack.length > 0) {
71+
newSlot = this._freeSlotStack.pop()!;
72+
} else {
73+
newSlot = this._values.length;
74+
if (newSlot >= SLOT_MASK) {
75+
throw new RangeError(
76+
`Reference slot overflow: ${newSlot} exceeds ${SLOT_MASK}`,
77+
);
78+
}
79+
80+
if (this._generations[newSlot] === undefined) {
81+
this._generations[newSlot] = 0;
82+
}
83+
}
84+
85+
this._values[newSlot] = value;
86+
this._refCounts[newSlot] = 1;
87+
this._slotByValue.set(value, newSlot);
88+
return this._encodeRef(newSlot);
89+
}
90+
91+
retainByRef(reference: ref) {
92+
const slot = this._expectValidSlot(reference);
93+
this._refCounts[slot]++;
94+
// Return the exact incoming ref to preserve identity while live.
95+
return reference;
96+
}
97+
98+
release(reference: ref) {
99+
const slot = this._expectValidSlot(reference);
100+
if (--this._refCounts[slot] !== 0) return;
101+
102+
const value = this._values[slot];
103+
this._slotByValue.delete(value);
104+
this._values[slot] = undefined;
105+
106+
this._generations[slot] = ((this._generations[slot]! + 1) & GEN_MASK) >>> 0;
107+
108+
if (slot === this._values.length - 1) {
109+
// Compact trailing holes in fast arrays, but keep generations so
110+
// future reuse of the same slot still gets a new generation.
111+
this._values.length = slot;
112+
this._refCounts.length = slot;
113+
} else {
114+
this._freeSlotStack.push(slot);
115+
}
116+
}
117+
118+
getObject(reference: ref) {
119+
return this._values[this._expectValidSlot(reference)];
120+
}
121+
}

0 commit comments

Comments
 (0)