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.
(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