Skip to content

Commit 0516660

Browse files
committed
implement weakrefs
1 parent 3ab3264 commit 0516660

32 files changed

Lines changed: 798 additions & 65 deletions

.eslintrc.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
'use strict';
22

3+
const Module = require('module');
4+
5+
const ModuleFindPath = Module._findPath; // eslint-disable-line no-underscore-dangle
6+
const hacks = [
7+
'eslint-plugin-engine262',
8+
];
9+
// eslint-disable-next-line no-underscore-dangle
10+
Module._findPath = (request, paths, isMain) => {
11+
const r = ModuleFindPath(request, paths, isMain);
12+
if (!r && hacks.includes(request)) {
13+
return require.resolve(`./test/${request}`);
14+
}
15+
return r;
16+
};
17+
318
module.exports = {
419
extends: 'airbnb-base',
20+
plugins: ['engine262'],
521
parser: 'babel-eslint',
622
parserOptions: {
723
ecmaVersion: 2019,
@@ -45,5 +61,6 @@ module.exports = {
4561
'import/order': ['error', { 'newlines-between': 'never' }],
4662
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
4763
'global-require': 'off',
64+
'engine262/valid-throw': 'error',
4865
},
4966
};

src/abstract-ops/all.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ export * from './symbol-objects.mjs';
2626
export * from './testing-comparison.mjs';
2727
export * from './type-conversion.mjs';
2828
export * from './typedarray-objects.mjs';
29+
export * from './weak-operations.mjs';

src/abstract-ops/promise-operations.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ export class PromiseCapabilityRecord {
3535
this.Resolve = Value.undefined;
3636
this.Reject = Value.undefined;
3737
}
38+
39+
mark(m) {
40+
m(this.Promise);
41+
m(this.Resolve);
42+
m(this.Reject);
43+
}
3844
}
3945

4046
// 25.6.1.2 #sec-promisereaction-records
@@ -49,6 +55,11 @@ export class PromiseReactionRecord {
4955
this.Type = O.Type;
5056
this.Handler = O.Handler;
5157
}
58+
59+
mark(m) {
60+
m(this.Capability);
61+
m(this.Handler);
62+
}
5263
}
5364

5465
// 25.6.1.3 #sec-createresolvingfunctions

