Hacker News new | ask | show | jobs
by uecker 18 days ago
It is trivial to see that the compiler could do inlining also in this case. Your modification does not "break" inlining in the sense of making it impossible. In fact, just making the vtables "const" is enough and it does it: https://godbolt.org/z/oYv47xx8G Or adding "inline" would also do it. (But this not why your argument is wrong.)

The compiler chooses to not inline in your example. And this is the point: The compiler makes inlining decisions based on some heuristics exactly because inlining is not always an advantage (even if it is in toy examples), otherwise it would do it far more often. Implementing type-erasure gives it the freedom to do this while monomorphization hardcodes it. It takes away this freedom. You can get the same result as monomorphization by forcing certain inlining or function-cloning decisions, but the reverse is much harder: you can not automatically deduplicate the already expanded function. (in theory yes, in practice no)

The argument that for a language with type-erasure the inlining heuristics of this specific C compiler may not be ideal, is rather irrelevant. (but interprocedural value propagation is rather smart in principle)

1 comments

My issue isn't that the modification made it impossible to inline, more so how trivial it was: I just added a different type. Generally, optimizations are disabled not because the compiler finds them inneficent, but because it lacks enough contextual information/intelligence to safely add it. So similar to what you said, in theory, the compiler can optimize type-erasure just like monomorphism, but, in practice, it lacks info to do so. Either by programmer accident, language/compiler limitations, or both.

Also, monomorphization doesn't actually take the compiler freedom away (on its own) to optimize. Similar to how type-erasure can be optimized to behave like monomorphization through devirtualization, monomorphized calls can be virtualized (which Swift shows). The problem is that it takes away transparency from the programmer. It also changes the semantics of the call behind the scene (type-erasure is strictly pass-by-pointers, monomorphism is not). That wouldn't work well for a low-level language.

But your example did not show that the compiler lacks the contextual information. My argument is exactly that in all cases where one can do monomorphization, this contextual information is trivially available because it simply a small part of what can be done with interprocedural constant-propagation anyhow. I think this is easy to see.

I am not commenting on your Gish Gallop of new arguments.

How did it not lack of contextual info? By adding "const", you provided more info to the compiler, and it decided it was safe to inline, despite no actual behavior change. My point is, despite the possibility, these optimizations are easy to disable by mistake.

Also, there's no gish-gallop. You said the compiler can optimize type-erasure like monomorphization, but not monomorphization like type-erasure, and I said that's not true: the compiler can, in fact, do that. I simply addressed your argument. The rest simply elaborates on why languages why might not do it.

No, you did not address my argument. You need to show that it is impossible (or hard) to do function cloning when using interprocedural constant-propagation in a situation where monomorphism does this. But nothing you said contradicts this and the fact that a C compiler may sometimes decide to inline or not a function is completely irrelevant to this question.

In fact, it was exactly my argument that it is an advantage of type-erasure that the compiler has more freedom. An example where the compiler does not specialize a function just demonstrates this flexibility.

I feel like you are attacking a strawman here. My point isn't that it's impossible to optimize, but such optimizations are fickle and easy to break by programmer error and require adequate language expressivity (although low-level languages generally provide that).

You say that the compiler made the decision to not inline it because it judged it to be the most efficient, yet, by adding the "const", thus adding more info, it did inline it. Why did the heuristics change here, even though the logic remained the same? I argue it's not because the compiler decided it would be more performant to not inline, but that it was acting on the side of caution.

Finally, if giving the compiler more freedom to make choices is your goal, then forcing it to start by either type-erasure or monomorphization is the wrong answer. The compiler should be free to choose type-erasure/monomorphization from the start in such case. However, that introduces limitations to the language. Since the generic function is prohibited from knowing the type size at compile time, it can't allocate space for it in the stack. Long story short, I think you'd be forcing the programmer into an abstraction they might not need nor would necessarily help them, and then hoping the compiler to optimize appropriately.

Personally, I care about expressivity and transparency, thus I think having both natively supported is preferable. But if one must be chosen over the other, monomorphism should come first, as you can build a vtable with it (also, there are many ways to build vtables and dynamically diapatch).