Bug 15671 – The compiler should take into account inline pragmas when inlining

Status
NEW
Severity
enhancement
Priority
P4
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2016-02-10T22:28:17Z
Last change time
2024-12-13T18:46:54Z
Keywords
rejects-valid
Assigned to
No Owner
Creator
Steven Schveighoffer
Moved to GitHub: dmd#19098 →

Comments

Comment #0 by schveiguy — 2016-02-10T22:28:17Z
Given the following code: import std.random; shared int x; void longFunc() { version(good) x = uniform!int(); x = uniform!int(); x = uniform!int(); x = uniform!int(); x = uniform!int(); x = uniform!int(); x = uniform!int(); } void foo(alias func)() { pragma(inline, true); func(); x = uniform!int(); } void main() { foo!longFunc(); } The compiler fails if passed the -inline command line switch. However, it succeeds if -inline -version=good is passed Here is what happens: 1. The compiler inlines longFunc into foo (perhaps even calls to uniform as well) 2. The compiler tries to inline foo into main, but fails because of the inlined call to longFunc. 3. Since pragma(inline, true) is specified for foo, this fails to compile. However, the compiler could succeed inlining foo into main by not inlining longFunc into foo first. This is demonstrated by the version=good compilation (one extra call to uniform prevents longFunc from being inlined)
Comment #1 by bugzilla — 2020-06-06T06:19:36Z
The compiler is using a heuristic of "if the amount of code in a function is above a certain threshold, then it is not inlined." The idea is that the overhead of a function call becomes small when the size of the function is large. Where the code came from is irrelevant.
Comment #2 by schveiguy — 2020-06-06T15:30:40Z
First, to update the issue, you need a lot less uniform calls (probably the underlying uniform call is more code than it used to be), update longFunc to this: void longFunc() { version(good) x = uniform!int(); x = uniform!int(); } Second, the problem here is not the inliner heuristic. There is no reason that the compiler cannot change: foo!longFunc(); into: longFunc(); x = uniform!int(); In other words, inline foo, but NOT inline func (and all the stuff it calls). The point of forced inlining is to change a common call situation into directly written code. What this bug report is saying is that the inlining heuristic should take into account that an outer portion should be inlined even if all the underlying code cannot be. If I change this to make longFunc opaque, it works: pragma(mangle, "longFunc") void longFunc_impl() { x = uniform!int(); } void foo(alias func)() { pragma(inline, true); func(); x = uniform!int(); } extern(C) void longFunc(); void main() { foo!longFunc(); } This is the behavior I would expect -- inline *this function* even if you can't inline the calls it makes.
Comment #3 by tobias — 2020-06-09T06:24:50Z
LLVM has flatten [1] next to inline. I think what you want at this point is the behaviour of flatten. [1] https://clang.llvm.org/docs/AttributeReference.html#flatten
Comment #4 by bugzilla — 2020-06-09T10:24:20Z
(In reply to Steven Schveighoffer from comment #2) > [...] I'm sorry, I find your post to be completely confusing.
Comment #5 by stanislav.blinov — 2020-06-09T12:44:05Z
(In reply to Walter Bright from comment #1) > The compiler is using a heuristic of "if the amount of code in a function is > above a certain threshold, then it is not inlined." The idea is that the > overhead of a function call becomes small when the size of the function is > large. Where the code came from is irrelevant. void foo(alias func)() { pragma(inline, true); func(); x = uniform!int(); } The "amount of code" in `foo` (which is explicitly marked by a pragma) is two function calls and a store. After inlining, the void main() { foo!longFunc(); } should become, at the very least void main() { longFunc(); x = uniform!int(); } Yet it fails, presumably because the compiler also attempts to inline `longFunc` and/or `uniform` in `foo` before inlining `foo` itself.
Comment #6 by schveiguy — 2020-06-09T14:03:22Z
Yes, Stanislav's description is correct. The problem with the way pragma(inline) is implemented today is that it's very brittle if you are depending on functions that might change the heuristic in the future. Consider a function like: int foo(int val) { // a bunch of complicated code. ... } In normal code, I enable inlining and whether the compiler inlines this or not, I don't care. Now, it turns out that a very frequent use of foo among users is: auto x = someString.length ? foo(someString.to!int) : 0; So I want to capture that as a wrapping function: int fooStr(string val) { import std.conv : to; return val.length ? foo(val.to!int) : 0; } But I don't want to change the performance of the original code, so I want the compiler to inline fooStr. I still don't care and want to leave it up to the compiler whether foo itself can be inlined. I just want to provide a nice way to avoid having to import std.conv, and write that conditional call every time. I don't care if foo is inlined, but I DO want fooStr to be inlined. Without this functionality I'm not exactly sure why pragma(inline) even exists.
Comment #7 by robert.schadek — 2024-12-13T18:46:54Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/19098 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB