Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3ea68fb
[Priroda] Render indirect local values
moabo3li Jul 10, 2026
037fbe7
[Priroda] Render indirect locals with allocation dumps
moabo3li Jul 16, 2026
cc00f34
Prepare for merging from rust-lang/rust
Jul 19, 2026
7c3257d
Merge ref 'c904ba32e22c' from rust-lang/rust
Jul 19, 2026
e69e7d2
Merge pull request #5218 from rust-lang/rustup-2026-07-19
RalfJung Jul 19, 2026
18ba449
[Priroda] Render mplace local byte ranges
moabo3li Jul 16, 2026
52cc3b1
[Priroda] Render provenance-aware local bytes
moabo3li Jul 19, 2026
0726da7
[Priroda] Add follow command for allocation bytes
moabo3li Jul 19, 2026
9b5b406
Merge pull request #5217 from moabo3li/local-value-bytes-handling
oli-obk Jul 20, 2026
f9c7888
support `neon.tbl*` intrinsics
folkertdev Jul 20, 2026
763fe4d
add a riscv target to CI
RalfJung Jul 18, 2026
2c92e6d
Merge pull request #5219 from folkertdev/aarch64-table
RalfJung Jul 21, 2026
810cdc0
readme: fix misspelling of `-Ctarget-feature`
niooss-ledger Jul 21, 2026
afefb4f
bump libc
RalfJung Jul 21, 2026
132caa8
Merge pull request #5221 from niooss-ledger/readme-Ctarget-feature-typo
RalfJung Jul 21, 2026
940657f
Merge pull request #5216 from RalfJung/riscv
RalfJung Jul 21, 2026
6ca5127
Prepare for merging from rust-lang/rust
RalfJung Jul 22, 2026
ac32e6a
Merge ref '1af98b7cdf86' from rust-lang/rust
RalfJung Jul 22, 2026
94fde0c
Revert "disable `portable_simd` miri test that needs additional intri…
RalfJung Jul 22, 2026
cabd1f1
disable swizzle_dyn test on loongarch
RalfJung Jul 22, 2026
7e9fba3
Merge pull request #5224 from RalfJung/rustup
RalfJung Jul 22, 2026
525e1aa
Prepare for merging from rust-lang/rust
Jul 23, 2026
2f39e7e
Merge ref '390279b302ca' from rust-lang/rust
Jul 23, 2026
21b23c2
[Priroda] Test projected mplace value range rendering
moabo3li Jul 23, 2026
d37c84d
[Priroda] Render projected debug-info values
moabo3li Jul 21, 2026
c9492bb
Merge pull request #5225 from rust-lang/rustup-2026-07-23
RalfJung Jul 23, 2026
a9bc5ec
Merge pull request #5220 from moabo3li/handle-projected-values
oli-obk Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/tools/miri/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ endian-sensitive code.
### Controlling target features

Controlling target features works similar to regular rustc invocations:
`RUSTFLAGS="-Ctarget-features=+avx512f" cargo miri test` runs the tests with AVX512 enabled. (Miri
`RUSTFLAGS="-Ctarget-feature=+avx512f" cargo miri test` runs the tests with AVX512 enabled. (Miri
only supports very few AVX512 intrinsics at the moment.) `-Ctarget-cpu` also works. If target
features are also relevant for doctests, you have to also set `RUSTDOCFLAGS`.

Expand Down
1 change: 1 addition & 0 deletions src/tools/miri/ci/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ case $HOST_TARGET in
# Not officially supported tier 2
MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests
MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics
MANY_SEEDS=16 TEST_TARGET=riscv64a23-unknown-linux-gnu run_tests
;;
aarch64-apple-darwin)
# Host
Expand Down
29 changes: 29 additions & 0 deletions src/tools/miri/priroda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Current focus:
- source-location output after stepping
- source-location breakpoint prototype
- source-local listing prototype
- runtime local state and value rendering
- range-limited byte output for indirect locals

## Setup

