Skip to content

Commit c1dc7e1

Browse files
committed
bugc: compile a void function call as a statement
irgen converted a call's type to an IR type before it checked for a void function, whose call type is the type checker's "void function" failure, so fromBugType threw. Check first and convert only a call with a result.
1 parent 8714233 commit c1dc7e1

6 files changed

Lines changed: 135 additions & 14 deletions

File tree

‎packages/bugc/CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ support. Changes to the specification itself are tracked in the root
7272

7373
### Fixed
7474

75+
- A call to a function with no return type now compiles as a statement,
76+
as in `bump();`, at every optimization level. The function's `return`
77+
context has no `data`. Before, IR generation failed with "Cannot convert
78+
type with kind fail to IR type" ([#PR]).
7579
- At optimization levels 2 and 3, an inlined function that starts with an
7680
inlined call, or returns one's result, now has its own `invoke` and
7781
`return` contexts, around the inner call's. Before, its `invoke` (or its
@@ -277,3 +281,4 @@ First publication.
277281
[#345]: https://github.com/ethdebug/format/pull/345
278282
[#347]: https://github.com/ethdebug/format/pull/347
279283
[#349]: https://github.com/ethdebug/format/pull/349
284+
[#PR]: https://github.com/ethdebug/format/pull/PR

‎packages/bugc/src/evmgen/behavioral.test.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,59 @@ code {
429429
}
430430
}
431431

432+
// A function with no return type, called as a statement. `n` (7)
433+
// keeps the calls from folding; `out` records the effects.
434+
const voidCalls = {
435+
"a void call": [`function bump() { out = out + n; };`, `bump();`, 7n],
436+
"a void call with arguments and an early return": [
437+
`function put(x: uint256, y: uint256) {
438+
if (x > y) { out = x; return; }
439+
out = y;
440+
};`,
441+
`put(n, 3); out = out * 10; put(1, out);`,
442+
70n,
443+
],
444+
"void calls in a loop": [
445+
`function bump() { out = out + n; };`,
446+
`for (let i = 0; i < 3; i = i + 1) { bump(); }`,
447+
21n,
448+
],
449+
"a void call from a void function": [
450+
`function bump() { out = out + n; };
451+
function twice() { bump(); bump(); };`,
452+
`twice();`,
453+
14n,
454+
],
455+
"a local across a void call": [
456+
`function bump() { out = out + n; };`,
457+
`let y = n + 1; bump(); out = out + y;`,
458+
15n,
459+
],
460+
} as const;
461+
462+
for (const [name, [define, body, expected]] of Object.entries(voidCalls)) {
463+
for (const level of [0, 1, 2, 3] as const) {
464+
it(`should make ${name} (level ${level})`, async () => {
465+
const source = `name VoidCalls;
466+
define {
467+
${define}
468+
}
469+
storage { [0] n: uint256; [1] out: uint256; }
470+
create { n = 7; }
471+
code {
472+
${body}
473+
}`;
474+
const result = await executeProgram(source, {
475+
calldata: "",
476+
optimizationLevel: level,
477+
});
478+
479+
expect(result.callSuccess).toBe(true);
480+
expect(await result.getStorage(1n)).toBe(expected);
481+
});
482+
}
483+
}
484+
432485
// Each parameter gets a distinct weight, so a misordered argument
433486
// changes the result. `n` (7) keeps the arguments from folding.
434487
for (const count of [6, 7, 8] as const) {

‎packages/bugc/src/evmgen/call-contexts.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,38 @@ code {
259259
expect(ret.data).toBeDefined();
260260
},
261261
);
262+
263+
it(
264+
"should emit invoke and data-free return contexts " +
265+
"for a void function called as a statement",
266+
async () => {
267+
const program = await compileProgram(`name VoidStatement;
268+
define {
269+
function bump() { count = count + 1; };
270+
}
271+
storage { [0] count: uint256; }
272+
create { count = 0; }
273+
code { bump(); }`);
274+
275+
const invokeJumps = findInstructionsWithContext(
276+
program,
277+
"JUMP",
278+
Context.isInvoke,
279+
);
280+
expect(invokeJumps).toHaveLength(1);
281+
expect(invokeJumps[0].context.invoke.identifier).toBe("bump");
282+
283+
const returnJumps = findInstructionsWithContext(
284+
program,
285+
"JUMP",
286+
Context.isReturn,
287+
);
288+
expect(returnJumps).toHaveLength(1);
289+
const { return: ret } = returnJumps[0].context;
290+
expect(ret.identifier).toBe("bump");
291+
expect(ret.data).toBeUndefined();
292+
},
293+
);
262294
});
263295

264296
describe("nested function calls", () => {

‎packages/bugc/src/evmgen/call-timing.test.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ create { r = 0; }
5555
code { r = fact(4); }`,
5656
returns: [1n, 2n, 6n, 24n],
5757
},
58+
{
59+
name: "void calls",
60+
source: `name Void;
61+
define {
62+
function bump() { r = r + 1; };
63+
function twice() { bump(); bump(); };
64+
}
65+
storage { [0] r: uint256; }
66+
create { r = 0; }
67+
code { twice(); }`,
68+
// A void exit has no data to read
69+
returns: [undefined, undefined, undefined],
70+
},
5871
];
5972

6073
/** A context's invoke and return leaves, outside inlined bodies */
@@ -102,7 +115,7 @@ describe("call frame timing", () => {
102115
// JUMP and the callee's JUMPDEST both carry the invoke, on
103116
// consecutive steps: count it once.
104117
const frames: { name: string; step: number }[] = [];
105-
const read: bigint[] = [];
118+
const read: (bigint | undefined)[] = [];
106119
let calls = 0;
107120
for (let i = 0; i < steps.length; i++) {
108121
if (i > 0) {
@@ -122,6 +135,8 @@ describe("call frame timing", () => {
122135
const cursor = await dereference(ret.data.pointer, { state });
123136
const view = await cursor.view(state);
124137
read.push((await view.read(view.regions[0])).asUint());
138+
} else {
139+
read.push(undefined);
125140
}
126141
} else if (invoke !== undefined) {
127142
const top = frames[frames.length - 1];

‎packages/bugc/src/evmgen/debug/local-variables.soundness.test.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,28 @@ code {
753753
k: { values: [7n] },
754754
},
755755
},
756+
{
757+
name: "a void call",
758+
source: `name VoidCall;
759+
define {
760+
function put(a: uint256) {
761+
let b = a * 3;
762+
r = b;
763+
};
764+
}
765+
storage { [0] r: uint256; [1] s: uint256; }
766+
create { s = 5; }
767+
code {
768+
let m = s + 1;
769+
put(m);
770+
s = m + r;
771+
}`,
772+
locals: {
773+
a: { values: [6n] },
774+
b: { values: [18n] },
775+
m: { values: [6n] },
776+
},
777+
},
756778
{
757779
name: "one-armed if in a function",
758780
source: `name PickFn;

‎packages/bugc/src/irgen/generate/expressions/call.ts‎

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,13 @@ export const makeBuildCall = (
8181
argValues.push(yield* buildExpression(arg, { kind: "rvalue" }));
8282
}
8383

84-
// Generate call terminator and split block
85-
const irType = fromBugType(callType);
86-
let dest: string | undefined;
87-
88-
// Only create a destination if the function returns a value
89-
// Check if it's a void function by checking if the type is a failure with "void function" message
84+
// A void function's call has the type checker's "void
85+
// function" failure as its type, which has no IR type: only a
86+
// call with a result gets a destination
9087
const isVoidFunction =
91-
Type.isFailure(callType) &&
92-
(callType as Type.Failure).reason === "void function";
93-
94-
if (!isVoidFunction) {
95-
dest = yield* Process.Variables.newTemp();
96-
}
88+
Type.isFailure(callType) && callType.reason === "void function";
89+
const irType = isVoidFunction ? undefined : fromBugType(callType);
90+
const dest = irType ? yield* Process.Variables.newTemp() : undefined;
9791

9892
// Create a continuation block for after the call
9993
const continuationBlockId = yield* Process.Blocks.create("call_cont");
@@ -112,7 +106,7 @@ export const makeBuildCall = (
112106
yield* Process.Blocks.switchTo(continuationBlockId);
113107

114108
// Return the result value or a dummy value for void functions
115-
if (dest) {
109+
if (dest && irType) {
116110
return Ir.Value.temp(dest, irType);
117111
}
118112
// Void function - return a dummy value

0 commit comments

Comments
 (0)