mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
14 KiB
14 KiB
model
| model |
|---|
| accounts/fireworks/routers/kimi-k2p5-turbo |
Plan: ask262Debug Module for engine262
Overview
Runtime execution tracker that records which ECMAScript spec sections are entered during execution. The Babel transform auto-injects ask262Debug.mark() at the start of every function body with URL comments — developer writes nothing. All metadata (sectionId, file, line) resolved at build time.
Primary Use Case: AI analysis of execution flow mapped to ECMAScript spec sections for understanding JavaScript engine behavior.
Files to Change
| File | Action |
|---|---|
engine262/src/ask262-debug.mts |
CREATE — the module with deduplication logic |
engine262/src/index.mts |
MODIFY — add named re-export |
engine262/tsconfig.base.json |
MODIFY — add #self/ask262Debug path mapping |
engine262/package.json |
MODIFY — add #self/ask262Debug import |
engine262/scripts/rollup.config.mts |
MODIFY — add resolve-self plugin to main build, extend for subpaths |
engine262/scripts/transform.mts |
MODIFY — auto-inject mark() and import |
engine262/scripts/verify-ask262-debug.mts |
CREATE — post-build verification script |
1. scripts/transform.mts — Enhance the Babel Plugin
1a. Extend State Interface
type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' |
'IteratorClose' | 'AsyncIteratorClose' | 'Value' |
'skipDebugger' | 'ask262Debug'; // NEW
interface State extends PluginPass {
needed: Partial<Record<NeededNames, boolean>>;
fileRelativePath?: string; // NEW: captured at Program enter
}
1b. Refactor addSectionFromComments
function addSectionFromComments(
path: NodePath<t.FunctionDeclaration> | NodePath<t.VariableDeclaration> |
NodePath<t.ExportNamedDeclaration> | NodePath<t.ClassMethod> | NodePath<t.ObjectMethod>,
state: State,
getName: () => string
) {
if (!path.node.leadingComments) return;
const sectionIds: string[] = [];
let url = '';
for (const c of path.node.leadingComments) {
for (const line of c.value.split('\n')) {
const matches = line.match(/#(sec-[a-zA-Z0-9._%-]+)/g);
if (matches) {
sectionIds.push(...matches.map(m => m.substring(1)));
if (!url) {
const section = line.split(' ').find(l => l.includes('#sec'));
if (section) {
url = section.includes('https') ? section : `https://tc39.es/ecma262/${section}`;
}
}
}
}
}
if (sectionIds.length === 0) return;
const name = getName();
// 1. Keep existing .section (backward compat) - uses first URL
if (url) {
path.insertAfter(template.ast(`${name}.section = '${url}';`));
}
// 2. Determine if we can inject mark()
let body: t.BlockStatement | null = null;
if (path.isFunctionDeclaration()) {
body = path.node.body;
} else if (path.isClassMethod() || path.isObjectMethod()) {
body = path.node.body;
} else if (path.isVariableDeclaration()) {
const init = (path.get('declarations.0') as NodePath<t.VariableDeclarator>).get('init');
if (init.isFunctionExpression()) {
body = init.node.body;
} else if (init.isArrowFunctionExpression()) {
body = init.node.body.type === 'BlockStatement' ? init.node.body : null;
}
} else if (path.isExportNamedDeclaration()) {
const declaration = path.node.declaration;
if (declaration?.type === 'FunctionDeclaration') {
body = declaration.body;
}
}
// 3. Inject mark() at start of function body (only for block bodies)
if (body?.type === 'BlockStatement') {
const line = path.node.loc?.start.line ?? 0;
const markCall = template.statement(
`ask262Debug.mark(${JSON.stringify(sectionIds)}, ${JSON.stringify(state.fileRelativePath)}, ${line});`
)();
body.body.unshift(markCall);
state.needed.ask262Debug = true;
}
}
1c. Add Visitors for All Function-like Nodes
return {
visitor: {
Program: {
enter(path, state) {
state.needed = {};
// Capture relative file path from state.filename
const absolutePath = state.filename || '';
state.fileRelativePath = absolutePath.replace(/.*\/(src\/)/, '$1').replace(/\.mts$/, '.mts') || 'unknown.mts';
},
exit(path, state) {
// Existing imports...
if (state.needed.ask262Debug) {
path.unshiftContainer('body', template.ast(`import ask262Debug from '#self/ask262Debug';`));
}
},
},
FunctionDeclaration(path, state) {
addSectionFromComments(path, state, () => path.node.id!.name);
},
VariableDeclaration(path, state) {
const init = path.get('declarations.0.init');
if (init.isFunctionExpression() || init.isArrowFunctionExpression()) {
const id = path.node.declarations[0].id as t.Identifier;
addSectionFromComments(path, state, () => id.name);
}
},
ExportNamedDeclaration(path, state) {
const declaration = path.node.declaration;
if (declaration?.type === 'FunctionDeclaration') {
addSectionFromComments(path, state, () => declaration.id!.name);
}
},
ClassMethod(path, state) {
const key = path.node.key;
if (key.type === 'Identifier') {
addSectionFromComments(path, state, () => key.name);
}
},
ObjectMethod(path, state) {
const key = path.node.key;
if (key.type === 'Identifier') {
addSectionFromComments(path, state, () => key.name);
}
},
// ... rest of existing visitors (CallExpression, ThrowStatement, etc.) unchanged
},
};
1d. Arrow Function Special Handling
The updated addSectionFromComments handles this automatically:
- Arrow with block body
() => { ... }:.section+mark()injected - Arrow with expression body
() => expr:.sectiononly, nomark()
2. engine262/package.json — Add Subpath Import
{
"imports": {
"#self": "./lib/engine262.mjs",
"#self/ask262Debug": "./src/ask262-debug.mts"
}
}
3. engine262/tsconfig.base.json — Add Path Mapping
{
"compilerOptions": {
"paths": {
"#self": ["./lib/engine262.mjs"],
"#self/ask262Debug": ["./src/ask262-debug.mts"]
}
}
}
4. engine262/scripts/rollup.config.mts — Add resolve-self to Main Build
4a. Extend Existing resolve-self Plugin
// Inspector build (existing plugin, modified)
{
name: 'resolve-self',
resolveId(source, _importer, _options) {
if (source === '#self') {
return { id: './engine262.mjs' };
}
if (source.startsWith('#self/')) {
// For inspector build, resolve subpaths to bundled engine262
return { id: './engine262.mjs' }; // All #self/* resolve to main bundle
}
return undefined;
},
}
4b. Add to Main Engine262 Build
{
input: './src/index.mts',
plugins: [
importUnicodeLib(),
(json.default || json)({ compact: true }),
(commonjs.default || commonjs)(),
nodeResolve({ exportConditions: ['rollup'], extensions: ['.mts'] }),
{
name: 'resolve-self',
resolveId(source) {
if (source === '#self') {
return { id: '#self' }; // Mark as internal, bundled
}
if (source === '#self/ask262Debug') {
return { id: './src/ask262-debug.mts' }; // Will be bundled
}
return undefined;
},
},
babel({
...babelOptions,
plugins: [
'./scripts/transform.mts',
['@babel/plugin-proposal-decorators', { 'version': '2023-11' }],
],
}),
// ... dts plugin
],
// ... output config
}
5. engine262/src/index.mts — Add Named Re-export
// Add to existing exports
export { default as ask262Debug, type MarkData } from './ask262-debug.mts';
6. engine262/src/ask262-debug.mts — The Module
Deduplicated data collection. No stack parsing, no node: imports, no decorators.
export interface MarkData {
readonly sectionIds: string[];
readonly fileRelativePath: string;
readonly lineNumber: number;
readonly important: boolean;
}
interface MarkKey {
readonly sectionIds: string;
readonly file: string;
readonly line: number;
}
class Ask262Debug {
marks: MarkData[] = [];
private _importantStack = false;
private _markIndex = new Map<string, number>(); // key -> index in marks
private _makeKey(sectionIds: string[], file: string, line: number): string {
return `${sectionIds.join(',')}|${file}|${line}`;
}
mark(sectionIds: string[], file: string, line: number) {
const key = this._makeKey(sectionIds, file, line);
const existingIndex = this._markIndex.get(key);
if (existingIndex !== undefined) {
// Merge important flag
const existing = this.marks[existingIndex];
if (this._importantStack && !existing.important) {
(this.marks[existingIndex] as any).important = true;
}
return;
}
const newMark: MarkData = {
sectionIds: [...sectionIds], // defensive copy
fileRelativePath: file,
lineNumber: line,
important: this._importantStack,
};
this._markIndex.set(key, this.marks.length);
this.marks.push(newMark);
}
startImportant() {
this._importantStack = true;
}
stopImportant() {
this._importantStack = false;
}
}
const ask262Debug = new Ask262Debug();
export default ask262Debug;
Key Behaviors
| Scenario | Behavior |
|---|---|
Duplicate (sectionIds, file, line) |
Keep one entry, merge important flags (OR logic) |
| Generator resumption | Mark on every .next() call (may create duplicates if same location) |
| Nested functions | Each entry adds its mark (chronological trace) |
7. engine262/scripts/verify-ask262-debug.mts — Verification Script
#!/usr/bin/env node
import engine262 from '../lib/engine262.mjs';
console.log('=== ask262Debug Verification ===\n');
// 1. Check export exists
console.log('1. Checking export...');
if (!engine262.ask262Debug) {
console.error('FAIL: ask262Debug not exported');
process.exit(1);
}
console.log(' ✓ ask262Debug exported\n');
// 2. Run code that hits known spec sections
console.log('2. Executing test code...');
const agent = new engine262.Agent();
const realm = new engine262.ManagedRealm({ agent });
realm.evaluateScript(`
// Array.prototype.every - should hit sec-array.prototype.every
[1, 2, 3].every(x => x > 0);
// Proxy creation - should hit sec-proxycreate or similar
new Proxy({}, {});
`);
// 3. Verify marks were captured
console.log('3. Checking marks...');
const marks = engine262.ask262Debug.marks;
console.log(` Captured ${marks.length} unique marks\n`);
if (marks.length === 0) {
console.error('FAIL: No marks captured');
process.exit(1);
}
// 4. Show sample marks
console.log('4. Sample marks:');
marks.slice(0, 5).forEach((m, i) => {
console.log(` [${i}] ${m.sectionIds.join(', ')} @ ${m.fileRelativePath}:${m.lineNumber}${m.important ? ' [important]' : ''}`);
});
console.log('\n=== All Checks Passed ===');
Key Design Decisions Summary
| Decision | Choice | Rationale |
|---|---|---|
| Use case | AI execution flow analysis | Map runtime to spec sections |
| Data structure | Deduplicated array | Chronological trace with unique entries only |
| Deduplication key | (sectionIds, file, line) |
Same location = same semantic entry |
| Duplicate handling | Merge important flags (OR) |
If any call was important, mark it important |
| Module name | #self/ask262Debug |
Follows existing #self pattern |
| Export style | Named export from engine262 | import { ask262Debug } from '#self' |
| Bundle strategy | Included in engine262.mjs | Single artifact, no external dependency |
| All metadata | From transform (build time) | No runtime stack parsing, no node: imports |
mark() injection |
Auto-injected at start of block body | Developer writes nothing |
| Arrow functions | Block body: .section + mark(), Expression: .section only |
Can't inject statement in expression |
| Multi-section | Array of section IDs per function | Functions can implement multiple spec sections |
.section |
Kept (backward compat) | Used by test262-intrinsics |
| Generators | Mark on every .next() |
Simpler, deduplication handles repeats |
| Nested functions | Mark all | Chronological trace for AI analysis |
important |
User-driven startImportant()/stopImportant() |
Manual annotation of interesting sections |
| Function types | FunctionDeclaration + ClassMethod + ObjectMethod + VariableDeclaration | Covers all URL comment patterns |
| Class/Object method keys | Identifier names only | Simple, covers 99% of cases |
| File paths | Relative to engine262/src/, .mts extension |
Maps to source, not compiled output |
| Line numbers | 1-indexed from Babel AST | Matches source file line numbers |
Verification Steps
- Type checking:
cd engine262 && npx tsc -b . - Linting:
cd engine262 && npm run lint - Build:
cd engine262 && npm run build:engine - Verification:
cd engine262 && node scripts/verify-ask262-debug.mts - Manual inspection: Check that transformed files have
ask262Debug.mark()at start of functions with URL comments - Multi-section test: Find a function with multiple URL comments (e.g.,
ArrayProto_every) and verify all section IDs are in the array
Expected Output from Verification
=== ask262Debug Verification ===
1. Checking export...
✓ ask262Debug exported
2. Executing test code...
3. Checking marks...
Captured 47 unique marks
4. Sample marks:
[0] sec-array.prototype.every @ intrinsics/ArrayPrototypeShared.mts:173
[1] sec-proxycreate @ abstract-ops/proxy-objects.mts:540
...
=== All Checks Passed ===