Expand Down Expand Up @@ -65,8 +67,35 @@ RUSTC_BLESS=1 cargo test
| `c`, `continue` | Continue until the program finishes or reaches a breakpoint. |
| `b <path>:<line>`, `break <path>:<line>` | Add a source-location breakpoint. |
| `l`, `locals` | List source-level locals in the current frame by name. |
| `p <local>`, `print <local>` | Print one MIR local by numeric id. |
| `f <alloc> <offset>`, `follow <alloc> <offset>` | Render allocation bytes from an offset, including the full allocation size. |
| `q`, `quit` | Exit Priroda. |

## Value Output

Immediate values use Miri's `Immediate` display representation. Indirect
locals are rendered as the bytes belonging to the current value range, not as
the entire backing allocation:

```text
[01 02 03]
[?? ?? ??]
```

`??` means the byte is uninitialized. A value whose runtime size cannot be
determined is reported as `<unsupported-unsized>`.

Pointer/provenance spans are planned as part of the raw byte output, using a
compact dump-like marker such as:

```text
[<ptr alloc5+0> 2a 00 00 00]
```

Automatic pointer following is future work and should be explicit, not part of
ordinary value printing. Typed field rendering and dereference/projection-aware
printing are also future work.

EOF also exits Priroda cleanly.

Example:
Expand Down
179 changes: 158 additions & 21 deletions src/tools/miri/priroda/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![feature(rustc_private)]

extern crate miri;
extern crate rustc_abi;
extern crate rustc_codegen_ssa;
extern crate rustc_data_structures;
extern crate rustc_driver;
Expand All @@ -16,13 +17,17 @@ extern crate rustc_type_ir;

use std::collections::{HashMap, HashSet};
use std::io::{self, Write};
use std::num::NonZeroU64;
use std::ops::Range;
use std::path::PathBuf;

use miri::Immediate::{Scalar, ScalarPair, Uninit};
use miri::*;
use miri::Immediate::Uninit;
use miri::{interpret, *};
use rustc_abi::Size;
use rustc_driver::Compilation;
use rustc_hir::attrs::CrateType;
use rustc_interface::interface;
use rustc_middle::mir::interpret::AllocId;
use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment};
use rustc_middle::ty::{TyCtxt, TyKind};
use rustc_session::EarlyDiagCtxt;
Expand Down Expand Up @@ -377,10 +382,25 @@ impl<'tcx> PrirodaContext<'tcx> {
DebuggerCommand::ListLocals => interp_ok(CommandResult::Locals(self.list_locals())),
DebuggerCommand::Print(local) =>
interp_ok(CommandResult::SingleLocal(self.get_local(local))),
DebuggerCommand::Follow(alloc_id, offset) =>
self.follow_alloc(alloc_id, offset).map(CommandResult::Memory),
DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession),
}
}

fn follow_alloc(&self, alloc_id: AllocId, offset: usize) -> InterpResult<'tcx, String> {
let alloc = self.ecx.get_alloc_raw(alloc_id)?;
if offset > alloc.len() {
return Err(miri::err_unsup_format!(
"allocation offset {offset} is outside {alloc_id}"
))
.into();
}

let memory = self.render_alloc_bytes(alloc_id, offset..alloc.len())?;
interp_ok(format!("Allocation {alloc_id}+{offset}: {memory}"))
}

