15 ms·
Would have liked to see the machine code generated by the example function, and a deeper dive mapping compiler choices to the unintuitive results. The article
by rkagerer 7y ago
Would have liked to see the machine code generated by the example function, and a deeper dive mapping compiler choices to the unintuitive results.
The article (indeed the point of it) abstracts that all away behind "undefined behavior" and a mental model sitting between your code and its resulting executable. Which is fine, but it leaves a loose end which fails to sate my curiosity.
- deleted 7y ago[deleted]
- Arnavion 7y agoThe dropdown next to the "Run" button in the playground link shows you the ASM. But here's the relevant part: playground::main: push rax call std::panicking::begin_panic ud2 That is, it's an unconditional panic.
- rrobukef 7y agoThe provided example ([rust playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=da278adb50142d14909df74ea1e43069) https://play.rust-lang.org/?version=stable&mode=release&edit...) can generate assembly ('...' next to 'Run'). `main` just calls `panic` immediately. The functions returns undef. The reason: `x > 150` is undefined, `undef || ...` is undefined thus the first if-statement with side-effects may be interpret undefined as true. I wonder why the optimiser didn't choose false and let the assert pass?
- ralfjung 7y ago> I wonder why the optimiser didn't choose false and let the assert pass? I don't know exactly, but it's kind of a moot point. I would have negated the statements until I found a way to make it return what I want it to. The point is that the compiler picks some result, and it does so "locally", so when it picks results for multiple comparisons it makes no attempt to check that these results are all "consistent" and can even arise for a single value. The result that we can observe is that the value is "unstable". To "fix" this (assuming we wanted to specify that unstable values are not allowed in C/C++/Rust), the compiler would have to keep track of which constant foldings it already did for some uninitialized value, and make sure it remains consistent with that. That's a hard problem and likely undecidable in general. Allowing unstable values frees the optimizer from this burden, letting it optimize more code better.
- archgoon 7y agoIt depends on whether you generate using rustc 1.28 or rustc 1.36, and whether you're compiling with or without optimzations. This does not crash in unoptimized rust (either 1.36 or 1.28) but it will crash in optimized rust 1.36. https://godbolt.org/z/8Yxl2c https://godbolt.org/z/8Yxl2c I think though (as your question indicates); that the author misses the point of why people care about "What the hardware does". At the end of the day, assembly code is going to execute, and that assembly code is going to (despite the authors protestations to the contrary) have well defined memory of one value or another. The moment you start saying "Rust has a third value of uninitialized" the question comes up "How is that abstraction enforced by the hardware?" This is valuable information for understanding how the language works. From the authors discussion, I was expecting some sort of sentinel value being checked; however, instead, the uninitialized memory access is detected by the compiler and it panics uniformly regardless of the actual memory state. The idea that one should only worry about the abstract virtual machine of rust seems like an encouragement of magical thinking. "Don't worry about how any of this works, the compiler will just make it happen". This will not go over well with many people who are curious about learning Rust. However, if the author is arguing "Don't let the behavior of a naive enforcement of a Rust safety construct dictate how the optimized version should work" this seems like a more interesting position; but it's not clear that is the argument being made here.
- steveklabnik 7y ago> At the end of the day, assembly code is going to execute, and that assembly code is going to (despite the authors protestations to the contrary) have well defined memory of one value or another. The point is that you may not get the assembly you assume you’re going to get. Like the example shows, it may never even generate something that accesses the value at all.
- archgoon 7y agoYes, but that doesn't mean that what the hardware does is irrelevant.
- steveklabnik 7y ago
- mjw1007 7y agoThere's a link to the generated machine code in a footnote (for a variant returning a bool rather than calling assert!). It's literally just xor eax, eax ret
- robocat 7y agoThat's what I expected. And I that is probably due to an optimisation rather than uninitialised variables (can anyone confirm that?). I am sceptical the author really knows much as some of their statements seem blatantly wrong or just nonsense: "So, one time we 'look' at x it can be at least 150, and then when we look at it again it is less than 120, even though x did not change." Is talking about "x < 150 || x > 120", but gets it the wrong way around, ouch! "Memory remembers if you initialized it. The x that is passed to always_return_true is not the 8-bit representation of some number, it is an uninitialized byte." Benefit of doubt could be extremely poor metaphors, or referencing the wrong code? Also stating C is not low-level is a conceited attempt to redefine the word.
- Dylan16807 7y ago> Also stating C is not low-level is a conceited attempt to redefine the word. I expect a low level language to run the code I typed, or something that has the same effect. They're not redefining the term. C itself has been redefined away from its origins.
- saagarjha 7y agoThe author tries to ascribe too much meaning to undefined behavior and gets some parts of this wrong, but they are correct in saying that C is not a low-level language in the context that they're using it.
- ralfjung 7y agoI don't think I got any of it wrong, but in case I did I'd appreciate if you could point out my mistake(s). :)