Skip to content

Commit eea5ef7

Browse files
committed
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 b3fc3a1 commit eea5ef7

File tree

5 files changed

+105
-24
lines changed

5 files changed

+105
-24
lines changed

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

Lines changed: 30 additions & 9 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
@@ -215,18 +220,34 @@ function generatePhi<S extends Stack>(
215220

216221
/**
217222
* Initialize the free memory pointer at runtime
218-
* 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
219225
*/
220226
function initializeMemory<S extends Stack>(
221227
nextStaticOffset: number,
228+
sourceInfo?: { sourceId: string; loc: Ast.SourceLocation },
222229
): Transition<S, S> {
223230
const { PUSHn, MSTORE } = operations;
224231

225-
const debug = {
226-
context: {
227-
remark: "initialize free memory pointer",
228-
},
229-
};
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+
};
230251

231252
return pipe<S>()
232253
.then(PUSHn(BigInt(nextStaticOffset), { debug }), {

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

Lines changed: 44 additions & 10 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];
@@ -117,11 +134,28 @@ function generatePrologue<S extends Stack>(
117134
// so nested function calls don't clobber it.
118135
const savedPcOffset = currentState.memory.savedReturnPcOffset;
119136
if (savedPcOffset !== undefined) {
120-
const savePcDebug = {
121-
context: {
122-
remark: `prologue: save return PC to 0x${savedPcOffset.toString(16)}`,
123-
},
124-
};
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+
};
125159
const highByte = (savedPcOffset >> 8) & 0xff;
126160
const lowByte = savedPcOffset & 0xff;
127161
currentState = {

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,35 @@ 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
110132
? [
111133
{
112134
mnemonic: "STOP" as const,
113135
opcode: 0x00,
114-
debug: {
115-
context: {
116-
remark: "guard: prevent fall-through into functions",
117-
},
118-
},
136+
debug: stopGuardDebug,
119137
},
120138
]
121139
: [];

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)