Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> But if it's load-bearing, maybe that's a bug?

If your "debug optimization" code is so slow as to be unusable (see: Rust), then your optimizations qualify as load-bearing.

The problem is that "optimization level" needs a mindset change. The optimization levels should be "release", "small" and "experimental".

"Release level" needs to be perfectly usable for debugging as well as in production--"debug level" should be "release level". Compilation time should be reasonable and run time should be functional.

After that, for embedded, you should have "small level"--checks should get turned off and any optimizations that make code significantly bigger should get turned off (loop unrolling, for example). You might enable some optimizations that make compile time brutally slow.

Finally, there should be an "experimental level" which tests out optimizations before they go into release.

And there should be no "fast" optimization level. If your optimization is that situation specific, it should stay stuck in "experimental".

And through all of this, the compiler should also eject files that carry enough information to allow debuggers to make sense of the compiled code, unwind the optimizations when the users asks, and present a coherent version of what is going on. This is actually where compilers really break down nowadays. The compiler needs to eject enough information and context that a debugger can unwind what is going on rather than being an afterthought.

We need the equivalent of an LSP (Language Server Protocol) for debuggers.



DWARF does contain turing-complete VM for doing unwinding, so theoretically it should already be possible to make a compiler that gives precise debug info everywhere, no new protocol necessary.

But completely-reversible-anywhere optimizations are rather limiting, disallowing a bunch of things; including, but not limited to, dead code elimination (more generally, forgetting some fraction of state from somewhere), identical code merging, and instruction reordering (esp. vectorization, which essentially reorders across multiple loop iterations), severely limiting what your optimizer can even do.


> And through all of this, the compiler should also eject files that carry enough information to allow debuggers to make sense of the compiled code, unwind the optimizations when the users asks, and present a coherent version of what is going on.

You can't do this because some optimizations can't be seen through; most obvious one is identical code merging. If you're in a merged function then you don't know which of the original source lines you're looking at.

I'd like to see a system which only had optimized compilation and used an interpreter for debugging.


You can do that by having several different trampolines for the merged code that stash away a unique cookie on the stack to distinguish which function the code is currently executing. This is not zero-overhead, of course, but one can argue that the slight overhead is worth it given better error reporting and increased debuggability.


> If you're in a merged function then you don't know which of the original source lines you're looking at.

Then don't do that optimization.

How much is gained versus the grief that is caused?

Even when I did a lot of embedded device coding, I don't think that optimization ever saved me more than a couple dozen bytes of code.

And, as you point out, the optimization is stupidly difficult for debuggers to to unwind.

This is one of the disconnects of modern optimization. There is no good backpressure from downstream tools to say "Oy. That optimization is going to make our lives difficult. If you can't demonstrate a significant improvement, don't do it."


Indeed, you can just not do it for important things - usually the error paths leading to a crash or exit.

It's important for embedded software though. But that was a bad example; I should've said tail calls. Which are much more common and more important, because avoiding them blows out the stack for recursive code.


How would this model handle code that needs to be as fast as possible, at the cost of debugging? E.g. all hot-loop code, all high performance numerical code, all latency sensitive and instruction sensitive code?

> If your optimization is that situation specific, it should stay stuck in "experimental".

Yeah, I have a feeling that there’s low-key lots of applications for whom many compiler optimisations that you’re branding “experimental” are in fact run-of-the-mill and would just enable “experimental” mode so frequently it’d just be synonymous with “actually release mode”.

> And through all of this, the compiler should also eject files that carry enough information to allow debuggers to make sense of the compiled code, unwind the optimizations when the users asks, and present a coherent version of what is going on.

This is a pretty cool idea, semi-related, you should check out some of the work around E-Graphs, which attempt to enable the same level of optimisations but also preserve more original context. It’d be neat to have the equivalent of source-maps for highly optimised code, but I’m not sure how we’d manage them without them becoming enormous. After things like inlining, constant-folding, desugaring, rearrangement operations, etc all take place I suspect you’d be left with code that bears only the merest passing resemblance to its original form.


For the web, there are debuggers, but no apparent "debug mode."




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: