Skip to content

Commit 75761b8

Browse files
NullVoxPopuliclaude
andcommitted
Decouple VM debug symbols/names from opcodes.ts
`opcodes.ts` previously imported `@glimmer/debug` (DebugLogger, VmSnapshot, debugOp / describeOp, opcodeMetadata, frag, etc.) at the top level and assembled the per-opcode `debugBefore`/`debugAfter` hooks inline in `AppendOpcodes`'s constructor — gated by `LOCAL_DEBUG`, so dead in production, but the imports still pulled the heavy `@glimmer/debug` graph into the bundle. Same registration pattern as the DebugRenderTree split: opcodes.ts exposes `registerDebugOpcodeSetup(setup)`; the heavy hook implementation moved to `opcodes-debug-setup.ts`, which calls the registry on import. `externs(vm)` now also requires the hooks to be registered (returns `undefined` otherwise) so dev builds that don't opt in skip the debug path entirely instead of crashing on a non-null assertion. Production hello-world holds at 134.12 KB / 42.90 KB gzip (`LOCAL_DEBUG` already eliminated the hooks there); the analysis bundle drops the `@glimmer/debug` files entirely. Verified: lint clean (after `pnpm lint:fix`), type-checks pass, hello-world builds, classic v2-app-template `pnpm test` 1/1 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 1f71ded commit 75761b8

2 files changed

Lines changed: 135 additions & 121 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { DebugOp, SomeDisassembledOperand } from '@glimmer/debug/lib/debug';
2+
import { DebugLogger } from '@glimmer/debug/lib/render/logger';
3+
import { debugOp, describeOp, describeOpcode } from '@glimmer/debug/lib/debug';
4+
import { frag } from '@glimmer/debug/lib/render/fragment';
5+
import { opcodeMetadata } from '@glimmer/debug/lib/opcode-metadata';
6+
import { recordStackSize } from '@glimmer/debug/lib/stack-check';
7+
import { VmSnapshot } from '@glimmer/debug/lib/vm/snapshot';
8+
import type { DebugVmSnapshot, Dict, Maybe, RuntimeOp } from '@glimmer/interfaces';
9+
import { unwrap } from '@glimmer/debug-util/lib/platform-utils';
10+
import { LOCAL_TRACE_LOGGING } from '@glimmer/local-debug-flags';
11+
import { LOCAL_LOGGER } from '@glimmer/util';
12+
import { $pc, $ra, $s0, $s1, $sp, $t0, $t1, $v0 } from '@glimmer/vm/lib/registers';
13+
14+
import type { AppendOpcodes, DebugState } from './opcodes';
15+
import { registerDebugOpcodeSetup } from './opcodes';
16+
17+
registerDebugOpcodeSetup((opcodes: AppendOpcodes): void => {
18+
opcodes.debugBefore = (debug: DebugVmSnapshot, opcode: RuntimeOp): DebugState => {
19+
let opcodeSnapshot = {
20+
type: opcode.type,
21+
size: opcode.size,
22+
isMachine: opcode.isMachine,
23+
} as const;
24+
25+
let snapshot = new VmSnapshot(opcodeSnapshot, debug);
26+
let params: Maybe<Dict<SomeDisassembledOperand>> = undefined;
27+
let op: DebugOp | undefined = undefined;
28+
let closeGroup: (() => void) | undefined;
29+
30+
if (LOCAL_TRACE_LOGGING) {
31+
const logger = DebugLogger.configured();
32+
33+
let pos = debug.registers[$pc] - opcode.size;
34+
35+
op = debugOp(debug.context.program, opcode, debug.template);
36+
37+
closeGroup = logger
38+
.group(frag`${pos}. ${describeOp(opcode, debug.context.program, debug.template)}`)
39+
.expanded();
40+
41+
let debugParams = [];
42+
for (let [name, param] of Object.entries(op.params)) {
43+
const value = param.value;
44+
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
45+
debugParams.push(name, '=', value);
46+
}
47+
}
48+
LOCAL_LOGGER.debug(...debugParams);
49+
}
50+
51+
recordStackSize(debug.registers[$sp]);
52+
return {
53+
op,
54+
closeGroup,
55+
params,
56+
opcode: opcodeSnapshot,
57+
debug,
58+
snapshot,
59+
};
60+
};
61+
62+
opcodes.debugAfter = (postSnapshot: DebugVmSnapshot, pre: DebugState) => {
63+
let post = new VmSnapshot(pre.opcode, postSnapshot);
64+
let diff = pre.snapshot.diff(post);
65+
let {
66+
opcode: { type },
67+
} = pre;
68+
69+
let sp = diff.registers[$sp];
70+
71+
let meta = opcodeMetadata(type);
72+
let actualChange = sp.after - sp.before;
73+
if (
74+
meta &&
75+
meta.check !== false &&
76+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
77+
typeof meta.stackChange! === 'number' &&
78+
meta.stackChange !== actualChange
79+
) {
80+
throw new Error(
81+
`Error in ${pre.op?.name}:\n\n${pre.debug.registers[$pc]}. ${
82+
pre.op ? describeOpcode(pre.op.name, pre.params) : unwrap(opcodeMetadata(type)).name
83+
}\n\nStack changed by ${actualChange}, expected ${meta.stackChange}`
84+
);
85+
}
86+
87+
if (LOCAL_TRACE_LOGGING) {
88+
const logger = DebugLogger.configured();
89+
90+
logger.log(diff.registers[$pc].describe());
91+
logger.log(diff.registers[$ra].describe());
92+
logger.log(diff.registers[$s0].describe());
93+
logger.log(diff.registers[$s1].describe());
94+
logger.log(diff.registers[$t0].describe());
95+
logger.log(diff.registers[$t1].describe());
96+
logger.log(diff.registers[$v0].describe());
97+
logger.log(diff.stack.describe());
98+
logger.log(diff.destructors.describe());
99+
logger.log(diff.scope.describe());
100+
101+
if (diff.constructing.didChange || diff.blocks.change) {
102+
const done = logger.group(`tree construction`).expanded();
103+
try {
104+
logger.log(diff.constructing.describe());
105+
logger.log(diff.blocks.describe());
106+
logger.log(diff.cursors.describe());
107+
} finally {
108+
done();
109+
}
110+
}
111+
112+
pre.closeGroup?.();
113+
}
114+
};
115+
});

