-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Regression: Miscompilation due to bug in "mutable noalias" logic #84958
Comments
@nikic: found it :P |
Marking this as a beta regression since 1.53 is about to be promoted to beta: #84909 |
The code in untrusted 0.8.0 (which I yanked) is not substantially different from the untrusted 0.7.1 so I'm afraid that ring (and all ring-based crates) relying on untrusted 0.7.1 to avoid this will not be a viable solution. I think there is sufficient evidence here to warrant disabling the broken optimization for the beta. I am happy to continue minimizing the test case too. |
Marking this as needs-MCVE since we'll need it to fix the upstream bug, but I agree this alone is probably enough to get it reverted. |
Bad news regarding the MCVE: I just tried inlining all the untrusted code into the repro test case so I could start minimizing it, but that caused the test case to pass. So it looks like creating a MCVE will be more difficult than I expected. |
For context, the test case is reduced from ring's |
Assigning priority as discussed as part of the Prioritization Working Group procedure and removing @rustbot label -I-prioritize +P-critical |
Is this minimal enough for
use untrusted::{Reader, Slice};
#[inline(never)] // Required otherwise the test passes.
fn read_single_byte_value<'a>(input: &mut Reader<'a>) -> Slice<'a> {
input.read_bytes(1)
}
fn main() {
let bytes = &[0x00];
let slice = Slice { bytes };
slice.read_all(|input| {
let value = read_single_byte_value(input);
let mut r2 = Reader::new(value);
r2.read_byte();
assert!(input.at_end());
});
}
#![no_std]
#[derive(Clone, Copy)]
pub struct Slice<'a> {
pub bytes: &'a [u8],
}
impl<'a> Slice<'a> {
#[inline]
pub fn subslice(&self, r: core::ops::Range<usize>) -> Self {
let bytes = self.bytes.get(r).unwrap();
Self { bytes }
}
pub fn into_value(self) -> Slice<'a> {
self
}
pub fn read_all<F, R>(self, read: F) -> R
where
F: FnOnce(&mut Reader<'a>) -> R,
{
let mut input = Reader::new(self.into_value());
read(&mut input)
}
}
pub struct Reader<'a> {
input: Slice<'a>,
i: usize,
}
impl<'a> Reader<'a> {
#[inline]
pub fn new(input: Slice<'a>) -> Self {
Self {
input: input.into_value(),
i: 0,
}
}
#[inline]
pub fn at_end(&self) -> bool {
self.i == self.input.bytes.len()
}
#[inline]
pub fn read_byte(&mut self) -> u8 {
let b = self.input.bytes.get(self.i).unwrap();
self.i += 1;
*b
}
#[inline]
pub fn read_bytes(&mut self, num_bytes: usize) -> Slice<'a> {
let new_i = self.i.checked_add(num_bytes).unwrap();
let bytes = self.input.subslice(self.i..new_i);
self.i = new_i;
bytes
}
} In the time I tried minimizing this, I wasn't able to easily trigger the issue without the multiple crates, LTO, and codegen-units=1. Here are the commands to trigger the bug:
As mentioned, removing the LTO and codegen-units flags will stop the assert from triggering. Here's that command which doesn't trigger the bug for the same code, for easy copy-pasting:
|
It is fine if the crash only occurs with multiple crates. One further minimization that would help is to get cargo out of the way (by specifying the minimal |
@nagisa updated to remove the test infra and cargo |
|
Edit: It seems others (including me) is now able to reproduce it on Linux. |
@comex: I was able to reproduce the original example on Linux (I haven't tested the minimization) |
The following rustc invokations seem to be the minimal required ones:
with untrusted.rs and regression.rs containing the code from lqd's comment
EDIT: the untrusted crate does not need (I apparently also can't read and completely missed lqd's update to their comment) |
Oops. The issue is actually that I was using @lqd's "command for easy copy-pasting" but it's missing |
@comex The command for easy copy pasting is the one that removes the LTO and CGU flags to show it works, contrasted to the above commands which trigger the bug |
I can't read. |
More likely that I can't write :) I've now added explicitly what the commands do; for the record I've minimized this on |
Minimized further. #![no_std]
#[derive(Clone, Copy)]
pub struct Slice<'a> {
pub bytes: &'a [u8],
}
impl<'a> Slice<'a> {
pub fn into_value(self) -> Slice<'a> {
self
}
}
use untrusted::{Slice};
struct Reader<'a> {
pub input: Slice<'a>,
pub i: usize,
}
#[inline(never)] // Required otherwise the test passes.
fn read_single_byte_value<'a>(input: &mut Reader<'a>) -> Slice<'a> {
input.i = 1;
input.input
}
#[inline(always)]
fn read(input: &mut Reader) {
let value = read_single_byte_value(input);
value.into_value().bytes.get(0).unwrap();
let val = input.i;
assert!(val == 1);
}
fn main() {
let bytes = &[0x00];
let slice = Slice { bytes };
slice.into_value();
let mut input = Reader {
input: slice,
i: 0,
};
read(&mut input)
} Running this panics: |
So, start with this LLVM IR: https://gist.github.com/comex/490edb708682370dcc40c059b761811f This is based on the minimized version in my last comment, after being run through rustc with The two important parts are, first:
This calls And second:
This corresponds to: let val = input.i;
assert!(val == 1); Note the But now run: And in the output, suddenly we have: %4 = call fastcc { i8*, i64 } @_ZN4main22read_single_byte_value17h6a8946d030728332E(%Reader* nonnull align 8 dereferenceable(24) %input) #4, !noalias !9
And when compiling the original source with rustc, LLVM went on to optimize the load into a constant 0 – which would be valid under the assumption that the call couldn't perform any stores that aliased with the load. For some reason, I can't get the same optimization to happen if I manually feed the .ll file into either the rustup-shipped |
In order to get it to reproduce with
|
So, the noalias gets tacked on as part of the inlining of /// When inlining a call site that has !llvm.mem.parallel_loop_access,
/// !llvm.access.group, !alias.scope or !noalias metadata, that metadata should
/// be propagated to all memory-accessing cloned instructions. It achieves this by iterating over What is Anyway, It does do one thing with the key. As I said, when the key is an Argument, the corresponding Value is not a copied instruction, but some random other Value from the outer function that was used as an actual parameter. So it checks for that: // Check that key is an instruction, to skip the Argument mapping, which
// points to an instruction in the original function, not the inlined one.
if (!VMI->second || !isa<Instruction>(VMI->first))
continue; But there's another case it didn't consider. When the key is an Instruction, the corresponding Value is normally a freshly-made clone Instruction. But not always. CloneFunction.cpp does some simplifications during the cloning process, such as this one: // If we can simplify this instruction to some other value, simply add
// a mapping to that value rather than inserting a new instruction into
// the basic block. Here's the IR again:
When inlining In other words,
and whose value is
(For the sake of anyone debugging this: it also has an entry with key When |
Shouldn't the
|
It should, so the pass tries to propagate the noalias metadata to cloned inner function's instructions. But in this case it applies the metadata to the outer function's instructions, which is invalid. |
Upstream bug report: https://bugs.llvm.org/show_bug.cgi?id=50270 |
Ooh... so the inliner assumes that, if a function argument is declared noalias, then the SSA value that was passed to that argument in the outer scope was noalias for its own scope? That is vicious. |
nikic's proposed LLVM patch: https://reviews.llvm.org/D102110 |
Do we expect the patch to be approved and ready for backport soon, or should we disable mutable-noalias for now? |
Just speaking as a user, who happened to find this bug: I'm very excited about the fix, but also I am nervous about the noalias optimizations being in Rust 1.53.0. I didn't find this bug intentionally. It was 100% an accident; i.e. pure luck. I know a lot of people are doing great work on getting this optimization production-ready but it isn't clear (to people like me who aren't involved in the compiler development) what validation is missing in the process and what steps have been taken within LLVM to ensure that future changes to LLVM don't break this (for Rust specifically). So IMO it makes sense to disable it for 1.53.0, at least. |
Update LLVM submodule This merges recent changes from the upstream LLVM 12 branch. One of them is intended to address rust-lang#84958.
nikic's patch for this issue has been merged into LLVM (https://reviews.llvm.org/rGaa9b02ac75350a6c7c949dd24d5c6a931be26ff9), and rustc's LLVM submodule has been updated (#85236). The change should be observable in the current nightly build (2021-05-14, commit 1025db84a). I've tested comex's reproduction with this nightly and it did not panic. @briansmith , can you see if the bug persists for you? (You may need to use |
Note that mutable noalias is on track to be disabled for the upcoming 1.53 release: #86036 |
Fixed by #86036. |
That's just for 1.53, what about master or 1.54? (Ah I believe those are fixed by #85236) |
The ring test suite started failing when I tried to upgrade to untrusted 0.8.0. The regression first shipped in nightly-2021-03-23:
We were further able to narrow down the regression by bisecting down to: 97663b6 "Auto merge of #82834 - nikic:mutable-noalias, r=nagisa Enable mutable noalias for LLVM >= 12."
The symptoms are 100% consistent with lifetimes being an input into the problem. The regression occurs in
--release
mode.Code
I tried this code:
Cargo.toml:
src/lib.rs:
I expected to see this happen: explanation
cargo +nightly test --release
should pass.Instead, this happened: explanation
cargo +nightly test --release
fails with:Version it worked on
Stable
Beta
nightly-2021-03-22
Version with regression
cargo +nightly-2021-03-23 version --verbose
@rustbot modify labels: +regression-from-stable-to-nightly -regression-untriaged
The text was updated successfully, but these errors were encountered: