1

If I have the following functions:

fn a() -> ! {
    b()
}

fn b() -> ! {
    loop {}
}

Does Rust optimize such calls to simple jumps?

I have looked at a Playground to show the assembly, but a seems to just callq the b function.

Is this always the case?

DrunkCoder
  • 379
  • 2
  • 9
  • As you cna see in the playground output, the assembly was generated without optimisations: `[unoptimized + debuginfo]` – Svetlin Zarev Aug 31 '21 at 09:10
  • When compiling the playground example with optimizations, both `a()` and `b()` get inlined into main, and the assembly code simply becomes `.LBB5_1: jmp .LBB5_1`. – Sven Marnach Aug 31 '21 at 09:13
  • 2
    Note that you rarely get guarantees what exactly the optimizer is going to do. I recommend not to care unless you have an actual performance problem. – Sven Marnach Aug 31 '21 at 09:15
  • 2
    Why would you expect this optmization only for tail calls, by the way? – Sven Marnach Aug 31 '21 at 09:15
  • `.LBB0_1: jmp .LBB0_1` => https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=1fd2857df2dcdfe4de41cb911a12e9e7 – Stargateur Aug 31 '21 at 09:26
  • @SvenMarnach cause not tail call need stack ? – Stargateur Aug 31 '21 at 09:28
  • @Stargateur A diverging function never returns, regardless of whether it's a tail call or not. – Sven Marnach Aug 31 '21 at 09:41
  • With the `loop`, optimizations reduce it to `.LBB5_1: jmp .LBB5_1`. But if I redefine `b` to recursively call itself, all the jumps are replaced by `callq`s. Seems like a missed opportunity for optimization - you really should just jump to a diverging function instead of calling it. – DrunkCoder Aug 31 '21 at 11:25

1 Answers1

0

First, you can tell the compiler to inline a function using #[inline]: Technically, this does not guarantee inlining, but it should work in reasonable situations. I would recommend using this.

Second, as noted in the comments, you have to do an optimised build to see such inlining. Maybe a better tool for this would be Compiler Explorer. Here is a slight modification of your example (notice the -O flag in compiler options). As you can see, everything is inlined into one infinite loop, and the compiler can even eliminate some work done in the loop if it can prove that it is useless.

Overall, unless you use #[inline], the result will depend on the contents of a and b. So I would advise to create some kind of minimal-viable-implementation of what you are trying to achieve with a and b, and then test it in Compiler Explorer.

daemontus
  • 1,047
  • 10
  • 17
  • What about mutually-recursive functions such that a calls b and b calls a both in the tail? – Fifnmar Mar 15 '23 at 13:54
  • 1
    As always, depends on the compiler settings/version/etc... so it's best to check your specific use case. In general, simple cases of mutual recursion are inlined automatically (see https://godbolt.org/z/Pd7en83q1). But this will probably stop working at some level of function complexity. It's hard to tell exactly what that level of complexity is though. – daemontus Mar 16 '23 at 21:20