@@ -134,15 +134,21 @@ function countCallSites(program: Format.Program): CallSiteCounts {
134134}
135135
136136/**
137- * Every function listed has a `return` on its exit JUMP(s), no
138- * other function has one, and no JUMPDEST carries a `return` (the
139- * continuation carries only the call site's `code`). A function
140- * has one exit per return epilogue, so the count follows its
141- * return statements, not its call sites.
137+ * Each function listed has exactly that many exit JUMPs carrying
138+ * a `return`, no other function has one, and no JUMPDEST carries a
139+ * `return` (the continuation carries only the call site's `code`).
140+ * A function has one exit per `return` terminator in its IR, so the
141+ * count follows its return statements, not its call sites. It can
142+ * be one more than the source shows: an if/else whose branches
143+ * both return leaves an empty merge block that gets an implicit
144+ * return, until the optimizer drops it.
142145 */
143- function expectExits ( counts : CallSiteCounts , functions : string [ ] ) : void {
146+ function expectExits (
147+ counts : CallSiteCounts ,
148+ exits : Record < string , number > ,
149+ ) : void {
144150 expect ( counts . returnJumpdest ) . toEqual ( { } ) ;
145- expect ( Object . keys ( counts . returnExit ) . sort ( ) ) . toEqual ( [ ... functions ] . sort ( ) ) ;
151+ expect ( counts . returnExit ) . toEqual ( exits ) ;
146152}
147153
148154describe ( "optimizer preserves invoke/return contexts" , ( ) => {
@@ -176,7 +182,7 @@ code { r = add(10, 20); }`;
176182 // exit JUMP — all naming "add".
177183 expect ( counts . invokeJump ) . toEqual ( { add : 1 } ) ;
178184 expect ( counts . invokeJumpdest ) . toEqual ( { add : 1 } ) ;
179- expectExits ( counts , [ " add" ] ) ;
185+ expectExits ( counts , { add : 1 } ) ;
180186 }
181187
182188 // Behavior is still correct.
@@ -217,7 +223,7 @@ code { r = add(2 + 3, 4 * 5); }`;
217223 } else {
218224 expect ( counts . invokeJump ) . toEqual ( { add : 1 } ) ;
219225 expect ( counts . invokeJumpdest ) . toEqual ( { add : 1 } ) ;
220- expectExits ( counts , [ " add" ] ) ;
226+ expectExits ( counts , { add : 1 } ) ;
221227 }
222228
223229 const result = await executeProgram ( source , {
@@ -262,7 +268,7 @@ code {
262268 } else {
263269 expect ( counts . invokeJump ) . toEqual ( { dbl : 2 } ) ;
264270 expect ( counts . invokeJumpdest ) . toEqual ( { dbl : 1 } ) ;
265- expectExits ( counts , [ " dbl" ] ) ;
271+ expectExits ( counts , { dbl : 1 } ) ;
266272 }
267273
268274 const result = await executeProgram ( source , {
@@ -302,7 +308,10 @@ code { r = fact(5); }`;
302308 // fact's exit JUMPs carry its returns.
303309 expect ( counts . invokeJump ) . toEqual ( { fact : 2 } ) ;
304310 expect ( counts . invokeJumpdest ) . toEqual ( { fact : 1 } ) ;
305- expectExits ( counts , [ "fact" ] ) ;
311+ // Two return statements, plus the implicit return of the
312+ // empty merge block after the if/else (no predecessors),
313+ // which the optimizer drops from level 2.
314+ expectExits ( counts , { fact : level < 2 ? 3 : 2 } ) ;
306315
307316 const result = await executeProgram ( source , {
308317 calldata : "" ,
@@ -348,7 +357,10 @@ code { r = isEven(4); }`;
348357 isEven : 1 ,
349358 isOdd : 1 ,
350359 } ) ;
351- expectExits ( counts , [ "isEven" , "isOdd" ] ) ;
360+ // As in `fact`: two returns each, plus an unreachable
361+ // implicit return each until level 2.
362+ const exits = level < 2 ? 3 : 2 ;
363+ expectExits ( counts , { isEven : exits , isOdd : exits } ) ;
352364
353365 const result = await executeProgram ( source , {
354366 calldata : "" ,
@@ -399,7 +411,7 @@ code { r = addThree(1, 2, 3); }`;
399411 addThree : 1 ,
400412 add : 1 ,
401413 } ) ;
402- expectExits ( counts , [ " addThree" , " add" ] ) ;
414+ expectExits ( counts , { addThree : 1 , add : 1 } ) ;
403415 }
404416
405417 const result = await executeProgram ( source , {
@@ -438,7 +450,10 @@ code { r = check(3, 4); }`;
438450
439451 expect ( counts . invokeJump ) . toEqual ( { check : 1 } ) ;
440452 expect ( counts . invokeJumpdest ) . toEqual ( { check : 1 } ) ;
441- expectExits ( counts , [ "check" ] ) ;
453+ // Three return statements, in three blocks: the two
454+ // `return 42` blocks stay apart, and no if/else leaves an
455+ // implicit return.
456+ expectExits ( counts , { check : 3 } ) ;
442457
443458 const result = await executeProgram ( source , {
444459 calldata : "" ,
@@ -485,7 +500,9 @@ code { r = count(0, 5); }`;
485500 // self-call. succ is still a separate function call.
486501 expect ( counts . invokeJump ) . toEqual ( { count : 2 , succ : 1 } ) ;
487502 expect ( counts . invokeJumpdest ) . toEqual ( { count : 1 , succ : 1 } ) ;
488- expectExits ( counts , [ "count" , "succ" ] ) ;
503+ // count: two returns plus an unreachable implicit return
504+ // after the if/else.
505+ expectExits ( counts , { count : 3 , succ : 1 } ) ;
489506 // At level 1 there are no TCO back-edge JUMPs.
490507 expect ( counts . returnJump ) . toEqual ( { } ) ;
491508 } ) ;
@@ -512,7 +529,7 @@ code { r = count(0, 5); }`;
512529
513530 // count's and succ's exit JUMPs carry return
514531 // contexts as usual.
515- expectExits ( counts , [ " count" , " succ" ] ) ;
532+ expectExits ( counts , { count : 1 , succ : 1 } ) ;
516533
517534 // The TCO back-edge JUMP additionally carries a
518535 // return context for `count` (the previous
0 commit comments