]> git.ralfj.de Git - web.git/blobdiff - personal/_posts/2026-03-13-inline-asm.md
link to OOPSLA paper on inline-asm
[web.git] / personal / _posts / 2026-03-13-inline-asm.md
index 5e9a93b503fa43be3707b1651abc805ecec6b066..09ffd36a53e6698a8c4a8465fba4ce176ac7c86f 100644 (file)
@@ -1,6 +1,7 @@
 ---
 title: "How to use storytelling to fit inline assembly into Rust"
 categories: rust
+reddit: /rust/comments/1rshm93/how_to_use_storytelling_to_fit_inline_assembly/
 ---
 
 The Rust Abstract Machine is full of [wonderful oddities]({% post_url 2020-12-14-provenance %}) that do not exist on the [actual hardware]({% post_url 2019-07-14-uninit %}).
@@ -81,7 +82,7 @@ Specifically, for every inline assembly block, there has to be a corresponding p
 When reasoning about the behavior of the overall program, the inline assembly block then gets replaced by that "story" code.
 You don't have to actually write this code; what's important is that the code exists and tells a coherent story with what the surrounding Rust code does.
 
-[^alice]: Thank you to Alice Ryhl for suggesting the term "telling a story" for this model.
+[^alice]: Credits go to Alice Ryhl for suggesting the term "telling a story" for this model.
 
 For our example above, this immediately explains what went wrong:
 the story code for the inline assembly block would have to be something like `(x as *const i32 as *mut i32).write(0)`, and if we insert that code in place of the inline assembly block, we can immediately see (and Miri could confirm) that the program has UB.
@@ -134,7 +135,7 @@ Slightly more concretely, "allocating" a page in a way that is compatible with t
 - Next, the address of the page is cast to a pointer (using [`with_exposed_provenance`](https://doc.rust-lang.org/std/ptr/fn.with_exposed_provenance.html)).
 - Finally, Rust code may use that pointer to access the new page.
 
-[^volatile]: Why am I insisting on volatile accesses here? Because if you had the page tables inside a regular Rust allocation, writes to that page table could have "interesting" effects, and that doesn't really correspond to anything that can happen when you write to a nomral Rust allocation. In other words, I haven't (yet) come up with a proper story that would allow for those writes to be non-volatile.
+[^volatile]: Why am I insisting on volatile accesses here? Because if you had the page tables inside a regular Rust allocation, writes to that page table could have "interesting" effects, and that doesn't really correspond to anything that can happen when you write to a normal Rust allocation. In other words, I haven't (yet) come up with a proper story that would allow for those writes to be non-volatile.
 
 The story of this asm block is that it performs memory allocation at the given address, which we know to be unallocated.[^alloc-control]
 This creates a fresh provenance that represents the new allocation.
@@ -203,7 +204,7 @@ Therefore, one can write a Rust program that seems to be data-race-free (accordi
 In other words, rule 3 (the inline asm block must refine the story code) is violated.
 
 The principled fix for this is to extend the C++ memory model (which is shared by Rust) with a notion of non-temporal stores so that one can reason about how they interact with everything else that can go on in a concurrent program.
-I am not aware of anyone having actually done this---extending or even just slightly adjusting the C++ memory model is an [enormous undertaking](https://plv.mpi-sws.org/scfix/paper.pdf).
+This is [possible](https://people.mpi-sws.org/~viktor/papers/oopsla2024-inline.pdf), but it requires re-proving compiler correctness results, and at least the approach taken in that paper is architecture-specific which does not scale to the large number of architectures Rust supports.
 However, there is a simpler alternative: we can try coming up with a more complicated story such that rule 3 is not violated.
 This is exactly what a bunch of folks did when the issue around non-temporal stores was discovered.
 The story says that doing a non-temporal store corresponds to *spawning a thread* that will asynchronously perform the actual store,
@@ -262,7 +263,7 @@ The Abstract Machine has no floating-point status bits that the story code could
 This directly reflects the fact that the compiler makes no guarantees for the value that the program will observe in the status register, and since floating-point operations can be arbitrarily reordered, this should be taken quite literally.
 
 For writing the control register, there simply is no possible story: no Rust operation exists that would change the rounding mode of subsequent floating-point operations.
-Any inline asm block that changes the rounding mode therefore has undefined behavior.
+Any inline asm block that changes the rounding mode therefore has undefined behavior (and the same goes for other flags that change the behavior of instructions used by the Rust compiler, like flushing subnormals to zero).
 
 While this may sound bleak, it is entirely possible to write an inline asm block that changes the rounding mode, performs some floating-point operations, and then changes it back!
 The story code for this block can use a soft-float library to perform exactly the same floating-point operations with a non-default rounding mode.
@@ -280,16 +281,18 @@ In some cases, however, there are no obvious miscompilations.
 And indeed, if we knew exactly which universal properties of Rust programs the compiler relies on, we could allow inline asm code that satisfies all those universal properties, even if it has no story which can be expressed in Rust source code.
 Unfortunately, this approach would require us to commit to the full set of universal properties the compiler may ever use.
 If we discover a new universal property tomorrow, we cannot use it since there might be an inline asm block for which the universal property does not hold.
+
 This is why I am proposing to take the conservative approach:
-only allow inline asm blocks that are obviously compatible with all universal properties of actual Rust code, because their story can be expressed as actual Rust code!
+only allow inline asm blocks that are obviously compatible with all universal properties of actual Rust code, because their story can be expressed as actual Rust code.
+If there is an operation we want to allow that currently has no valid story, we should just [add](https://github.com/rust-lang/rfcs/pull/3700) a [new language operation](https://github.com/rust-lang/rfcs/pull/3605), which corresponds to officially blessing that operation as one the compiler will keep respecting.
 
-[^noopt]: This assumes that we do not want to sacrifice the optimizations in question. Since inline assembly could hide inside any function call, this typically becomes a language-wide tradeoff: either we forbid such inline asm blocks, or we cannot do the optimization even in pure Rust code.
+[^noopt]: This assumes that we do not want to sacrifice the optimizations in question. Since inline assembly could hide inside any function call, this typically becomes a language-wide trade off: either we forbid such inline asm blocks, or we cannot do the optimization even in pure Rust code.
 
 Right now, we have no official documentation or guidelines for how inline asm blocks and FFI interact with Rust-level UB,
 but as the `innocent` example at the top of the post shows, we cannot leave inline asm blocks unconstrained like that.
 The storytelling approach is my proposal for filling that gap.
 I plan to eventually suggest it as the official rules for inline assembly.
 But before I do that, I'd like to be more confident that this approach really can handle most real-world scenarios.
-If you have examples of assembly blocks that cannot be explained with storytelling, but that you are convinced are correct and hence should be supported, please let us know, either in the immediate discussion for this blog post or (if you are reading this later) in the [t-opsem Zulip channel](https://rust-lang.zulipchat.com/#narrow/channel/136281-t-opsem).
+If you have examples of assembly blocks that cannot be explained with storytelling, but that you are convinced are correct and hence should be supported, please let us know, either in the immediate [discussion](https://www.reddit.com/r/rust/comments/1rshm93/how_to_use_storytelling_to_fit_inline_assembly/) for this blog post or (if you are reading this later) in the [t-opsem Zulip channel](https://rust-lang.zulipchat.com/#narrow/channel/136281-t-opsem).
 
 #### Footnotes