fn get_local(&self, local: usize) -> Option<LocalDesc> {
let frame = self.ecx.active_thread_stack().last()?;

Expand All @@ -399,6 +419,106 @@ impl<'tcx> PrirodaContext<'tcx> {
self.build_local_descs(frame)
}

/// Renders the current byte range of an indirect MIR value.
///
/// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`,
/// and complete pointer-sized provenance as pointer markers.
fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> {
let size = match self.ecx.size_and_align_of_val(mplace)? {
Some((size, _)) => size,
None => {
// Extern types cannot currently be executed as by-value locals,
// so this path cannot yet be covered by a Priroda UI fixture.
// FIXME: Add coverage once Priroda supports printing dereferenced places.
return interp_ok("<unsupported-unsized>".to_string());
}
};

let size = size.bytes_usize();
if size == 0 {
return interp_ok("[]".to_string());
}

let (alloc_id, offset, _) =
self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?;
let offset = offset.bytes_usize();
let range = offset..offset.strict_add(size);

self.render_alloc_bytes(alloc_id, range)
}

/// Render a raw allocation range without requiring a typed memory place.
///
/// This is also used by the future-facing `follow` command, where we have a
/// pointer target but do not yet know the target's type or size.
fn render_alloc_bytes(
&self,
alloc_id: AllocId,
range: Range<usize>,
) -> InterpResult<'tcx, String> {
let alloc = self.ecx.get_alloc_raw(alloc_id)?;

let mut rendered = Vec::with_capacity(range.len());

let ptr_size = self.ecx.tcx.data_layout.pointer_size();

for chunk in alloc.init_mask().range_as_init_chunks(range.into()) {
let chunk_range = chunk.range();
let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize();

if chunk.is_init() {
let ptr_size = ptr_size.bytes_usize();
let mut cursor = chunk_range.start;

while cursor < chunk_range.end {
// Full pointer provenance is rendered as a pointer marker. Bytewise
// provenance fragments are intentionally left as raw bytes here: they do
// not represent a complete pointer-sized value.
if let Some(prov) = alloc.provenance().get_ptr(Size::from_bytes(cursor))
&& cursor + ptr_size <= chunk_range.end
{
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(
cursor..cursor + ptr_size,
);
let offset = read_target_uint(self.ecx.tcx.data_layout.endian, bytes)
.map_err(|err| {
miri::err_unsup_format!("invalid pointer representation: {err}")
})?;

let offset = Size::from_bytes(offset);
rendered.push(format!("{:?}", Pointer::new(Some(prov), offset)));

cursor += ptr_size;
} else {
let byte = alloc
.inspect_with_uninit_and_ptr_outside_interpreter(cursor..cursor + 1)[0];

rendered.push(format!("{byte:02x}"));
cursor += 1;
}
}
} else {
rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len()));
}
}

interp_ok(format!("[{}]", rendered.join(" ")))
}

/// Render an evaluated operand using the same raw representation for
/// whole locals and projected MIR places.
fn render_op(&self, op: OpTy<'tcx>) -> String {
match op.as_mplace_or_imm() {
Either::Right(imm) => format!("{imm}"),

Either::Left(mplace) =>
match self.render_mplace_bytes(&mplace).report_err() {
Ok(bytes) => bytes,
Err(err) => format!("<error: {}>", interpret::format_interp_error(err)),
},
}
}

