5 ms·
> the Rust program you wrote does not run on your hardware. It runs on the Rust abstract machine Excellent point, and very well made… Thank you for writing thi
by an_d_rew 7y ago
> the Rust program you wrote does not run on your hardware. It runs on the Rust abstract machine
Excellent point, and very well made… Thank you for writing this!
There is a constant back-and-forth here on Hacker News about whether or not “undefined behavior“ is the root of all evil or the root of all real-worls optimizations… And your article does a great job of explaining, in real-world terms, what UB really (in-part) is.
- twic 7y agoI don't like this formulation much. I would rather say that the program does indeed run on your hardware, but the program that runs is not the program you wrote. It's the program into which program you wrote has transformed by the compiler - better yet, it could be any one of the programs into which the compiler is allowed to transform the program you wrote. I prefer this because it is more open about the fact that all the weird and counterintuitive behaviour happens because the language specifiers and compiler implementers decided to make it that way (usually, with good reason!). It's not some unavoidable property of the universe, or the machine. The idea of an abstract machine is still really useful, because it lets you reason directly about the code you are writing, rather than having to express and reason about what the compiler might do with it. But i think we should be clear that it's a tool for thinking, not a truth. The idea that you can ignore the real hardware is particularly unhelpful in Rust, because it's a great fit to low-level problems where the real hardware is a big deal. For example, at work, we have a Rust program where we routinely need to think about NUMA placement and cache coherency protocols. Those don't exist in the Rust abstract machine at all!
- anp 7y ago> The idea that you can ignore the real hardware is particularly unhelpful in Rust, because it's a great fit to low-level problems where the real hardware is a big deal. For example, at work, we have a Rust program where we routinely need to think about NUMA placement and cache coherency protocols. Those don't exist in the Rust abstract machine at all! If I understand Ralf's overall model here, I think they'd argue that this sort of reasoning must be done within the context of the abstract machine's behavior for std::ptr::{read,write}_volatile, no?
- twic 7y agoThe kind of reasoning we do is "if thread A writes to this location, then next time thread B writes to this location, it will have to take ownership of the cache line, which will take N cycles, so let's not do that". I don't think that kind of performance reasoning maps on to anything in Rust.
- twic 7y agoI missed out so many words in this comment. I need to hire a proofreader.
- ralfjung 7y agoI think thinking about real hardware (most of the time) just distracts from thinking about what your program "actually does", which is specified by the abstract machine. By thinking in terms of the abstract machine, you can forget about compilers and optimizations when writing your program, and focus on your code and what it does. Of course, when you ask why the abstract machine is the way it is, optimizations and hardware come up again. But I think these concerns are better separated. This also mirrors how languages like C/C++ are actually designed, at least in theory: optimizations are justified against the abstract machine, not the other way around. Isn't it much easier to have one, albeit weird, machine in your head, than a (only marginally simpler) "real" machine plus a list of optimizations that also contribute to the behavior of the compiled program? And that list can even change any time! > The idea of an abstract machine is still really useful, because it lets you reason directly about the code you are writing, rather than having to express and reason about what the compiler might do with it. But i think we should be clear that it's a tool for thinking, not a truth. If you read the C/C++ standard, you can see that the abstract machine is the truth. Same if you read the really well-written WebAssembly standard (which comes with a mathematically precise formal definition). So IMO you got it backwards. The abstract machine is the truth, the optimizations are just the way that machine gets exploited right now and can change with any compiler update. Of course the abstract machine is not "God-given", but neither are the optimizations. And the abstract machine can only change when switching to a new version of the language standard, the optimizations can change on any minor compiler update. The machine is much more stable than the list of optimizations. > The idea that you can ignore the real hardware is particularly unhelpful in Rust, because it's a great fit to low-level problems where the real hardware is a big deal. For example, at work, we have a Rust program where we routinely need to think about NUMA placement and cache coherency protocols. Those don't exist in the Rust abstract machine at all! I admit that once you think about performance, the details of what your compiler and hardware happen to do become very relevant. But when talking about correctness, I think that is an unsuited level of (lack of) abstraction. EDIT: Based on this feedback and others, I have amended the blog post a bit. It now says > Maybe the most important lesson to take away from this post is that “what the hardware does” is most of the time irrelevant when discussing what a Rust/C/C++ program does, unless you already established that there is no undefined behavior. [...] > UB-free programs can be made sense of by looking at their assembly, but whether a program has UB is impossible to tell on that level. For that, you need to think in terms of the abstract machine.
- ralfjung 7y agoAnother argument for considering the abstract machine as the primary way to think about programs in a language is that it is very easy to end up with a set of optimizations that all look reasonable in isolation but are inconsistent, and lead to incorrect code when combined. Both GCC and LLVM suffer from this (and in fact MSVC had/has the exact same bug). GCC: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65752 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65752 LLVM: https://bugs.llvm.org/show_bug.cgi?id=35229 https://bugs.llvm.org/show_bug.cgi?id=35229, https://bugs.llvm.org/show_bug.cgi?id=34548 https://bugs.llvm.org/show_bug.cgi?id=34548