diff --git a/packages/bugc/CHANGELOG.md b/packages/bugc/CHANGELOG.md index 9714910944..5eee6e0cc1 100644 --- a/packages/bugc/CHANGELOG.md +++ b/packages/bugc/CHANGELOG.md @@ -202,6 +202,11 @@ support. Changes to the specification itself are tracked in the root - `.length` of an array literal, as in `[1, 2, 3].length`, is now the number of elements. Before, at every optimization level, it was 32 ([#344]). +- A local initialized with a `bytes` literal + (`let data: bytes = 0x0001…;`) now holds the literal's bytes. Before, at + every optimization level, its data was the memory address of a copy of + the literal, so its debug pointer read zeros and then that address + ([#345]). ## 0.1.0-preview.1 — 2026-10-03 @@ -256,3 +261,4 @@ First publication. [#341]: https://github.com/ethdebug/format/pull/341 [#343]: https://github.com/ethdebug/format/pull/343 [#344]: https://github.com/ethdebug/format/pull/344 +[#345]: https://github.com/ethdebug/format/pull/345 diff --git a/packages/bugc/src/evmgen/bytes-literal.test.ts b/packages/bugc/src/evmgen/bytes-literal.test.ts new file mode 100644 index 0000000000..c8299ba1ca --- /dev/null +++ b/packages/bugc/src/evmgen/bytes-literal.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from "vitest"; + +import { executeProgram } from "#test/evm/behavioral"; + +// 40 bytes, 0x01 to 0x28: too long for a bytesN, so `bytes` +const literal = Array.from({ length: 40 }, (_, i) => + (i + 1).toString(16).padStart(2, "0"), +).join(""); + +describe("a bytes literal", () => { + for (const level of [0, 1, 2, 3] as const) { + it(`should hold its bytes and length (level ${level})`, async () => { + const source = `name BytesLiteral; +storage { [0] n: uint256; [1] head: bytes32; [2] tail: bytes32; } +code { + let data: bytes = 0x${literal}; + n = data.length; + head = data[0:32] as bytes32; + tail = data[8:40] as bytes32; +}`; + + const result = await executeProgram(source, { + calldata: "", + optimizationLevel: level, + }); + + expect(result.callSuccess).toBe(true); + expect(await result.getStorage(0n)).toBe(40n); + expect(await result.getStorage(1n)).toBe( + BigInt("0x" + literal.slice(0, 64)), + ); + expect(await result.getStorage(2n)).toBe( + BigInt("0x" + literal.slice(16, 80)), + ); + }); + } +}); diff --git a/packages/bugc/src/evmgen/debug/local-variables.soundness.test.ts b/packages/bugc/src/evmgen/debug/local-variables.soundness.test.ts index d3d6d750e3..951c0bf3a8 100644 --- a/packages/bugc/src/evmgen/debug/local-variables.soundness.test.ts +++ b/packages/bugc/src/evmgen/debug/local-variables.soundness.test.ts @@ -960,6 +960,25 @@ code { }, }, }, + { + name: "bytes literal", + source: `name BytesLit; +storage { [0] r: uint256; [1] s: uint256; } +create { s = 1; } +code { + let data: bytes = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223; + if (s > 0) { r = data.length; } + r = r + 1; +}`, + locals: { + data: { + shape: { kind: "bytes" }, + values: [ + "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223", + ], + }, + }, + }, { name: "bytes slice", source: `name Slice; diff --git a/packages/bugc/src/irgen/generate/statements/declare.ts b/packages/bugc/src/irgen/generate/statements/declare.ts index 9242c1e075..4680a16a4f 100644 --- a/packages/bugc/src/irgen/generate/statements/declare.ts +++ b/packages/bugc/src/irgen/generate/statements/declare.ts @@ -58,34 +58,9 @@ function* buildVariableDeclaration( if (needsMemoryAllocation) { // For types that need memory allocation - // For reference types, calculate size needed - let sizeValue: Ir.Value; - - // Check if we have an initializer to determine size - if (decl.initializer) { - // For hex literals, calculate actual size needed - if ( - Ast.Expression.isLiteral(decl.initializer) && - Ast.Expression.Literal.isHex(decl.initializer) - ) { - const hexLiteral = decl.initializer as Ast.Expression.Literal; - const hexValue = hexLiteral.value.startsWith("0x") - ? hexLiteral.value.slice(2) - : hexLiteral.value; - const byteSize = hexValue.length / 2; - // Add 32 bytes for length prefix - sizeValue = Ir.Value.constant( - BigInt(byteSize + 32), - Ir.Type.Scalar.uint256, - ); - } else { - // Default size for other initializers - sizeValue = Ir.Value.constant(64n, Ir.Type.Scalar.uint256); - } - } else { - // Default size when no initializer - sizeValue = Ir.Value.constant(64n, Ir.Type.Scalar.uint256); - } + // A placeholder allocation, for a local declared without an + // initializer; with one, the local's value is the initializer's + const sizeValue = Ir.Value.constant(64n, Ir.Type.Scalar.uint256); // Allocate memory const allocTemp = yield* Process.Variables.newTemp(); @@ -98,14 +73,9 @@ function* buildVariableDeclaration( } as Ir.Instruction); // Declare the SSA variable and directly use the allocTemp as its value - // With an initializer other than a hex literal, the local's value - // is the initializer's (below), not this allocation. - const builtLater = - !!decl.initializer && - !( - Ast.Expression.isLiteral(decl.initializer) && - Ast.Expression.Literal.isHex(decl.initializer) - ); + // With an initializer, the local's value is the initializer's + // (below), not this allocation. + const builtLater = !!decl.initializer; yield* Process.Variables.declareWithExistingTemp( decl.name, irType, @@ -120,71 +90,24 @@ function* buildVariableDeclaration( kind: "rvalue", }); - // For reference types, we need to handle initialization - // Check the initializer type to determine how to store it - if (!builtLater) { - const hexLiteral = decl.initializer as Ast.Expression.Literal; - if (Ast.Expression.Literal.isHex(hexLiteral)) { - const hexValue = hexLiteral.value.startsWith("0x") - ? hexLiteral.value.slice(2) - : hexLiteral.value; - const byteSize = hexValue.length / 2; - - // Store length at the beginning - yield* Process.Instructions.emit({ - kind: "write", - location: "memory", - offset: Ir.Value.temp(allocTemp, Ir.Type.Scalar.uint256), - length: Ir.Value.constant(32n, Ir.Type.Scalar.uint256), - value: Ir.Value.constant(BigInt(byteSize), Ir.Type.Scalar.uint256), - operationDebug: yield* Process.Debug.forAstNode(decl), - } as Ir.Instruction.Write); - - // Store the actual bytes data after the length - const dataOffsetTemp = yield* Process.Variables.newTemp(); - yield* Process.Instructions.emit({ - kind: "binary", - op: "add", - left: Ir.Value.temp(allocTemp, Ir.Type.Scalar.uint256), - right: Ir.Value.constant(32n, Ir.Type.Scalar.uint256), - dest: dataOffsetTemp, - operationDebug: yield* Process.Debug.forAstNode(decl), - } as Ir.Instruction.BinaryOp); - - yield* Process.Instructions.emit({ - kind: "write", - location: "memory", - offset: Ir.Value.temp(dataOffsetTemp, Ir.Type.Scalar.uint256), - length: Ir.Value.constant(BigInt(byteSize), Ir.Type.Scalar.uint256), - value: value, - operationDebug: yield* Process.Debug.forAstNode(decl), - } as Ir.Instruction.Write); - } + // The value is already a reference to memory (a literal, an + // array or a slice): the local refers to it + if (value.kind === "temp") { + yield* Process.Variables.updateSsaToExistingTemp( + decl.name, + value.id, + irType, + ); } else { - // For string literals, slice expressions and other bytes operations, - // the value is already a reference to memory - // We need to copy the slice result to the new allocation - // This is a simplified version - a full implementation would need to - // handle different cases more carefully - - // If the value is a temp, update the SSA variable to point to it - if (value.kind === "temp") { - yield* Process.Variables.updateSsaToExistingTemp( - decl.name, - value.id, - irType, - ); - } else { - // For non-temp values, we need to copy it - yield* Process.Instructions.emit({ - kind: "binary", - op: "add", - left: value, - right: Ir.Value.constant(0n, Ir.Type.Scalar.uint256), - dest: allocTemp, - operationDebug: yield* Process.Debug.forAstNode(decl), - } as Ir.Instruction.BinaryOp); - } + // For non-temp values, we need to copy it + yield* Process.Instructions.emit({ + kind: "binary", + op: "add", + left: value, + right: Ir.Value.constant(0n, Ir.Type.Scalar.uint256), + dest: allocTemp, + operationDebug: yield* Process.Debug.forAstNode(decl), + } as Ir.Instruction.BinaryOp); } } } else {