packages/@glimmer/runtime/lib/opcodes.ts

Lines changed: 20 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { DebugOp, SomeDisassembledOperand } from '@glimmer/debug/lib/debug'
22
import type {
33
DebugVmSnapshot,
44
Dict,
5-
Maybe,
65
Nullable,
76
Optional,
87
RuntimeOp,
@@ -11,21 +10,21 @@ import type {
1110
VmOp,
1211
} from '@glimmer/interfaces';
1312
import { VM_SYSCALL_SIZE } from '@glimmer/constants/lib/syscall-ops';
14-
import { DebugLogger } from '@glimmer/debug/lib/render/logger';
15-
import { debugOp, describeOp, describeOpcode } from '@glimmer/debug/lib/debug';
16-
import { frag } from '@glimmer/debug/lib/render/fragment';
17-
import { opcodeMetadata } from '@glimmer/debug/lib/opcode-metadata';
18-
import { recordStackSize } from '@glimmer/debug/lib/stack-check';
19-
import { VmSnapshot } from '@glimmer/debug/lib/vm/snapshot';
13+
import type { VmSnapshot } from '@glimmer/debug/lib/vm/snapshot';
2014
import { dev, unwrap } from '@glimmer/debug-util/lib/platform-utils';
2115
import assert from '@glimmer/debug-util/lib/assert';
22-
import { LOCAL_DEBUG, LOCAL_TRACE_LOGGING } from '@glimmer/local-debug-flags';
23-
import { LOCAL_LOGGER } from '@glimmer/util';
24-
import { $pc, $ra, $s0, $s1, $sp, $t0, $t1, $v0 } from '@glimmer/vm/lib/registers';
16+
import { LOCAL_DEBUG } from '@glimmer/local-debug-flags';
2517

2618
import type { LowLevelVM, VM } from './vm';
2719
import type { Externs } from './vm/low-level';
2820

21+
type DebugOpcodeSetup = (opcodes: AppendOpcodes) => void;
22+
let debugOpcodeSetup: DebugOpcodeSetup | null = null;
23+
24+
export function registerDebugOpcodeSetup(setup: DebugOpcodeSetup): void {
25+
debugOpcodeSetup = setup;
26+
}
27+
2928
export interface OpcodeJSON {
3029
type: number | string;
3130
guid?: Nullable<number>;
@@ -69,104 +68,8 @@ export class AppendOpcodes {
6968
declare debugAfter?: (debug: DebugVmSnapshot, pre: DebugState) => void;
7069

7170
constructor() {
72-
if (LOCAL_DEBUG) {
73-
this.debugBefore = (debug: DebugVmSnapshot, opcode: RuntimeOp): DebugState => {
74-
let opcodeSnapshot = {
75-
type: opcode.type,
76-
size: opcode.size,
77-
isMachine: opcode.isMachine,
78-
} as const;
79-
80-
let snapshot = new VmSnapshot(opcodeSnapshot, debug);
81-
let params: Maybe<Dict<SomeDisassembledOperand>> = undefined;
82-
let op: DebugOp | undefined = undefined;
83-
let closeGroup: (() => void) | undefined;
84-
85-
if (LOCAL_TRACE_LOGGING) {
86-
const logger = DebugLogger.configured();
87-
88-
let pos = debug.registers[$pc] - opcode.size;
89-
90-
op = debugOp(debug.context.program, opcode, debug.template);
91-
92-
closeGroup = logger
93-
.group(frag`${pos}. ${describeOp(opcode, debug.context.program, debug.template)}`)
94-
.expanded();
95-
96-
let debugParams = [];
97-
for (let [name, param] of Object.entries(op.params)) {
98-
const value = param.value;
99-
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
100-
debugParams.push(name, '=', value);
101-
}
102-
}
103-
LOCAL_LOGGER.debug(...debugParams);
104-
}
105-
106-
recordStackSize(debug.registers[$sp]);
107-
return {
108-
op,
109-
closeGroup,
110-
params,
111-
opcode: opcodeSnapshot,
112-
debug,
113-
snapshot,
114-
};
115-
};
116-
117-
this.debugAfter = (postSnapshot: DebugVmSnapshot, pre: DebugState) => {
118-
let post = new VmSnapshot(pre.opcode, postSnapshot);
119-
let diff = pre.snapshot.diff(post);
120-
let {
121-
opcode: { type },
122-
} = pre;
123-
124-
let sp = diff.registers[$sp];
125-
126-
let meta = opcodeMetadata(type);
127-
let actualChange = sp.after - sp.before;
128-
if (
129-
meta &&
130-
meta.check !== false &&
131-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
132-
typeof meta.stackChange! === 'number' &&
133-
meta.stackChange !== actualChange
134-
) {
135-
throw new Error(
136-
`Error in ${pre.op?.name}:\n\n${pre.debug.registers[$pc]}. ${
137-
pre.op ? describeOpcode(pre.op.name, pre.params) : unwrap(opcodeMetadata(type)).name
138-
}\n\nStack changed by ${actualChange}, expected ${meta.stackChange}`
139-
);
140-
}
141-
142-
if (LOCAL_TRACE_LOGGING) {
143-
const logger = DebugLogger.configured();
144-
145-
logger.log(diff.registers[$pc].describe());
146-
logger.log(diff.registers[$ra].describe());
147-
logger.log(diff.registers[$s0].describe());
148-
logger.log(diff.registers[$s1].describe());
149-
logger.log(diff.registers[$t0].describe());
150-
logger.log(diff.registers[$t1].describe());
151-
logger.log(diff.registers[$v0].describe());
152-
logger.log(diff.stack.describe());
153-
logger.log(diff.destructors.describe());
154-
logger.log(diff.scope.describe());
155-
156-
if (diff.constructing.didChange || diff.blocks.change) {
157-
const done = logger.group(`tree construction`).expanded();
158-
try {
159-
logger.log(diff.constructing.describe());
160-
logger.log(diff.blocks.describe());
161-
logger.log(diff.cursors.describe());
162-
} finally {
163-
done();
164-
}
165-
}
166-
167-
pre.closeGroup?.();
168-
}
169-
};
71+
if (LOCAL_DEBUG && debugOpcodeSetup) {
72+
debugOpcodeSetup(this);
17073
}
17174
}
17275

@@ -203,19 +106,15 @@ export class AppendOpcodes {
203106
}
204107

205108
export function externs(vm: VM): Externs | undefined {
206-
return LOCAL_DEBUG
207-
? {
208-
debugBefore: (opcode: RuntimeOp): DebugState => {
209-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
210-
return APPEND_OPCODES.debugBefore!(dev(vm.debug), opcode);
211-
},
212-
213-
debugAfter: (state: DebugState): void => {
214-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
215-
APPEND_OPCODES.debugAfter!(dev(vm.debug), state);
216-
},
217-
}
218-
: undefined;
109+
if (!LOCAL_DEBUG || !APPEND_OPCODES.debugBefore || !APPEND_OPCODES.debugAfter) {
110+
return undefined;
111+
}
112+
let debugBefore = APPEND_OPCODES.debugBefore;
113+
let debugAfter = APPEND_OPCODES.debugAfter;
114+
return {
115+
debugBefore: (opcode: RuntimeOp): DebugState => debugBefore(dev(vm.debug), opcode),
116+
debugAfter: (state: DebugState): void => debugAfter(dev(vm.debug), state),
117+
};
219118
}
220119

221120
export const APPEND_OPCODES = new AppendOpcodes();

0 commit comments

Comments
 (0)