src/abstract-ops/reference-operations.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export function PutValue(V, W) {
9393
ReturnIfAbrupt(V);
9494
ReturnIfAbrupt(W);
9595
if (Type(V) !== 'Reference') {
96-
return surroundingAgent.Throw('ReferenceError');
96+
return surroundingAgent.Throw('ReferenceError', 'NotDefined', V);
9797
}
9898
let base = GetBase(V);
9999
if (IsUnresolvableReference(V) === Value.true) {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { surroundingAgent } from '../engine.mjs';
2+
import { Value } from '../value.mjs';
3+
import { NormalCompletion, AbruptCompletion, X } from '../completion.mjs';
4+
import {
5+
Assert,
6+
Call,
7+
ObjectCreate,
8+
RequireInternalSlot,
9+
} from './all.mjs';
10+
11+
// https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
12+
export function ClearKeptObjects() {
13+
// 1. Let agent be the surrounding agent.
14+
const agent = surroundingAgent;
15+
// 2. Set agent.[[KeptAlive]] to a new empty List.
16+
agent.KeptAlive = new Set();
17+
}
18+
19+
// https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
20+
export function AddToKeptObjects(object) {
21+
// 1. Let agent be the surrounding agent.
22+
const agent = surroundingAgent;
23+
// 2. Append object to agent.[[KeptAlive]].
24+
agent.KeptAlive.add(object);
25+
}
26+
27+
// https://tc39.es/proposal-weakrefs/#sec-check-for-empty-cells
28+
export function CheckForEmptyCells(finalizationGroup) {
29+
// 1. Assert: finalizationGroup has an [[Cells]] internal slot.
30+
Assert('Cells' in finalizationGroup);
31+
// 2. For each cell in finalizationGroup.[[Cells]], do
32+
for (const cell of finalizationGroup.Cells) {
33+
// a. If cell.[[WeakRefTarget]] is empty, then
34+
if (cell.WeakRefTarget === undefined) {
35+
// i. Return true.
36+
return Value.true;
37+
}
38+
}
39+
// 3. Return false.
40+
return Value.false;
41+
}
42+
43+
// https://tc39.es/proposal-weakrefs/#sec-createfinalizationgroupcleanupiterator
44+
function CreateFinalizationGroupCleanupIterator(finalizationGroup) {
45+
// 1. Assert: Type(finalizationGroup) is Object.
46+
// 2. Assert: finalizationGroup has a [[Cells]] internal slot.
47+
X(RequireInternalSlot(finalizationGroup, 'Cells'));
48+
// 3. Assert: finalizationGroup.[[Realm]].[[Intrinsics]].[[%FinalizationGroupCleanupIteratorPrototype%]] exists and has been initialized.
49+
Assert(finalizationGroup.Realm.Intrinsics['%FinalizationGroupCleanupIteratorPrototype%']);
50+
// 4. Let prototype be finalizationGroup.[[Realm]].[[Intrinsics]].[[%FinalizationGroupCleanupIteratorPrototype%]].
51+
const prototype = finalizationGroup.Realm.Intrinsics['%FinalizationGroupCleanupIteratorPrototype%'];
52+
// 5. Let iterator be ObjectCreate(prototype, « [[FinalizationGroup]] »).
53+
const iterator = ObjectCreate(prototype, ['FinalizationGroup']);
54+
// 6. Set iterator.[[FinalizationGroup]] to finalizationGroup.
55+
iterator.FinalizationGroup = finalizationGroup;
56+
// 7. Return iterator.
57+
return iterator;
58+
}
59+
60+
// https://tc39.es/proposal-weakrefs/#sec-cleanup-finalization-group
61+
export function CleanupFinalizationGroup(finalizationGroup, callback) {
62+
// 1. Assert: finalizationGroup has [[Cells]], [[CleanupCallback]], and [[IsFinalizationGroupCleanupJobActive]] internal slots.
63+
Assert('Cells' in finalizationGroup);
64+
// 2. If CheckForEmptyCells(finalizationGroup) is false, return.
65+
if (CheckForEmptyCells(finalizationGroup) === Value.false) {
66+
return NormalCompletion(Value.undefined);
67+
}
68+
// 3. Let iterator be ! CreateFinalizationGroupCleanupIterator(finalizationGroup).
69+
const iterator = X(CreateFinalizationGroupCleanupIterator(finalizationGroup));
70+
// 4. If callback is not present or undefined, set callback to finalizationGroup.[[CleanupCallback]].
71+
if (callback === undefined || callback === Value.undefined) {
72+
callback = finalizationGroup.CleanupCallback;
73+
}
74+
// 5. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to true.
75+
finalizationGroup.IsFinalizationGroupCleanupJobActive = true;
76+
// 6. Let result be Call(callback, undefined, « iterator »).
77+
const result = Call(callback, Value.undefined, [iterator]);
78+
// 7. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to false.
79+
finalizationGroup.IsFinalizationGroupCleanupJobActive = false;
80+
// 8. Set iterator.[[FinalizationGroup]] to empty.
81+
iterator.FinalizationGroup = undefined;
82+
// 9. If result is an abrupt completion, return result.
83+
if (result instanceof AbruptCompletion) {
84+
return result;
85+
}
86+
// 10. Else, return NormalCompletion(undefined).
87+
return NormalCompletion(Value.undefined);
88+
}

src/api.mjs

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
setSurroundingAgent,
1111
Agent,
1212
HostReportErrors,
13+
HostCleanupFinalizationGroup,
1314
FEATURES,
1415
} from './engine.mjs';
1516
import {
@@ -22,7 +23,7 @@ import {
2223
AbruptCompletion,
2324
Completion,
2425
NormalCompletion,
25-
Q,
26+
Q, X,
2627
ThrowCompletion,
2728
EnsureCompletion,
2829
} from './completion.mjs';
@@ -45,10 +46,67 @@ export {
4546

4647
export { inspect } from './inspect.mjs';
4748

49+
function mark() {
50+
const marked = new Set();
51+
const weakrefs = new Set();
52+
const fgs = new Set();
53+
54+
const markCb = (o) => {
55+
if (o === undefined || o === null) {
56+
return;
57+
}
58+
if (marked.has(o)) {
59+
return;
60+
}
61+
marked.add(o);
62+
if ('WeakRefTarget' in o) {
63+
weakrefs.add(o);
64+
}
65+
if ('Cells' in o) {
66+
fgs.add(o);
67+
}
68+
o.mark(markCb);
69+
};
70+
markCb(surroundingAgent);
71+
72+
// https://tc39.es/proposal-weakrefs/#sec-weakref-execution
73+
// At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:
74+
// 1. For each WeakRef ref such that ref.[[WeakRefTarget]] is obj,
75+
// a. Set ref.[[WeakRefTarget]] to empty.
76+
// 2. For each FinalizationGroup fg such that fg.[[Cells]] contains cell, such that cell.[[WeakRefTarget]] is obj,
77+
// a. Set cell.[[WeakRefTarget]] to empty.
78+
// b. Optionally, perform ! HostCleanupFinalizationGroup(fg).
79+
80+
weakrefs.forEach((w) => {
81+
if (!marked.has(w.WeakRefTarget)) {
82+
w.WeakRefTarget = undefined;
83+
}
84+
});
85+
86+
fgs.forEach((fg) => {
87+
let foundEmptyCell = false;
88+
fg.Cells.forEach((cell) => {
89+
if (!marked.has(cell.WeakRefTarget)) {
90+
cell.WeakRefTarget = undefined;
91+
foundEmptyCell = true;
92+
}
93+
});
94+
if (foundEmptyCell) {
95+
X(HostCleanupFinalizationGroup(fg));
96+
}
97+
});
98+
}
99+
48100
function runJobQueue() {
101+
if (surroundingAgent.executionContextStack.length !== 0) {
102+
return;
103+
}
104+
105+
49106
while (true) { // eslint-disable-line no-constant-condition
50107
const nextQueue = surroundingAgent.jobQueue;
51-
if (nextQueue.length === 0) {
108+
if (nextQueue.length === 0
109+
|| nextQueue.find((j) => j.HostDefined.queueName !== 'FinalizationCleanup') === undefined) {
52110
break;
53111
}
54112
const nextPending = nextQueue.shift();
@@ -58,10 +116,16 @@ function runJobQueue() {
58116
newContext.ScriptOrModule = nextPending.ScriptOrModule;
59117
surroundingAgent.executionContextStack.push(newContext);
60118
const result = nextPending.Job(...nextPending.Arguments);
61-
surroundingAgent.executionContextStack.pop(newContext);
62119
if (result instanceof AbruptCompletion) {
63120
HostReportErrors(result.Value);
64121
}
122+
123+
if (surroundingAgent.feature('WeakRefs')) {
124+
AbstractOps.ClearKeptObjects();
125+
mark();
126+
}
127+
128+
surroundingAgent.executionContextStack.pop(newContext);
65129
}
66130
}
67131

@@ -132,7 +196,8 @@ class APIRealm {
132196
if (typeof sourceText !== 'string') {
133197
throw new TypeError('sourceText must be a string');
134198
}
135-
return this.scope(() => {
199+
200+
const res = this.scope(() => {
136201
// BEGIN ScriptEvaluationJob
137202
const realm = surroundingAgent.currentRealmRecord;
138203
const s = ParseScript(sourceText, realm, {
@@ -146,12 +211,14 @@ class APIRealm {
146211
}
147212
// END ScriptEvaluationJob
148213

149-
const res = Q(ScriptEvaluation(s));
214+
return EnsureCompletion(ScriptEvaluation(s));
215+
});
150216

217+
if (!(res instanceof AbruptCompletion)) {
151218
runJobQueue();
219+
}
152220

153-
return EnsureCompletion(res);
154-
});
221+
return res;
155222
}
156223

157224
createSourceTextModule(specifier, sourceText) {
@@ -167,11 +234,13 @@ class APIRealm {
167234
specifier,
168235
Link: () => this.scope(() => module.Link()),
169236
GetNamespace: () => this.scope(() => GetModuleNamespace(module)),
170-
Evaluate: () => this.scope(() => {
171-
const result = module.Evaluate();
172-
runJobQueue();
173-
return result;
174-
}),
237+
Evaluate: () => {
238+
const res = this.scope(() => module.Evaluate());
239+
if (!(res instanceof AbruptCompletion)) {
240+
runJobQueue();
241+
}
242+
return res;
243+
},
175244
},
176245
}));
177246
if (Array.isArray(module)) {
@@ -229,6 +298,7 @@ export {
229298
export function Throw(realm, V, ...args) {
230299
return realm.scope(() => {
231300
if (typeof V === 'string') {
301+
// eslint-disable-next-line engine262/valid-throw
232302
return surroundingAgent.Throw(V, 'Raw', args[0]);
233303
}
234304
return new ThrowCompletion(V);

0 commit comments

Comments
 (0)