Skip to content

Commit f04ed54

Browse files
committed
Add calldata, returndata, code, and transient storage support to EVM codegen
The read/write instruction handlers in storage.ts previously only supported storage and memory locations. This adds full support for: - calldata reads via CALLDATALOAD (with shift+mask for partial reads) - returndata reads via RETURNDATACOPY to scratch memory + MLOAD - code reads via CODECOPY to scratch memory + MLOAD - transient storage reads via TLOAD - transient storage writes via TSTORE Includes tests covering all new read/write paths.
1 parent 728482f commit f04ed54

File tree

2 files changed

+467
-113
lines changed

2 files changed

+467
-113
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import { describe, it, expect } from "vitest";
2+
3+
import * as Ir from "#ir";
4+
import { Memory, Layout } from "#evmgen/analysis";
5+
6+
import { generate } from "../function.js";
7+
8+
/**
9+
* Helper to generate bytecode for a function with given
10+
* instructions and return the mnemonic sequence.
11+
*/
12+
function mnemonicsFor(
13+
instructions: Ir.Instruction[],
14+
allocations: Record<string, { offset: number; size: number }> = {},
15+
): string[] {
16+
const func: Ir.Function = {
17+
name: "test",
18+
parameters: [],
19+
entry: "entry",
20+
blocks: new Map([
21+
[
22+
"entry",
23+
{
24+
id: "entry",
25+
phis: [],
26+
instructions,
27+
terminator: { kind: "return", operationDebug: {} },
28+
predecessors: new Set(),
29+
debug: {},
30+
} as Ir.Block,
31+
],
32+
]),
33+
};
34+
35+
const memory: Memory.Function.Info = {
36+
allocations,
37+
nextStaticOffset: 0x80,
38+
};
39+
40+
const layout: Layout.Function.Info = {
41+
order: ["entry"],
42+
offsets: new Map(),
43+
};
44+
45+
const { instructions: evmInstructions } = generate(func, memory, layout);
46+
47+
return evmInstructions.map((i) => i.mnemonic);
48+
}
49+
50+
describe("generateRead", () => {
51+
describe("calldata reads", () => {
52+
it("should use CALLDATALOAD for full 32-byte read", () => {
53+
const mnemonics = mnemonicsFor([
54+
{
55+
kind: "read",
56+
location: "calldata",
57+
offset: {
58+
kind: "const",
59+
value: 0n,
60+
type: Ir.Type.Scalar.uint256,
61+
},
62+
length: {
63+
kind: "const",
64+
value: 32n,
65+
type: Ir.Type.Scalar.uint256,
66+
},
67+
type: Ir.Type.Scalar.uint256,
68+
dest: "%1",
69+
operationDebug: {},
70+
},
71+
]);
72+
73+
expect(mnemonics).toContain("CALLDATALOAD");
74+
expect(mnemonics).not.toContain("CALLDATACOPY");
75+
});
76+
77+
it("should use CALLDATALOAD + shift/mask for partial read", () => {
78+
const mnemonics = mnemonicsFor([
79+
{
80+
kind: "read",
81+
location: "calldata",
82+
offset: {
83+
kind: "const",
84+
value: 4n,
85+
type: Ir.Type.Scalar.uint256,
86+
},
87+
length: {
88+
kind: "const",
89+
value: 20n,
90+
type: Ir.Type.Scalar.uint256,
91+
},
92+
type: Ir.Type.Scalar.address,
93+
dest: "%1",
94+
operationDebug: {},
95+
},
96+
]);
97+
98+
expect(mnemonics).toContain("CALLDATALOAD");
99+
expect(mnemonics).toContain("SHR");
100+
expect(mnemonics).toContain("AND");
101+
});
102+
});
103+
104+
describe("returndata reads", () => {
105+
it("should use RETURNDATACOPY + MLOAD", () => {
106+
const mnemonics = mnemonicsFor([
107+
{
108+
kind: "read",
109+
location: "returndata",
110+
offset: {
111+
kind: "const",
112+
value: 0n,
113+
type: Ir.Type.Scalar.uint256,
114+
},
115+
length: {
116+
kind: "const",
117+
value: 32n,
118+
type: Ir.Type.Scalar.uint256,
119+
},
120+
type: Ir.Type.Scalar.uint256,
121+
dest: "%1",
122+
operationDebug: {},
123+
},
124+
]);
125+
126+
expect(mnemonics).toContain("RETURNDATACOPY");
127+
expect(mnemonics).toContain("MLOAD");
128+
// Should zero scratch memory first
129+
expect(mnemonics).toContain("MSTORE");
130+
});
131+
});
132+
133+
describe("code reads", () => {
134+
it("should use CODECOPY + MLOAD", () => {
135+
const mnemonics = mnemonicsFor([
136+
{
137+
kind: "read",
138+
location: "code",
139+
offset: {
140+
kind: "const",
141+
value: 0n,
142+
type: Ir.Type.Scalar.uint256,
143+
},
144+
length: {
145+
kind: "const",
146+
value: 32n,
147+
type: Ir.Type.Scalar.uint256,
148+
},
149+
type: Ir.Type.Scalar.uint256,
150+
dest: "%1",
151+
operationDebug: {},
152+
},
153+
]);
154+
155+
expect(mnemonics).toContain("CODECOPY");
156+
expect(mnemonics).toContain("MLOAD");
157+
});
158+
});
159+
160+
describe("transient storage reads", () => {
161+
it("should use TLOAD", () => {
162+
const mnemonics = mnemonicsFor([
163+
{
164+
kind: "read",
165+
location: "transient",
166+
slot: {
167+
kind: "const",
168+
value: 0n,
169+
type: Ir.Type.Scalar.uint256,
170+
},
171+
type: Ir.Type.Scalar.uint256,
172+
dest: "%1",
173+
operationDebug: {},
174+
},
175+
]);
176+
177+
expect(mnemonics).toContain("TLOAD");
178+
});
179+
});
180+
});
181+
182+
describe("generateWrite", () => {
183+
describe("transient storage writes", () => {
184+
it("should use TSTORE", () => {
185+
const mnemonics = mnemonicsFor([
186+
{
187+
kind: "write",
188+
location: "transient",
189+
slot: {
190+
kind: "const",
191+
value: 0n,
192+
type: Ir.Type.Scalar.uint256,
193+
},
194+
value: {
195+
kind: "const",
196+
value: 42n,
197+
type: Ir.Type.Scalar.uint256,
198+
},
199+
operationDebug: {},
200+
},
201+
]);
202+
203+
expect(mnemonics).toContain("TSTORE");
204+
});
205+
});
206+
});

0 commit comments

Comments
 (0)