Is it possible to prevent inlining and tail call optimization?


(Garth Snyder) #1

I'm trying to do some performance assessment with the Time Profiler in Instruments. It's pretty clear that some traces are being scrambled by inlining and/or tail call optimization, to the extent that it's hard to tell where time is really being consumed.

Is there any way to turn these optimizations off globally? I know this would have its own biasing effect on the execution profile, but it seems like those distortions would be easier to deal with than the ones introduced by inlining and tail calls. And of course, I want to keep all other optimizations.

This WWDC 2015 talk and this 2017 article by Jordan Morgan both suggest adding -fno-optimize-sibling-calls to CFLAGS (and by extension, I assume -fno-inline-functions for inlining), but this no longer seems to have any effect. These are Clang flags; was the Swift compiler previously based on Clang and is now separate?


(Joe Groff) #2

Those are Clang driver flags; Swift never implemented them. You can suppress inlining of a function with @inline(never), or more forcibly all optimization of a function with @_optimize(none), but AFAIK the Swift driver does not have flags like those.


(Alexander Momchilov) #3

On a similar note, is there a way to make the optimizer not cull a variable?

e.g. let _ = (0...1_000_000).map { $0 * $0 } would get optimized out because it has no side effects, and there's no usage of the result.

Is there a way to prevent this, without actually using the value in some expensive way, such as printing it?


(Joe Groff) #4

You could write an @_optimize(none) function that takes the value as an argument but does nothing. I believe that will prevent it from getting optimized away.


(Alexander Momchilov) #5

Hmm, I was playing around in god bolt and I couldn't make it behave much differently. The "expensive computation" was eliminated out.


(Cory Benfield) #6

@inline(never) is almost always enough to make this work, as you can see on Godbolt. So long as you pass the result of a dependent computation to an @inline(never) function, the compiler cannot throw that computation away.


(Martin R) #7

Possibly helpful:


(Joe Groff) #8

That's not really guaranteed, though, since while @inline(never) suppresses inlining, it does not suppress other interprocedural optimization. LLVM in particular could notice that the callee does nothing and still remove the call even though it's not inlined. @_optimize(none) AIUI is supposed to correspond to the clang/llvm optnone attribute, which prevents any interprocedural optimization, so it seems like a bug if it does not. @Erik_Eckstein is that accurate?


(Cory Benfield) #9

You're the LLVM expert, so I defer to your knowledge. That said, I've never observed this happening.

The Swift benchmarks use @inline(never), though they have a mysterious comment that says "It's important that this function is in another module than the tests which are using it." Given that there is no cross-module optimisation I have no idea why that's necessary (or even why @inline(never) would be needed in that case), but nevertheless, that's been my reference point for this kind of thing.