Yes, that is what I meant by my edit (both sideEffects() and check() are inlined in main).
The question is about the fact that in the first example, val is only ever used by check() which is a noop in release. Thus, the call to sideEffects() should be subject to dead-code removal
Edit and conclusion:
I guess that the compiler greedily emits all inlined code to topmost level, then applies dead-code removal which in this case only removes the return 0 but should do what I expect otherwise (e.g. option 1)
It would be subject to dead-code removal if the compiler can prove that the call to sideEffects() has no "side effects" , but you appropriately named function does have one side effect (the print statement).
function calls are evaluated inside to outside, so saveTheWorld() is evaluated before logResult(...).
If logResult(...) is a no-op in release builds, the compiler will optimize the code by simply discarding the return value of saveTheWorld(), but it won't remove the call to saveTheWorld() as that would change the semantics of the program, and the compiler will only perform optimizations that does not change the semantics of the code.
This example will be more clear on how compiler behaves if after print/in return statement was some intensive work -- summing to 1 billion, for example. In that case you will have significant difference in running time of optimised and debug versions.
Assertions are preserved through inlining, so if a function gets inlined it will have assertion behavior according to the build settings of where it's ultimately compiled into. If you call the non-inline entry point, it will behave as built with the original module according to its build settings.
I was only really using assertions to keep the example simple, but since you brought up modules:
How good are the @inlinable heuristics? As observed above, dead-code elimination can produce faster code even ignoring the cost of the function call itself which may be quite beneficial even when crossing package boundaries.
Is @inlinable @inline(__always) respected across modules?
It will always get inlined. Whether it can determine expensive is dead or not and eliminate the computation is a lot more nuanced. If anything on the path of computing expensive looks like it might have other side effects, we're not going to eliminate that side effecting code.
[nitpicky caveat] A function call should always be inlined. Other uses of a function may not be (if you’re passing the function as a value to another function that is not inlined, there’s nothing the compiler can do).