mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-19 00:31:06 +00:00
Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
import { ObjectValue, Value, type PropertyKeyValue } from './value.mts';
|
||||
import {
|
||||
surroundingAgent,
|
||||
ScriptEvaluation,
|
||||
type Markable,
|
||||
} from './host-defined/engine.mts';
|
||||
import { HostEnqueueFinalizationRegistryCleanupJob } from './execution-context/WeakReference.mts';
|
||||
import { AgentSignifier } from './execution-context/Agent.mts';
|
||||
import { ExecutionContext } from './execution-context/ExecutionContext.mts';
|
||||
import {
|
||||
X,
|
||||
ThrowCompletion,
|
||||
AbruptCompletion,
|
||||
type PlainCompletion,
|
||||
type ValueCompletion,
|
||||
NormalCompletion,
|
||||
Q,
|
||||
} from './completion.mts';
|
||||
import {
|
||||
ParseScript,
|
||||
ParseModule,
|
||||
ParseJSONModule,
|
||||
ScriptRecord,
|
||||
type ParseScriptHostDefined,
|
||||
} from './parse.mts';
|
||||
import {
|
||||
AbstractModuleRecord, ModuleRecord, SourceTextModuleRecord, type ModuleRecordHostDefined, type ModuleRecordHostDefinedPublic,
|
||||
} from './modules.mts';
|
||||
import { isWeakRef, type WeakRefObject } from './intrinsics/WeakRef.mts';
|
||||
import { isFinalizationRegistryObject, type FinalizationRegistryObject } from './intrinsics/FinalizationRegistry.mts';
|
||||
import { isWeakMapObject, type WeakMapObject } from './intrinsics/WeakMap.mts';
|
||||
import { isWeakSetObject, type WeakSetObject } from './intrinsics/WeakSet.mts';
|
||||
import type { PromiseObject } from './intrinsics/Promise.mts';
|
||||
import type { ParseNode } from './parser/ParseNode.mts';
|
||||
import {
|
||||
ClearKeptObjects,
|
||||
CreateIntrinsics,
|
||||
SetDefaultGlobalBindings,
|
||||
OrdinaryObjectCreate,
|
||||
Assert,
|
||||
} from '#self';
|
||||
import {
|
||||
Realm,
|
||||
EnsureCompletion, GetModuleNamespace, GlobalEnvironmentRecord, type Intrinsics,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakref-execution */
|
||||
export function gc() {
|
||||
// At any time, if a set of objects S is not live, an ECMAScript implementation may perform the following steps atomically:
|
||||
// 1. For each obj of S, do
|
||||
// a. For each WeakRef ref such that ref.[[WeakRefTarget]] is obj,
|
||||
// i. Set ref.[[WeakRefTarget]] to empty.
|
||||
// b. For each FinalizationRegistry fg such that fg.[[Cells]] contains cell, and cell.[[WeakRefTarget]] is obj,
|
||||
// i. Set cell.[[WeakRefTarget]] to empty.
|
||||
// ii. Optionally, perform ! HostEnqueueFinalizationRegistryCleanupJob(fg).
|
||||
// c. For each WeakMap map such that map.WeakMapData contains a record r such that r.Key is obj,
|
||||
// i. Set r.[[Key]] to empty.
|
||||
// ii. Set r.[[Value]] to empty.
|
||||
// d. For each WeakSet set such that set.[[WeakSetData]] contains obj,
|
||||
// i. Replace the element of set whose value is obj with an element whose value is empty.
|
||||
|
||||
const marked = new Set<unknown>();
|
||||
const weakrefs = new Set<WeakRefObject>();
|
||||
const fgs = new Set<FinalizationRegistryObject>();
|
||||
const weakmaps = new Set<WeakMapObject>();
|
||||
const weaksets = new Set<WeakSetObject>();
|
||||
const ephemeronQueue: WeakMapObject['WeakMapData'][number][] = [];
|
||||
|
||||
const markCb = (O: unknown) => {
|
||||
if (typeof O !== 'object' || O === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (marked.has(O)) {
|
||||
return;
|
||||
}
|
||||
marked.add(O);
|
||||
|
||||
if (isWeakRef(O)) {
|
||||
weakrefs.add(O);
|
||||
markCb(O.properties);
|
||||
markCb(O.Prototype);
|
||||
} else if (isFinalizationRegistryObject(O)) {
|
||||
fgs.add(O);
|
||||
markCb(O.properties);
|
||||
markCb(O.Prototype);
|
||||
O.Cells.forEach((cell) => {
|
||||
markCb(cell.HeldValue);
|
||||
});
|
||||
} else if (isWeakMapObject(O)) {
|
||||
weakmaps.add(O);
|
||||
markCb(O.properties);
|
||||
markCb(O.Prototype);
|
||||
O.WeakMapData.forEach((r) => {
|
||||
ephemeronQueue.push(r);
|
||||
});
|
||||
} else if (isWeakSetObject(O)) {
|
||||
weaksets.add(O);
|
||||
markCb(O.properties);
|
||||
markCb(O.Prototype);
|
||||
} else if ('mark' in O) {
|
||||
(O as Markable).mark(markCb);
|
||||
}
|
||||
};
|
||||
|
||||
markCb(surroundingAgent);
|
||||
|
||||
while (ephemeronQueue.length > 0) {
|
||||
const item = ephemeronQueue.shift()!;
|
||||
if (marked.has(item.Key)) {
|
||||
markCb(item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
weakrefs.forEach((ref) => {
|
||||
if (!marked.has(ref.WeakRefTarget)) {
|
||||
ref.WeakRefTarget = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
fgs.forEach((fg) => {
|
||||
let dirty = false;
|
||||
fg.Cells.forEach((cell) => {
|
||||
if (!marked.has(cell.WeakRefTarget)) {
|
||||
cell.WeakRefTarget = undefined;
|
||||
dirty = true;
|
||||
}
|
||||
});
|
||||
if (dirty) {
|
||||
X(HostEnqueueFinalizationRegistryCleanupJob(fg));
|
||||
}
|
||||
});
|
||||
|
||||
weakmaps.forEach((map) => {
|
||||
map.WeakMapData.forEach((r) => {
|
||||
if (!marked.has(r.Key)) {
|
||||
r.Key = undefined;
|
||||
r.Value = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
weaksets.forEach((set) => {
|
||||
set.WeakSetData.forEach((obj, i) => {
|
||||
if (!marked.has(obj)) {
|
||||
set.WeakSetData[i] = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-jobs */
|
||||
export function runJobQueue() {
|
||||
if (surroundingAgent.executionContextStack.some((e) => e.ScriptOrModule !== Value.null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At some future point in time, when there is no running execution context
|
||||
// and the execution context stack is empty, the implementation must:
|
||||
while (surroundingAgent.jobQueue.length > 0) { // eslint-disable-line no-constant-condition
|
||||
const {
|
||||
job: abstractClosure,
|
||||
callerRealm,
|
||||
callerScriptOrModule,
|
||||
} = surroundingAgent.jobQueue.shift()!;
|
||||
|
||||
// 1. Perform any implementation-defined preparation steps.
|
||||
const newContext = new ExecutionContext();
|
||||
surroundingAgent.executionContextStack.push(newContext);
|
||||
newContext.Function = Value.null;
|
||||
newContext.Realm = callerRealm;
|
||||
newContext.ScriptOrModule = callerScriptOrModule;
|
||||
// 2. Call the abstract closure.
|
||||
X(abstractClosure());
|
||||
// 3. Perform any host-defined cleanup steps, after which the execution context stack must be empty.
|
||||
ClearKeptObjects();
|
||||
gc();
|
||||
surroundingAgent.executionContextStack.pop(newContext);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ManagedRealmHostDefined {
|
||||
promiseRejectionTracker?(promise: PromiseObject, operation: 'reject' | 'handle'): void;
|
||||
getImportMetaProperties?(module: ModuleRecordHostDefinedPublic): readonly { readonly Key: PropertyKeyValue, readonly Value: Value }[];
|
||||
finalizeImportMeta?(meta: ObjectValue, module: ModuleRecordHostDefinedPublic): PlainCompletion<void>;
|
||||
resolverCache?: Map<string, AbstractModuleRecord>;
|
||||
|
||||
randomSeed?(): string;
|
||||
attachingInspector?: unknown;
|
||||
attachingInspectorReportError?(realm: Realm, error: Value): void;
|
||||
/**
|
||||
* See https://tc39.es/ecma262/#sec-HostLoadImportedModule
|
||||
* In case of
|
||||
* <button type="button" onclick="import('./foo.mjs')">Click me</button>
|
||||
* and
|
||||
* new ShadowRealm().importValue('./foo.mjs', 'default')
|
||||
* a Realm instead of a ModuleRecord or ScriptRecord is passed as the referrer.
|
||||
*/
|
||||
specifier?: string | undefined;
|
||||
/** The name displayed in the inspector. */
|
||||
name?: string | undefined;
|
||||
}
|
||||
export class ManagedRealm extends Realm {
|
||||
override TemplateMap: { Site: ParseNode.TemplateLiteral; Array: ObjectValue; }[];
|
||||
|
||||
override AgentSignifier: unknown;
|
||||
|
||||
override Intrinsics: Intrinsics;
|
||||
|
||||
override randomState: BigUint64Array<ArrayBufferLike> | undefined;
|
||||
|
||||
override GlobalObject: ObjectValue;
|
||||
|
||||
override GlobalEnv: GlobalEnvironmentRecord;
|
||||
|
||||
override HostDefined: ManagedRealmHostDefined;
|
||||
|
||||
topContext: ExecutionContext;
|
||||
|
||||
active = false;
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-initializehostdefinedrealm */
|
||||
constructor(HostDefined: ManagedRealmHostDefined = {}, customizations?: (record: Realm) => [global: ObjectValue | undefined, thisValue: ObjectValue | undefined]) {
|
||||
super();
|
||||
this.Intrinsics = CreateIntrinsics(this);
|
||||
this.AgentSignifier = AgentSignifier();
|
||||
this.TemplateMap = [];
|
||||
let [global, thisValue] = customizations?.(this) || [];
|
||||
if (!global) {
|
||||
global = OrdinaryObjectCreate(this.Intrinsics['%Object.prototype%']);
|
||||
} else {
|
||||
Assert(global instanceof ObjectValue);
|
||||
}
|
||||
if (!thisValue) {
|
||||
thisValue = global;
|
||||
} else {
|
||||
Assert(thisValue instanceof ObjectValue);
|
||||
}
|
||||
this.GlobalObject = global;
|
||||
this.GlobalEnv = new GlobalEnvironmentRecord(global, thisValue);
|
||||
SetDefaultGlobalBindings(this);
|
||||
const newContext = new ExecutionContext();
|
||||
newContext.Function = Value.null;
|
||||
newContext.Realm = this;
|
||||
newContext.ScriptOrModule = Value.null;
|
||||
this.HostDefined = HostDefined;
|
||||
this.topContext = newContext;
|
||||
|
||||
surroundingAgent.hostDefinedOptions.onRealmCreated?.(this);
|
||||
}
|
||||
|
||||
scope(inspectorPreview?: boolean): Disposable | null;
|
||||
|
||||
scope<T>(cb: () => T, inspectorPreview?: boolean): T
|
||||
|
||||
scope<T>(arg0?: (() => T) | boolean, arg2?: boolean): T | Disposable | null {
|
||||
if (typeof arg0 !== 'function') {
|
||||
const inspectorPreview = arg0;
|
||||
if (this.active) {
|
||||
return null;
|
||||
}
|
||||
this.active = true;
|
||||
surroundingAgent.executionContextStack.push(this.topContext);
|
||||
using _ = inspectorPreview ? surroundingAgent.debugger_scopePreview() : null;
|
||||
return {
|
||||
[Symbol.dispose]: () => {
|
||||
surroundingAgent.executionContextStack.pop(this.topContext);
|
||||
this.active = false;
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const callback = arg0;
|
||||
if (this.active) {
|
||||
return arg0();
|
||||
}
|
||||
this.active = true;
|
||||
surroundingAgent.executionContextStack.push(this.topContext);
|
||||
const result = arg2 ? surroundingAgent.debugger_scopePreview(callback) : callback();
|
||||
surroundingAgent.executionContextStack.pop(this.topContext);
|
||||
this.active = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
compileScript(sourceText: string, hostDefined?: ParseScriptHostDefined): PlainCompletion<ScriptRecord> {
|
||||
return this.scope(() => {
|
||||
const s = ParseScript(sourceText, this, hostDefined);
|
||||
if (Array.isArray(s)) {
|
||||
return ThrowCompletion(s[0]);
|
||||
}
|
||||
return NormalCompletion(s);
|
||||
});
|
||||
}
|
||||
|
||||
compileModule(sourceText: string, hostDefined?: ModuleRecordHostDefined) {
|
||||
return this.scope(() => {
|
||||
const s = ParseModule(sourceText, this, {
|
||||
SourceTextModuleRecord: ManagedSourceTextModuleRecord,
|
||||
...hostDefined,
|
||||
});
|
||||
if (Array.isArray(s)) {
|
||||
return ThrowCompletion(s[0]);
|
||||
}
|
||||
return NormalCompletion(s);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call surroundingAgent.resumeEvaluate() to continue evaluation.
|
||||
*
|
||||
* This function will synchronously return a completion if this is a nested evaluation and debugger cannot be triggered.
|
||||
*/
|
||||
evaluate(sourceText: ScriptRecord | ModuleRecord | ValueEvaluator, callback: (completion: NormalCompletion<Value> | ThrowCompletion) => void) {
|
||||
if (!sourceText) {
|
||||
throw new TypeError('sourceText is null or undefined');
|
||||
}
|
||||
let result: ValueCompletion | undefined;
|
||||
|
||||
if (sourceText instanceof ModuleRecord) {
|
||||
const old = this.active;
|
||||
this.active = true;
|
||||
surroundingAgent.executionContextStack.push(this.topContext);
|
||||
|
||||
const loadModuleCompletion = sourceText.LoadRequestedModules();
|
||||
const link = ((): PlainCompletion<void> => {
|
||||
if (loadModuleCompletion.PromiseState === 'rejected') {
|
||||
Q(ThrowCompletion(loadModuleCompletion.PromiseResult!));
|
||||
} else if (loadModuleCompletion.PromiseState === 'pending') {
|
||||
throw new Error('Internal error: .LoadRequestedModules() returned a pending promise');
|
||||
}
|
||||
Q(sourceText.Link());
|
||||
})();
|
||||
if (link instanceof ThrowCompletion) {
|
||||
callback(link);
|
||||
return link;
|
||||
}
|
||||
surroundingAgent.evaluate(sourceText.Evaluate(), (completion) => {
|
||||
if (completion instanceof NormalCompletion && completion.Value.PromiseState === 'fulfilled') {
|
||||
result = GetModuleNamespace(sourceText, 'evaluation');
|
||||
} else {
|
||||
result = completion;
|
||||
}
|
||||
this.active = old;
|
||||
surroundingAgent.executionContextStack.pop(this.topContext);
|
||||
callback(EnsureCompletion(result));
|
||||
});
|
||||
return result;
|
||||
} else if (sourceText instanceof ScriptRecord) {
|
||||
const old = this.active;
|
||||
this.active = true;
|
||||
surroundingAgent.executionContextStack.push(this.topContext);
|
||||
|
||||
surroundingAgent.evaluate(ScriptEvaluation(sourceText), (completion) => {
|
||||
this.active = old;
|
||||
surroundingAgent.executionContextStack.pop(this.topContext);
|
||||
result = completion;
|
||||
callback(completion);
|
||||
});
|
||||
return result;
|
||||
} else {
|
||||
// this path only called by the inspector
|
||||
Assert(!!surroundingAgent.hostDefinedOptions.onDebugger);
|
||||
let emptyExecutionStack = false;
|
||||
if (!surroundingAgent.runningExecutionContext) {
|
||||
emptyExecutionStack = true;
|
||||
this.active = true;
|
||||
surroundingAgent.executionContextStack.push(this.topContext);
|
||||
}
|
||||
surroundingAgent.evaluate(sourceText, (completion) => {
|
||||
result = completion;
|
||||
if (emptyExecutionStack) {
|
||||
this.active = false;
|
||||
surroundingAgent.executionContextStack.pop(this.topContext);
|
||||
}
|
||||
callback(completion);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
evaluateScript(sourceText: string | ScriptRecord, { specifier, doNotTrackScriptId }: { specifier?: string, doNotTrackScriptId?: boolean } = {}): ValueCompletion {
|
||||
if (sourceText === undefined || sourceText === null) {
|
||||
throw new TypeError('sourceText must be a string or a ScriptRecord');
|
||||
}
|
||||
if (typeof sourceText === 'string') {
|
||||
sourceText = Q(this.compileScript(sourceText, { specifier, doNotTrackScriptId }));
|
||||
}
|
||||
|
||||
let completion;
|
||||
completion = this.evaluate(sourceText, (c) => {
|
||||
completion = c;
|
||||
});
|
||||
if (!completion) {
|
||||
surroundingAgent.resumeEvaluate({
|
||||
noBreakpoint: true,
|
||||
});
|
||||
}
|
||||
if (!completion) {
|
||||
throw new Assert.Error('Expect evaluation completes synchronously');
|
||||
}
|
||||
if (!(completion instanceof AbruptCompletion)) {
|
||||
runJobQueue();
|
||||
}
|
||||
|
||||
return completion;
|
||||
}
|
||||
|
||||
evaluateModule(sourceText: string, specifier: string): PlainCompletion<SourceTextModuleRecord>
|
||||
|
||||
evaluateModule<T extends ModuleRecord>(sourceText: T, specifier: string): PlainCompletion<T>
|
||||
|
||||
evaluateModule(sourceText: string | ModuleRecord, specifier: string): PlainCompletion<ModuleRecord> {
|
||||
if (sourceText === undefined || sourceText === null) {
|
||||
throw new TypeError('sourceText must be a string or a ModuleRecord');
|
||||
}
|
||||
if (typeof sourceText === 'string') {
|
||||
sourceText = Q(this.compileModule(sourceText, { specifier }));
|
||||
}
|
||||
|
||||
let completion;
|
||||
completion = this.evaluate(sourceText, (c) => {
|
||||
completion = c;
|
||||
if (!(completion instanceof AbruptCompletion)) {
|
||||
runJobQueue();
|
||||
}
|
||||
});
|
||||
if (!completion) {
|
||||
surroundingAgent.resumeEvaluate({
|
||||
noBreakpoint: true,
|
||||
});
|
||||
}
|
||||
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use compileModule
|
||||
*/
|
||||
createSourceTextModule(specifier: string, sourceText: string): PlainCompletion<SourceTextModuleRecord> {
|
||||
if (typeof specifier !== 'string') {
|
||||
throw new TypeError('specifier must be a string');
|
||||
}
|
||||
if (typeof sourceText !== 'string') {
|
||||
throw new TypeError('sourceText must be a string');
|
||||
}
|
||||
const module = this.scope(() => ParseModule(sourceText, this, {
|
||||
specifier,
|
||||
SourceTextModuleRecord: ManagedSourceTextModuleRecord,
|
||||
}));
|
||||
if (Array.isArray(module)) {
|
||||
return ThrowCompletion(module[0]);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
createJSONModule(specifier: string, sourceText: string) {
|
||||
if (typeof specifier !== 'string') {
|
||||
throw new TypeError('specifier must be a string');
|
||||
}
|
||||
if (typeof sourceText !== 'string') {
|
||||
throw new TypeError('sourceText must be a string');
|
||||
}
|
||||
const module = this.scope(() => ParseJSONModule(Value(sourceText), this, {
|
||||
specifier,
|
||||
}));
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
class ManagedSourceTextModuleRecord extends SourceTextModuleRecord {
|
||||
override* Evaluate() {
|
||||
const r = yield* super.Evaluate();
|
||||
runJobQueue();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user