From a78009758184f625a405d2289073b9122e2556bc Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Mon, 9 Mar 2026 15:57:29 -0400 Subject: [PATCH] Use source path as debug info source ID instead of hardcoded "0" The compiler was hardcoding source ID as "0" in three places when generating ethdebug/format debug info. Now threads the sourcePath from compilation input through the IR generation state and into the final Format.Program output. - Add sourceId field to Ir.Module and State.Module - IR gen pass accepts sourcePath, defaults to "0" if absent - process.ts code contexts use sourceId instead of module name - program-builder.ts reads sourceId from IR module - Update all test module constructions with sourceId field - Add tests verifying sourceId default and sourcePath threading --- .../bugc/src/evmgen/generation/module.test.ts | 6 ++++ packages/bugc/src/evmgen/program-builder.ts | 2 +- .../bugc/src/ir/analysis/formatter.test.ts | 3 ++ packages/bugc/src/ir/spec/module.ts | 2 ++ packages/bugc/src/irgen/generate/module.ts | 4 +-- packages/bugc/src/irgen/generate/process.ts | 2 +- packages/bugc/src/irgen/generate/state.ts | 1 + packages/bugc/src/irgen/generator.test.ts | 30 +++++++++++++++++++ packages/bugc/src/irgen/generator.ts | 10 +++++-- packages/bugc/src/irgen/pass.ts | 5 ++-- .../src/optimizer/optimizer.property.test.ts | 5 ++++ packages/bugc/src/optimizer/optimizer.ts | 1 + .../optimizer/steps/constant-folding.test.ts | 1 + 13 files changed, 64 insertions(+), 8 deletions(-) diff --git a/packages/bugc/src/evmgen/generation/module.test.ts b/packages/bugc/src/evmgen/generation/module.test.ts index 768192090..fb679742f 100644 --- a/packages/bugc/src/evmgen/generation/module.test.ts +++ b/packages/bugc/src/evmgen/generation/module.test.ts @@ -8,6 +8,7 @@ describe("Module.generate", () => { it("should generate runtime bytecode for module without constructor", () => { const module: Ir.Module = { name: "Test", + sourceId: "test", functions: new Map(), main: { name: "main", @@ -58,6 +59,7 @@ describe("Module.generate", () => { it("should generate deployment bytecode with constructor", () => { const module: Ir.Module = { name: "Test", + sourceId: "test", functions: new Map(), create: { name: "create", @@ -142,6 +144,7 @@ describe("Module.generate", () => { it("should use optimal PUSH opcodes based on value size", () => { const module: Ir.Module = { name: "Test", + sourceId: "test", functions: new Map(), main: { name: "main", @@ -228,6 +231,7 @@ describe("Module.generate", () => { const module: Ir.Module = { name: "LargeContract", + sourceId: "test", functions: new Map(), main: { name: "main", @@ -317,6 +321,7 @@ describe("Module.generate", () => { it("should calculate deployment size correctly with optimal PUSH opcodes", () => { const module: Ir.Module = { name: "Test", + sourceId: "test", functions: new Map(), main: { name: "main", @@ -403,6 +408,7 @@ describe("Module.generate", () => { for (const { value, expectedPushSize } of testCases) { const module: Ir.Module = { name: "Test", + sourceId: "test", functions: new Map(), main: { name: "main", diff --git a/packages/bugc/src/evmgen/program-builder.ts b/packages/bugc/src/evmgen/program-builder.ts index ea42254bb..cf33e2085 100644 --- a/packages/bugc/src/evmgen/program-builder.ts +++ b/packages/bugc/src/evmgen/program-builder.ts @@ -76,7 +76,7 @@ export function buildProgram( const contract: Format.Program.Contract = { name: ir.name, definition: { - source: { id: "0" }, // TODO: Get actual source ID + source: { id: ir.sourceId }, range: ir.loc ?? { offset: 0, length: 0 }, }, }; diff --git a/packages/bugc/src/ir/analysis/formatter.test.ts b/packages/bugc/src/ir/analysis/formatter.test.ts index 4c8a37cb0..d0a1834c3 100644 --- a/packages/bugc/src/ir/analysis/formatter.test.ts +++ b/packages/bugc/src/ir/analysis/formatter.test.ts @@ -6,6 +6,7 @@ describe("IrFormatter", () => { it("should format phi nodes with predecessor labels", () => { const module: Ir.Module = { name: "TestModule", + sourceId: "test", functions: new Map(), main: { name: "main", @@ -143,6 +144,7 @@ describe("IrFormatter", () => { it("should format multiple phi nodes in a block", () => { const module: Ir.Module = { name: "TestModule", + sourceId: "test", functions: new Map(), main: { name: "main", @@ -229,6 +231,7 @@ describe("IrFormatter", () => { it("should show block predecessors when there are phi nodes", () => { const module: Ir.Module = { name: "TestModule", + sourceId: "test", functions: new Map(), main: { name: "main", diff --git a/packages/bugc/src/ir/spec/module.ts b/packages/bugc/src/ir/spec/module.ts index 6d6abc3fc..9d28a6326 100644 --- a/packages/bugc/src/ir/spec/module.ts +++ b/packages/bugc/src/ir/spec/module.ts @@ -9,6 +9,8 @@ import type { Function as IrFunction } from "./function.js"; export interface Module { /** Program name from 'name' declaration */ name: string; + /** Source identifier for debug info */ + sourceId: string; /** User-defined functions */ functions: Map; /** Constructor function (optional, for contract creation) */ diff --git a/packages/bugc/src/irgen/generate/module.ts b/packages/bugc/src/irgen/generate/module.ts index 5f12d8945..dcc41e778 100644 --- a/packages/bugc/src/irgen/generate/module.ts +++ b/packages/bugc/src/irgen/generate/module.ts @@ -88,8 +88,7 @@ export function* buildModule( // Build program-level debug context with storage variables const state: State = yield { type: "peek" }; - const sourceId = "0"; // TODO: Get actual source ID - const variables = collectVariablesWithLocations(state, sourceId); + const variables = collectVariablesWithLocations(state, module_.sourceId); const debugContext = variables.length > 0 ? { variables: variables.map(toVariableContextEntry) } @@ -98,6 +97,7 @@ export function* buildModule( // Return the module, ensuring main exists const result: Ir.Module = { name: module_.name, + sourceId: module_.sourceId, functions: module_.functions, main: module_.main || createEmptyFunction("main"), debugContext, diff --git a/packages/bugc/src/irgen/generate/process.ts b/packages/bugc/src/irgen/generate/process.ts index 51b15ba00..96efbfec5 100644 --- a/packages/bugc/src/irgen/generate/process.ts +++ b/packages/bugc/src/irgen/generate/process.ts @@ -1046,7 +1046,7 @@ export namespace Process { } const { offset, length } = node.loc; - const id = (yield* Process.Modules.current()).name; + const { sourceId: id } = yield* Process.Modules.current(); // Combine code and variables in a single context object // No need for gather since the keys don't conflict diff --git a/packages/bugc/src/irgen/generate/state.ts b/packages/bugc/src/irgen/generate/state.ts index 86d8adf9e..a08356071 100644 --- a/packages/bugc/src/irgen/generate/state.ts +++ b/packages/bugc/src/irgen/generate/state.ts @@ -35,6 +35,7 @@ export namespace State { */ export interface Module { readonly name: string; + readonly sourceId: string; readonly functions: Map; readonly main?: Ir.Function; readonly create?: Ir.Function; diff --git a/packages/bugc/src/irgen/generator.test.ts b/packages/bugc/src/irgen/generator.test.ts index a448bfcae..d4706994d 100644 --- a/packages/bugc/src/irgen/generator.test.ts +++ b/packages/bugc/src/irgen/generator.test.ts @@ -50,6 +50,36 @@ describe("generateModule", () => { expect(ir.main.blocks.size).toBe(1); expect(ir.main.blocks.get("entry")).toBeDefined(); }); + + it("should default sourceId to '0' when no sourcePath", () => { + const ir = buildIR(` + name Test; + storage {} + code {} + `); + expect(ir.sourceId).toBe("0"); + }); + + it("should use sourcePath as sourceId when provided", () => { + const source = ` + name Test; + storage {} + code {} + `; + const parseResult = parse(source); + if (!parseResult.success) throw new Error("Parse failed"); + const typeCheckResult = TypeChecker.checkProgram(parseResult.value); + if (!typeCheckResult.success) { + throw new Error("Type check failed"); + } + const result = generateModule( + parseResult.value, + typeCheckResult.value.types, + "contracts/Test.bug", + ); + if (!result.success) throw new Error("IR gen failed"); + expect(result.value.sourceId).toBe("contracts/Test.bug"); + }); }); describe("expressions", () => { diff --git a/packages/bugc/src/irgen/generator.ts b/packages/bugc/src/irgen/generator.ts index 2393a997d..540f7cc45 100644 --- a/packages/bugc/src/irgen/generator.ts +++ b/packages/bugc/src/irgen/generator.ts @@ -15,9 +15,10 @@ import { buildModule } from "#irgen/generate"; export function generateModule( program: Ast.Program, types: Types, + sourcePath?: string, ): Result { // Create initial state - const initialState = createInitialState(program, types); + const initialState = createInitialState(program, types, sourcePath); // Run the generator const result = Process.run(buildModule(program, types), initialState); @@ -55,13 +56,18 @@ export function generateModule( /** * Create the initial IR generation state */ -function createInitialState(program: Ast.Program, types: Types): State { +function createInitialState( + program: Ast.Program, + types: Types, + sourcePath?: string, +): State { // Create errors array to collect any type resolution errors const errors: IrgenError[] = []; // Create initial module const module: State.Module = { name: program.name, + sourceId: sourcePath ?? "0", functions: new Map(), storageDeclarations: program.storage ?? [], }; diff --git a/packages/bugc/src/irgen/pass.ts b/packages/bugc/src/irgen/pass.ts index fc8728fba..e4ca8a205 100644 --- a/packages/bugc/src/irgen/pass.ts +++ b/packages/bugc/src/irgen/pass.ts @@ -15,14 +15,15 @@ const pass: Pass<{ needs: { ast: Program; types: Types; + sourcePath?: string; }; adds: { ir: Ir.Module; }; error: IrgenError; }> = { - async run({ ast, types }) { - return Result.map(generateModule(ast, types), (ir) => ({ ir })); + async run({ ast, types, sourcePath }) { + return Result.map(generateModule(ast, types, sourcePath), (ir) => ({ ir })); }, }; diff --git a/packages/bugc/src/optimizer/optimizer.property.test.ts b/packages/bugc/src/optimizer/optimizer.property.test.ts index 4435a2383..fa728822f 100644 --- a/packages/bugc/src/optimizer/optimizer.property.test.ts +++ b/packages/bugc/src/optimizer/optimizer.property.test.ts @@ -236,6 +236,7 @@ describe("Optimizer Property Tests", () => { function createModuleWithBinaryOp(a: bigint, b: bigint, op: string): Ir.Module { return { name: "Test", + sourceId: "test", main: { name: "main", parameters: [], @@ -336,6 +337,7 @@ function createModuleWithDeadCode(useFlags: boolean[]): Ir.Module { return { name: "Test", + sourceId: "test", main: { name: "main", parameters: [], @@ -361,6 +363,7 @@ function createModuleWithDeadCode(useFlags: boolean[]): Ir.Module { function createModuleWithDuplicateExpressions(a: bigint, b: bigint): Ir.Module { return { name: "Test", + sourceId: "test", main: { name: "main", parameters: [], @@ -513,6 +516,7 @@ function createModuleWithMergeableBlocks( return { name: "Test", + sourceId: "test", main: { name: "main", parameters: [], entry: "entry", blocks }, functions: new Map(), }; @@ -522,6 +526,7 @@ function createModuleWithMergeableBlocks( function generateRandomModule(): fc.Arbitrary { return fc.record({ name: fc.constant("Test"), + sourceId: fc.constant("test"), main: generateRandomFunction(), functions: fc.constant(new Map()), }); diff --git a/packages/bugc/src/optimizer/optimizer.ts b/packages/bugc/src/optimizer/optimizer.ts index 0e287a8ea..5b32a83cc 100644 --- a/packages/bugc/src/optimizer/optimizer.ts +++ b/packages/bugc/src/optimizer/optimizer.ts @@ -202,6 +202,7 @@ export abstract class BaseOptimizationStep implements OptimizationStep { return { name: module.name, + sourceId: module.sourceId, functions: clonedFunctions, create: clonedCreate, main: clonedMain, diff --git a/packages/bugc/src/optimizer/steps/constant-folding.test.ts b/packages/bugc/src/optimizer/steps/constant-folding.test.ts index 1124ac46b..ddc26a576 100644 --- a/packages/bugc/src/optimizer/steps/constant-folding.test.ts +++ b/packages/bugc/src/optimizer/steps/constant-folding.test.ts @@ -20,6 +20,7 @@ describe("ConstantFoldingStep", () => { return { name: "test", + sourceId: "test", functions: new Map(), main: { name: "main",