Skip to content

Commit 199533b

Browse files
committed
added generation tracking version
1 parent 944138d commit 199533b

7 files changed

Lines changed: 470 additions & 96 deletions

File tree

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
@@ -241,59 +241,90 @@ function deserializeError(error) {
241241

242242
const globalVariable = globalThis;
243243

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

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)