Skip to content

Commit 2c36ef8

Browse files
authored
bugc: add debug contexts to all unmapped bytecodes (#190)
* bugc: add debug contexts to all remaining unmapped bytecodes Thread remark/code contexts through all compiler-generated instructions that previously lacked debug info: - Free memory pointer initialization (remark) - Return value spill after call continuation (call expr source range) - STOP guard between main and user functions (remark) - Function prologue MSTORE for param storage (thread existing remark) - Function prologue return PC save sequence (thread existing remark) - Deployment wrapper CODECOPY+RETURN (remark) All 82 instructions across runtime and create programs now carry debug contexts (previously 22 were unmapped). * bugc: add code contexts with source ranges to compiler-generated instructions Add source location info (loc, sourceId) to Ir.Function so EVM codegen can build code contexts for compiler-generated instructions. Instructions that map to a source location now use gather contexts combining both a remark (for debugger tooling) and a code context (for source highlighting): - Free memory pointer init → code block / create block range - Function prologue (param stores, return PC save) → function decl range - STOP guard → code block range Deployment wrapper remains remark-only (no corresponding source). Return value spill already had correct source mapping (call expr).
1 parent 8864511 commit 2c36ef8

5 files changed

Lines changed: 165 additions & 37 deletions

File tree

packages/bugc/src/evmgen/generation/block.ts

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Block-level code generation
33
*/
44

5+
import type * as Ast from "#ast";
56
import type * as Format from "@ethdebug/format";
67
import * as Ir from "#ir";
78
import type { Stack } from "#evm";
@@ -47,9 +48,13 @@ export function generate<S extends Stack>(
4748

4849
// Initialize memory for first block
4950
if (isFirstBlock) {
50-
// Always initialize the free memory pointer for consistency
51-
// This ensures dynamic allocations start after static ones
52-
result = result.then(initializeMemory(state.memory.nextStaticOffset));
51+
const sourceInfo =
52+
func?.sourceId && func?.loc
53+
? { sourceId: func.sourceId, loc: func.loc }
54+
: undefined;
55+
result = result.then(
56+
initializeMemory(state.memory.nextStaticOffset, sourceInfo),
57+
);
5358
}
5459

5560
// Set JUMPDEST for non-first blocks
@@ -104,6 +109,7 @@ export function generate<S extends Stack>(
104109
predBlock.terminator.dest
105110
) {
106111
const destId = predBlock.terminator.dest;
112+
const spillDebug = predBlock.terminator.operationDebug;
107113
result = result.then(annotateTop(destId)).then((s) => {
108114
const allocation = s.memory.allocations[destId];
109115
if (!allocation) return s;
@@ -112,16 +118,25 @@ export function generate<S extends Stack>(
112118
...s,
113119
instructions: [
114120
...s.instructions,
115-
{ mnemonic: "DUP1" as const, opcode: 0x80 },
121+
{
122+
mnemonic: "DUP1" as const,
123+
opcode: 0x80,
124+
debug: spillDebug,
125+
},
116126
{
117127
mnemonic: "PUSH2" as const,
118128
opcode: 0x61,
119129
immediates: [
120130
(allocation.offset >> 8) & 0xff,
121131
allocation.offset & 0xff,
122132
],
133+
debug: spillDebug,
134+
},
135+
{
136+
mnemonic: "MSTORE" as const,
137+
opcode: 0x52,
138+
debug: spillDebug,
123139
},
124-
{ mnemonic: "MSTORE" as const, opcode: 0x52 },
125140
],
126141
};
127142
});
@@ -205,21 +220,45 @@ function generatePhi<S extends Stack>(
205220

206221
/**
207222
* Initialize the free memory pointer at runtime
208-
* Sets the value at 0x40 to the next available memory location after static allocations
223+
* Sets the value at 0x40 to the next available memory location
224+
* after static allocations
209225
*/
210226
function initializeMemory<S extends Stack>(
211227
nextStaticOffset: number,
228+
sourceInfo?: { sourceId: string; loc: Ast.SourceLocation },
212229
): Transition<S, S> {
213230
const { PUSHn, MSTORE } = operations;
214231

215-
return (
216-
pipe<S>()
217-
// Push the static offset value (the value to store)
218-
.then(PUSHn(BigInt(nextStaticOffset)), { as: "value" })
219-
// Push the free memory pointer location (0x40) (the offset)
220-
.then(PUSHn(BigInt(Memory.regions.FREE_MEMORY_POINTER)), { as: "offset" })
221-
// Store the initial free pointer (expects [value, offset] on stack)
222-
.then(MSTORE())
223-
.done()
224-
);
232+
const debug = sourceInfo
233+
? {
234+
context: {
235+
gather: [
236+
{ remark: "initialize free memory pointer" },
237+
{
238+
code: {
239+
source: { id: sourceInfo.sourceId },
240+
range: sourceInfo.loc,
241+
},
242+
},
243+
],
244+
} as Format.Program.Context,
245+
}
246+
: {
247+
context: {
248+
remark: "initialize free memory pointer",
249+
} as Format.Program.Context,
250+
};
251+
252+
return pipe<S>()
253+
.then(PUSHn(BigInt(nextStaticOffset), { debug }), {
254+
as: "value",
255+
})
256+
.then(
257+
PUSHn(BigInt(Memory.regions.FREE_MEMORY_POINTER), {
258+
debug,
259+
}),
260+
{ as: "offset" },
261+
)
262+
.then(MSTORE({ debug }))
263+
.done();
225264
}

packages/bugc/src/evmgen/generation/function.ts

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,28 @@ function generatePrologue<S extends Stack>(
7171
// Return PC is already in memory at 0x60 (stored by caller)
7272
// Pop and store each arg from argN down to arg0
7373

74-
const prologueDebug = {
75-
context: {
76-
remark: `prologue: store ${params.length} parameter(s) to memory`,
77-
},
78-
};
74+
const prologueDebug =
75+
func.sourceId && func.loc
76+
? {
77+
context: {
78+
gather: [
79+
{
80+
remark: `prologue: store ${params.length} parameter(s) to memory`,
81+
},
82+
{
83+
code: {
84+
source: { id: func.sourceId },
85+
range: func.loc,
86+
},
87+
},
88+
],
89+
} as Format.Program.Context,
90+
}
91+
: {
92+
context: {
93+
remark: `prologue: store ${params.length} parameter(s) to memory`,
94+
} as Format.Program.Context,
95+
};
7996

8097
for (let i = params.length - 1; i >= 0; i--) {
8198
const param = params[i];
@@ -104,7 +121,11 @@ function generatePrologue<S extends Stack>(
104121
...currentState,
105122
instructions: [
106123
...currentState.instructions,
107-
{ mnemonic: "MSTORE", opcode: 0x52 },
124+
{
125+
mnemonic: "MSTORE",
126+
opcode: 0x52,
127+
debug: prologueDebug,
128+
},
108129
],
109130
};
110131
}
@@ -113,11 +134,28 @@ function generatePrologue<S extends Stack>(
113134
// so nested function calls don't clobber it.
114135
const savedPcOffset = currentState.memory.savedReturnPcOffset;
115136
if (savedPcOffset !== undefined) {
116-
const savePcDebug = {
117-
context: {
118-
remark: `prologue: save return PC to 0x${savedPcOffset.toString(16)}`,
119-
},
120-
};
137+
const savePcDebug =
138+
func.sourceId && func.loc
139+
? {
140+
context: {
141+
gather: [
142+
{
143+
remark: `prologue: save return PC to 0x${savedPcOffset.toString(16)}`,
144+
},
145+
{
146+
code: {
147+
source: { id: func.sourceId },
148+
range: func.loc,
149+
},
150+
},
151+
],
152+
} as Format.Program.Context,
153+
}
154+
: {
155+
context: {
156+
remark: `prologue: save return PC to 0x${savedPcOffset.toString(16)}`,
157+
} as Format.Program.Context,
158+
};
121159
const highByte = (savedPcOffset >> 8) & 0xff;
122160
const lowByte = savedPcOffset & 0xff;
123161
currentState = {
@@ -130,13 +168,22 @@ function generatePrologue<S extends Stack>(
130168
immediates: [0x60],
131169
debug: savePcDebug,
132170
},
133-
{ mnemonic: "MLOAD", opcode: 0x51 },
171+
{
172+
mnemonic: "MLOAD",
173+
opcode: 0x51,
174+
debug: savePcDebug,
175+
},
134176
{
135177
mnemonic: "PUSH2",
136178
opcode: 0x61,
137179
immediates: [highByte, lowByte],
180+
debug: savePcDebug,
181+
},
182+
{
183+
mnemonic: "MSTORE",
184+
opcode: 0x52,
185+
debug: savePcDebug,
138186
},
139-
{ mnemonic: "MSTORE", opcode: 0x52 },
140187
],
141188
};
142189
}

packages/bugc/src/evmgen/generation/module.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,37 @@ export function generate(
105105
// Insert STOP between main and user functions to prevent
106106
// fall-through when the main function's last block omits
107107
// STOP (the isLastBlock optimization).
108+
const stopGuardDebug =
109+
module.main.sourceId && module.main.loc
110+
? {
111+
context: {
112+
gather: [
113+
{
114+
remark: "guard: prevent fall-through into functions",
115+
},
116+
{
117+
code: {
118+
source: { id: module.main.sourceId },
119+
range: module.main.loc,
120+
},
121+
},
122+
],
123+
},
124+
}
125+
: {
126+
context: {
127+
remark: "guard: prevent fall-through into functions",
128+
},
129+
};
108130
const stopGuard: Evm.Instruction[] =
109131
patchedFunctions.length > 0
110-
? [{ mnemonic: "STOP" as const, opcode: 0x00 }]
132+
? [
133+
{
134+
mnemonic: "STOP" as const,
135+
opcode: 0x00,
136+
debug: stopGuardDebug,
137+
},
138+
]
111139
: [];
112140
const stopGuardBytes: number[] = patchedFunctions.length > 0 ? [0x00] : [];
113141

@@ -243,13 +271,19 @@ function buildDeploymentInstructions(
243271
function deploymentTransition(runtimeOffset: bigint, runtimeLength: bigint) {
244272
const { PUSHn, CODECOPY, RETURN } = operations;
245273

274+
const debug = {
275+
context: {
276+
remark: "deployment: copy runtime bytecode and return",
277+
},
278+
};
279+
246280
return pipe()
247-
.then(PUSHn(runtimeLength), { as: "size" })
248-
.then(PUSHn(runtimeOffset), { as: "offset" })
249-
.then(PUSHn(0n), { as: "destOffset" })
250-
.then(CODECOPY())
251-
.then(PUSHn(runtimeLength), { as: "size" })
252-
.then(PUSHn(0n), { as: "offset" })
253-
.then(RETURN())
281+
.then(PUSHn(runtimeLength, { debug }), { as: "size" })
282+
.then(PUSHn(runtimeOffset, { debug }), { as: "offset" })
283+
.then(PUSHn(0n, { debug }), { as: "destOffset" })
284+
.then(CODECOPY({ debug }))
285+
.then(PUSHn(runtimeLength, { debug }), { as: "size" })
286+
.then(PUSHn(0n, { debug }), { as: "offset" })
287+
.then(RETURN({ debug }))
254288
.done();
255289
}

packages/bugc/src/ir/spec/function.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export interface Function {
1717
blocks: Map<string, Block>;
1818
/** SSA variable metadata mapping temp IDs to original variables */
1919
ssaVariables?: Map<string, Function.SsaVariable>;
20+
/** Source location of the function body */
21+
loc?: Ast.SourceLocation;
22+
/** Source identifier for debug info */
23+
sourceId?: string;
2024
}
2125

2226
export namespace Function {

packages/bugc/src/irgen/generate/function.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,16 @@ export function* buildFunction(
4646
// Collect SSA variable metadata
4747
const ssaVariables = yield* Process.Functions.collectSsaMetadata();
4848

49+
const module_ = yield* Process.Modules.current();
50+
4951
const function_: Ir.Function = {
5052
name,
5153
parameters: params,
5254
entry: "entry",
5355
blocks,
5456
ssaVariables: ssaVariables.size > 0 ? ssaVariables : undefined,
57+
loc: body.loc ?? undefined,
58+
sourceId: module_.sourceId,
5559
};
5660

5761
return function_;

0 commit comments

Comments
 (0)