/// Render the source-side path from composite debug info, such as `.field`.
fn render_source_projection(
fragment: Option<&VarDebugInfoFragment<'tcx>>,
Expand Down Expand Up @@ -486,22 +606,14 @@ impl<'tcx> PrirodaContext<'tcx> {
None => {
local_desc.value = "<dead>".to_string();
}
Some(Either::Left(_)) => {
local_desc.value = "<indirect>".to_string();
}
Some(Either::Right(imm)) => {
match imm {
Scalar(_) => {
local_desc.value = "<immediate>".to_string();
}
ScalarPair(_, _) => {
local_desc.value = "<immediate-pair>".to_string();
}

Uninit => {
local_desc.value = "<uninit>".to_string();
}
};
Some(Either::Right(Uninit)) => local_desc.value = "<uninit>".to_string(),

Some(Either::Left(_) | Either::Right(_)) => {
let op = self
.ecx
.local_to_op(local, None)
.expect("this error can only occur in CTFE on generic code");
local_desc.value = self.render_op(op);
}
};

Expand Down Expand Up @@ -552,7 +664,8 @@ impl<'tcx> PrirodaContext<'tcx> {
// and `_slice._extra`, not as two separate locals both named `_slice`.

// Whole-place debug entries enrich the direct storage-local description.
// Projected places and constants are handled separately/deferred.
// Projected places are evaluated from their original MIR Place and use
// the same raw renderer as ordinary locals.
for var_debug_info in &frame.body().var_debug_info {
if let VarDebugInfoContents::Place(place) = &var_debug_info.value {
if let Some(local_idx) = place.as_local()
Expand All @@ -566,15 +679,21 @@ impl<'tcx> PrirodaContext<'tcx> {
let storage_projection = Self::render_storage_projection(place.projection);
let source_projection =
Self::render_source_projection(var_debug_info.composite.as_deref());
let value = self
.ecx
.eval_place_to_op(*place, None)
.map(|op| self.render_op(op))
.unwrap_or_else(|err| {
format!("<error: {}>", interpret::format_interp_error(err))
});

local_descs.push(LocalDesc {
source_name: Some(var_debug_info.name),
source_projection,
local: Some(place.local),
storage_projection,
ty: place.ty(local_decls, self.ecx.tcx.tcx).ty.to_string(),
// FIXME: projection not handled yet.
value: "<unsupported-projection>".to_string(),
value,
});
}
}
Expand All @@ -592,6 +711,7 @@ enum DebuggerCommand {
Breakpoint(PathBuf, usize),
ListLocals,
Print(usize),
Follow(AllocId, usize),
}

enum BreakpointSetResult {
Expand All @@ -605,6 +725,7 @@ enum CommandResult {
BreakpointResult(BreakpointSetResult),
Locals(Vec<LocalDesc>),
SingleLocal(Option<LocalDesc>),
Memory(String),
// FIXME: distinguish terminating the debugger session from disconnecting a
// frontend and terminating the interpreted program once multiple frontends exist.
TerminateSession,
Expand Down Expand Up @@ -693,6 +814,7 @@ impl Cli {
}
None => println!("no local for this id"),
},
CommandResult::Memory(memory) => println!("{memory}"),
CommandResult::TerminateSession => {
println!("quitting");
return interp_ok(());
Expand Down Expand Up @@ -725,6 +847,7 @@ impl Cli {
"b" | "break" => self.parse_breakpoint(args),
"l" | "locals" => Some(DebuggerCommand::ListLocals),
"p" | "print" => self.parse_print_local(args),
"f" | "follow" => self.parse_follow(args),
_ => None,
}
}
Expand Down Expand Up @@ -757,4 +880,18 @@ impl Cli {
let local = input.parse().ok()?;
Some(DebuggerCommand::Print(local))
}

fn parse_follow(&self, input: &str) -> Option<DebuggerCommand> {
let mut parts = input.split_whitespace();
let alloc_id = parts.next()?;
let offset = parts.next()?;
if parts.next().is_some() {
return None;
}

let alloc_id = alloc_id.strip_prefix("alloc").unwrap_or(alloc_id).parse().ok()?;
let alloc_id = AllocId(NonZeroU64::new(alloc_id)?);
let offset = offset.parse().ok()?;
Some(DebuggerCommand::Follow(alloc_id, offset))
}
}
3 changes: 2 additions & 1 deletion src/tools/miri/priroda/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Regex::new(&regex::escape(&manifest_dir.display().to_string())).unwrap();
let miri_dir_regex = Regex::new(&regex::escape(&miri_dir.display().to_string())).unwrap();
let rustc_sysroot_regex = Regex::new(&regex::escape(&rustc_sysroot)).unwrap();

let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap();
config.comment_defaults.base().normalize_stdout.extend([
(manifest_dir_regex.into(), b"{MANIFEST_DIR}".to_vec()),
(miri_dir_regex.into(), b"{MIRI_DIR}".to_vec()),
(rustc_sysroot_regex.into(), b"{RUSTC_SYSROOT}".to_vec()),
(pointer_regex.into(), b"{ALLOC_PTR}".to_vec()),
]);

// Priroda CLI tests do not currently require annotation comments in the test files
Expand Down
12 changes: 6 additions & 6 deletions src/tools/miri/priroda/tests/ui/locals_access_field.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_access_field.rs:14
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: <indirect>
Name: _slice, Id: _2, Ty: &[u8], Value: <immediate-pair>
Name: _extra, Id: _3, Ty: u32, Value: <immediate>
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
Name: _extra, Id: _3, Ty: u32, Value: 0_u32
(priroda) Id: _0, Ty: (), Value: <uninit>
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: <indirect>
(priroda) Id: _2, Ty: &[u8], Value: <immediate-pair>
(priroda) Id: _3, Ty: u32, Value: <immediate>
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
(priroda) Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
(priroda) Id: _3, Ty: u32, Value: 0_u32
(priroda) no local for this id
(priroda) no local for this id
(priroda) quitting
1 change: 1 addition & 0 deletions src/tools/miri/priroda/tests/ui/locals_corpus_async.stdin
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ continue
locals
continue
locals
follow 2 0
continue
locals
continue
Expand Down
Loading
Loading