Skip to content

Commit

Permalink
implement weakrefs
Browse files Browse the repository at this point in the history
  • Loading branch information
devsnek committed Feb 13, 2020
1 parent 3ab3264 commit 0516660
Show file tree
Hide file tree
Showing 32 changed files with 798 additions and 65 deletions.
17 changes: 17 additions & 0 deletions .eslintrc.js
@@ -1,7 +1,23 @@
'use strict';

const Module = require('module');

const ModuleFindPath = Module._findPath; // eslint-disable-line no-underscore-dangle
const hacks = [
'eslint-plugin-engine262',
];
// eslint-disable-next-line no-underscore-dangle
Module._findPath = (request, paths, isMain) => {
const r = ModuleFindPath(request, paths, isMain);
if (!r && hacks.includes(request)) {
return require.resolve(`./test/${request}`);
}
return r;
};

module.exports = {
extends: 'airbnb-base',
plugins: ['engine262'],
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2019,
Expand Down Expand Up @@ -45,5 +61,6 @@ module.exports = {
'import/order': ['error', { 'newlines-between': 'never' }],
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'global-require': 'off',
'engine262/valid-throw': 'error',
},
};
1 change: 1 addition & 0 deletions src/abstract-ops/all.mjs
Expand Up @@ -26,3 +26,4 @@ export * from './symbol-objects.mjs';
export * from './testing-comparison.mjs';
export * from './type-conversion.mjs';
export * from './typedarray-objects.mjs';
export * from './weak-operations.mjs';
11 changes: 11 additions & 0 deletions src/abstract-ops/promise-operations.mjs
Expand Up @@ -35,6 +35,12 @@ export class PromiseCapabilityRecord {
this.Resolve = Value.undefined;
this.Reject = Value.undefined;
}

mark(m) {
m(this.Promise);
m(this.Resolve);
m(this.Reject);
}
}

// 25.6.1.2 #sec-promisereaction-records
Expand All @@ -49,6 +55,11 @@ export class PromiseReactionRecord {
this.Type = O.Type;
this.Handler = O.Handler;
}

mark(m) {
m(this.Capability);
m(this.Handler);
}
}

