That's literally what @CAD97's example shows. Here's another one:
fn my_fun(x: &mut i32, f: impl FnOnce()) {
*x = 0;
*x = 1;
f();
*x = 2;
}
When building with panic=abort
, the compiler is allowed to remove the *x = 1
. (SB would also let it remove the *x = 0
but TB might not permit that... let's not have that discussion here okay?
)
But if an inline asm block inside f
were allowed to even just read from the part of the AM state that x
points to, then obviously such an optimization would be unsound.
So, to ensure that inline assembly is compatible with optimizations on the rest of the code, and to ensure that code which does not want to use inline asm can be optimized as much as it could if inline asm did not exist, it is crucial that the AM state transition implemented by the inline asm block cannot even observe parts of the AM state that Rust code could not observe here.
To be clear, since that may explain some of the confusion above: It is totally legal to write an inline asm block that stores a MaybeUninit<i32>
in some register, loads it again, and returns the result as i32
. However, to justify the legailty of this, you have to declare that the semantics of your block is that it returns some arbitrary non-deterministically-chosen number, and then the rest of the code has to be correct under those semantics. That is obviously not the same semantics as freeze
, and that is the entire point.
But you cannot at the same time say "this behaves like freeze
" and "this is allowed to always return garbage". Those two statements just cannot be true at the same time.
Exactly. And by saying that this behaves like freeze
, you are assigning meaning to it, since freeze
is defined to preserve the original contents if they were already initialized (i.e., it does not always return garbage).
As I just sketched, it is possible to consider this function well-defined with the semantics of "reset *page
to contain arbitrarily non-deterministically-chosen contents (without provenance)". But it is not possible to consider this function well-defined with freeze
semantics, where existing contents are preserved if they have been already initialized.
We provided concrete examples of how other inline asm blocks that observe machine state which Rust code cannot observe can break in practice. I think that validates my approach in principle, so now the burden of proof is on whoever wants to introduce exceptions from this principle for particular cases.
I already acknowledged upthread that specifically for freeze
, it is possible that all optimizations remain sound, due to specifically how that state works. But our correctness standards are generally higher than "there exists the possibility of a correctness argument for this code, but nobody has actually been able to come up with a solid argument".
This is wrong, and that's what we are trying to tell you.
As long as you are writing Rust code, you are dealing with Rust AM states. If you use asm!
, you additionally deal with concrete machine states, but never ever can you ignore the AM state when writing anything that passes through the Rust compiler.
The urban myth than inline assembly lets you go down to the concrete machine level and entirely ignore the Abstract Machine is just that, an urban myth. It is fundamentally incompatible with how optimizing compilers work. You can curse the fact that we have optimizing compilers, you can demand a language that is less optimized and thus makes writing inline assembly much easier -- but you cannot have both powerful optimizations and easy inline assembly, that's just a logical impossibility.