Hacker News new | ask | show | jobs
by BeeOnRope 122 days ago
Dead code is extremely common in C or C++ after inlining, other optimizations.
4 comments

Or stubs. I'll often flesh out a class before implementing the methods.
If dead code (1) is common in your codebase then your code base is missing heaps of refactors

(1) "dead" meaning unused types, unreachable branches

Not really, no. If you use a regex library it is very likely that 80% of that code is effectively dead code.
public interfaces are not dead code
OP means that the code has a dual purpose: one purpose is to be compiled, the other is to communicate structure or intent to programmers.
Do we know that? I've written "dead" code. It's point was to communicate structure or intent, but it was also still dead. This pattern, in one form or another, crops up a lot IME (in multiple languages, even, with varying abilities to optimize it):

  if condition that is "always" false:
    abort with message detailing the circumstances
That `if` is "dead", in the sense that the condition is always false. But "dead" sometimes is just a proof — or if I'm not rigourous enough, an assumption — in my head. If the compiler can prove the same proof I have in my head, then the dead code is eliminated. If can't, well, presumably it is left in the binary, either to never be executed, or to be executed in the case that the proof in my head is wrong.
What about assertions that are meant to detect bad hardware? I'd think that's not too uncommon, particularly in shops building their own hardware. Noise on the bus, improper termination, ESD, dirty clock signal, etc. -- there are a million reasons why a bit might flip. I wouldn't want the compiler to optimize "obviously wrong" code out anymore then empty loops.
I think if you're in a language that's doing constant-propagation optimizations, you work around that in one of two ways:

1. you drop down to assembly.

2. you use functions that are purpose built to be sequence points the optimizer won't optimize through. E.g., in Rust, for the case you mention, `read_volatile`.

In either case, this gives the human the same benefit the code is giving the optimizer: an explicit indication that this code that might appear to be doing nothing isn't.

Some conditions depend strictly on inputs and the compiler can't reason much about them, and the developers can't be sure about what their users will do. So that pattern is common. It's a sibling of assertions.

There are even languages with mandatory else branch.

That's the problem
Why is that a problem? Inlining and optimization aren't minor aspects of compiling to native code, they are responsible for order-of-magnitude speedups.

My point is that it is easy to say "don't remove my code" while looking at a simple single-function example, but in actual compilation huge portions of a function are "dead" after inlining, constant propagation and other optimizations: not talking anything about C-specific UB or other shenanigans. You don't want to throw that out.

Apologies for the flippant one liner, You made a good point and deserve more than that.

On the one hand, having the optimizer save you from your own bad code is a huge draw, this is my desperate hope with SQL, I can write garbage queries and the optimizer will save me from myself.

But... Someone put that code there, spent time and effort to get that machinery into place with the expectation that it is doing something. and when the optimizer takes that away with no hint. That does not feel right either. Especially when the program now behaves differently when "optimized" vs unoptimized.

What I mean is that we look at a function in isolation and see that it doesn't have any "dead code", e.g.,:

  int factorial(int x) {
    if (x < 0) throw invalid_input();
    // compute factorial ...
  }
This doesn't have any dead code in a static examination: at compilation-time, however, this function may be compiled multiple times, e.g., as factorial(5) or factorial(x) where x is known to be non-negative by range analysis. In this case, the `if (x < 0)` is simply pruned away as "dead code", and you definitely want this! It's not a minor thing, it's a core component of an optimizing compiler.

This same pruning is also responsible for the objectionable pruning away of dead code in the examples of compilers working at cross-purposes to programmers, but it's not easy to have the former behavior without the latter, and that's also why something like -Wdead-code is hard to implement in a way which wouldn't give constant false-positives.

Removing unused inlined functions or false constexpr's is trivial to see. We already have -Winline. We care about removed branches, exprs and stmts due to some optimizer logic.

I'm talking about the optimizer, not the linker, which thanksfully does a lot of pruning.