// 25.6.1.3 #sec-createresolvingfunctions
Expand Down
2 changes: 1 addition & 1 deletion src/abstract-ops/reference-operations.mjs
Expand Up @@ -93,7 +93,7 @@ export function PutValue(V, W) {
ReturnIfAbrupt(V);
ReturnIfAbrupt(W);
if (Type(V) !== 'Reference') {
return surroundingAgent.Throw('ReferenceError');
return surroundingAgent.Throw('ReferenceError', 'NotDefined', V);
}
let base = GetBase(V);
if (IsUnresolvableReference(V) === Value.true) {
Expand Down
88 changes: 88 additions & 0 deletions src/abstract-ops/weak-operations.mjs
@@ -0,0 +1,88 @@
import { surroundingAgent } from '../engine.mjs';
import { Value } from '../value.mjs';
import { NormalCompletion, AbruptCompletion, X } from '../completion.mjs';
import {
Assert,
Call,
ObjectCreate,
RequireInternalSlot,
} from './all.mjs';

// https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
export function ClearKeptObjects() {
// 1. Let agent be the surrounding agent.
const agent = surroundingAgent;
// 2. Set agent.[[KeptAlive]] to a new empty List.
agent.KeptAlive = new Set();
}

// https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
export function AddToKeptObjects(object) {
// 1. Let agent be the surrounding agent.
const agent = surroundingAgent;
// 2. Append object to agent.[[KeptAlive]].
agent.KeptAlive.add(object);
}

// https://tc39.es/proposal-weakrefs/#sec-check-for-empty-cells
export function CheckForEmptyCells(finalizationGroup) {
// 1. Assert: finalizationGroup has an [[Cells]] internal slot.
Assert('Cells' in finalizationGroup);
// 2. For each cell in finalizationGroup.[[Cells]], do
for (const cell of finalizationGroup.Cells) {
// a. If cell.[[WeakRefTarget]] is empty, then
if (cell.WeakRefTarget === undefined) {
// i. Return true.
return Value.true;
}
}
// 3. Return false.
return Value.false;
}

// https://tc39.es/proposal-weakrefs/#sec-createfinalizationgroupcleanupiterator
function CreateFinalizationGroupCleanupIterator(finalizationGroup) {
// 1. Assert: Type(finalizationGroup) is Object.
// 2. Assert: finalizationGroup has a [[Cells]] internal slot.
X(RequireInternalSlot(finalizationGroup, 'Cells'));
// 3. Assert: finalizationGroup.[[Realm]].[[Intrinsics]].[[%FinalizationGroupCleanupIteratorPrototype%]] exists and has been initialized.
Assert(finalizationGroup.Realm.Intrinsics['%FinalizationGroupCleanupIteratorPrototype%']);
// 4. Let prototype be finalizationGroup.[[Realm]].[[Intrinsics]].[[%FinalizationGroupCleanupIteratorPrototype%]].
const prototype = finalizationGroup.Realm.Intrinsics['%FinalizationGroupCleanupIteratorPrototype%'];
// 5. Let iterator be ObjectCreate(prototype, « [[FinalizationGroup]] »).
const iterator = ObjectCreate(prototype, ['FinalizationGroup']);
// 6. Set iterator.[[FinalizationGroup]] to finalizationGroup.
iterator.FinalizationGroup = finalizationGroup;
// 7. Return iterator.
return iterator;
}

// https://tc39.es/proposal-weakrefs/#sec-cleanup-finalization-group
export function CleanupFinalizationGroup(finalizationGroup, callback) {
// 1. Assert: finalizationGroup has [[Cells]], [[CleanupCallback]], and [[IsFinalizationGroupCleanupJobActive]] internal slots.
Assert('Cells' in finalizationGroup);
// 2. If CheckForEmptyCells(finalizationGroup) is false, return.
if (CheckForEmptyCells(finalizationGroup) === Value.false) {
return NormalCompletion(Value.undefined);
}
// 3. Let iterator be ! CreateFinalizationGroupCleanupIterator(finalizationGroup).
const iterator = X(CreateFinalizationGroupCleanupIterator(finalizationGroup));
// 4. If callback is not present or undefined, set callback to finalizationGroup.[[CleanupCallback]].
if (callback === undefined || callback === Value.undefined) {
callback = finalizationGroup.CleanupCallback;
}
// 5. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to true.
finalizationGroup.IsFinalizationGroupCleanupJobActive = true;
// 6. Let result be Call(callback, undefined, « iterator »).
const result = Call(callback, Value.undefined, [iterator]);
// 7. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to false.
finalizationGroup.IsFinalizationGroupCleanupJobActive = false;
// 8. Set iterator.[[FinalizationGroup]] to empty.
iterator.FinalizationGroup = undefined;
// 9. If result is an abrupt completion, return result.
if (result instanceof AbruptCompletion) {
return result;
}
// 10. Else, return NormalCompletion(undefined).
return NormalCompletion(Value.undefined);
}
94 changes: 82 additions & 12 deletions src/api.mjs
Expand Up @@ -10,6 +10,7 @@ import {
setSurroundingAgent,
Agent,
HostReportErrors,
HostCleanupFinalizationGroup,
FEATURES,
} from './engine.mjs';
import {
Expand All @@ -22,7 +23,7 @@ import {
AbruptCompletion,
Completion,
NormalCompletion,
Q,
Q, X,
ThrowCompletion,
EnsureCompletion,
} from './completion.mjs';
Expand All @@ -45,10 +46,67 @@ export {

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

function mark() {
const marked = new Set();
const weakrefs = new Set();
const fgs = new Set();

const markCb = (o) => {
if (o === undefined || o === null) {
return;
}
if (marked.has(o)) {
return;
}
marked.add(o);
if ('WeakRefTarget' in o) {
weakrefs.add(o);
}
if ('Cells' in o) {
fgs.add(o);
}
o.mark(markCb);
};
markCb(surroundingAgent);

// https://tc39.es/proposal-weakrefs/#sec-weakref-execution
// At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:
// 1. For each WeakRef ref such that ref.[[WeakRefTarget]] is obj,
// a. Set ref.[[WeakRefTarget]] to empty.
// 2. For each FinalizationGroup fg such that fg.[[Cells]] contains cell, such that cell.[[WeakRefTarget]] is obj,
// a. Set cell.[[WeakRefTarget]] to empty.
// b. Optionally, perform ! HostCleanupFinalizationGroup(fg).

weakrefs.forEach((w) => {
if (!marked.has(w.WeakRefTarget)) {
w.WeakRefTarget = undefined;
}
});

fgs.forEach((fg) => {
let foundEmptyCell = false;
fg.Cells.forEach((cell) => {
if (!marked.has(cell.WeakRefTarget)) {
cell.WeakRefTarget = undefined;
foundEmptyCell = true;
}
});
if (foundEmptyCell) {
X(HostCleanupFinalizationGroup(fg));
}
});
}

function runJobQueue() {
if (surroundingAgent.executionContextStack.length !== 0) {
return;
}


while (true) { // eslint-disable-line no-constant-condition
const nextQueue = surroundingAgent.jobQueue;
if (nextQueue.length === 0) {
if (nextQueue.length === 0
|| nextQueue.find((j) => j.HostDefined.queueName !== 'FinalizationCleanup') === undefined) {
break;
}
const nextPending = nextQueue.shift();
Expand All @@ -58,10 +116,16 @@ function runJobQueue() {
newContext.ScriptOrModule = nextPending.ScriptOrModule;
surroundingAgent.executionContextStack.push(newContext);
const result = nextPending.Job(...nextPending.Arguments);
surroundingAgent.executionContextStack.pop(newContext);
if (result instanceof AbruptCompletion) {
HostReportErrors(result.Value);
}

if (surroundingAgent.feature('WeakRefs')) {
AbstractOps.ClearKeptObjects();
mark();
}

surroundingAgent.executionContextStack.pop(newContext);
}
}

Expand Down Expand Up @@ -132,7 +196,8 @@ class APIRealm {
if (typeof sourceText !== 'string') {
throw new TypeError('sourceText must be a string');
}
return this.scope(() => {

const res = this.scope(() => {
// BEGIN ScriptEvaluationJob
const realm = surroundingAgent.currentRealmRecord;
const s = ParseScript(sourceText, realm, {
Expand All @@ -146,12 +211,14 @@ class APIRealm {
}
// END ScriptEvaluationJob

const res = Q(ScriptEvaluation(s));
return EnsureCompletion(ScriptEvaluation(s));
});

if (!(res instanceof AbruptCompletion)) {
runJobQueue();
}

return EnsureCompletion(res);
});
return res;
}

createSourceTextModule(specifier, sourceText) {
Expand All @@ -167,11 +234,13 @@ class APIRealm {
specifier,
Link: () => this.scope(() => module.Link()),
GetNamespace: () => this.scope(() => GetModuleNamespace(module)),
Evaluate: () => this.scope(() => {
const result = module.Evaluate();
runJobQueue();
return result;
}),
Evaluate: () => {
const res = this.scope(() => module.Evaluate());
if (!(res instanceof AbruptCompletion)) {
runJobQueue();
}
return res;
},
},
}));
if (Array.isArray(module)) {
Expand Down Expand Up @@ -229,6 +298,7 @@ export {
export function Throw(realm, V, ...args) {
return realm.scope(() => {
if (typeof V === 'string') {
// eslint-disable-next-line engine262/valid-throw
return surroundingAgent.Throw(V, 'Raw', args[0]);
}
return new ThrowCompletion(V);
Expand Down

0 comments on commit 0516660

Please sign in to comment.