Skip to content

Commit 1869eb3

Browse files
authored
Unrolled build for #159962
Rollup merge of #159962 - RalfJung:miri, r=RalfJung miri subtree update Subtree update of `miri` to rust-lang/miri@15e1f27. Created using https://github.com/rust-lang/josh-sync. r? @ghost
2 parents dc3f851 + a9bc5ec commit 1869eb3

34 files changed

Lines changed: 776 additions & 100 deletions

src/tools/miri/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ endian-sensitive code.
162162
### Controlling target features
163163

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

src/tools/miri/ci/ci.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ case $HOST_TARGET in
178178
# Not officially supported tier 2
179179
MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests
180180
MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics
181+
MANY_SEEDS=16 TEST_TARGET=riscv64a23-unknown-linux-gnu run_tests
181182
;;
182183
aarch64-apple-darwin)
183184
# Host

src/tools/miri/priroda/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Current focus:
99
- source-location output after stepping
1010
- source-location breakpoint prototype
1111
- source-local listing prototype
12+
- runtime local state and value rendering
13+
- range-limited byte output for indirect locals
1214

1315
## Setup
1416

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

74+
## Value Output
75+
76+
Immediate values use Miri's `Immediate` display representation. Indirect
77+
locals are rendered as the bytes belonging to the current value range, not as
78+
the entire backing allocation:
79+
80+
```text
81+
[01 02 03]
82+
[?? ?? ??]
83+
```
84+
85+
`??` means the byte is uninitialized. A value whose runtime size cannot be
86+
determined is reported as `<unsupported-unsized>`.
87+
88+
Pointer/provenance spans are planned as part of the raw byte output, using a
89+
compact dump-like marker such as:
90+
91+
```text
92+
[<ptr alloc5+0> 2a 00 00 00]
93+
```
94+
95+
Automatic pointer following is future work and should be explicit, not part of
96+
ordinary value printing. Typed field rendering and dereference/projection-aware
97+
printing are also future work.
98+
7099
EOF also exits Priroda cleanly.
71100

72101
Example:

src/tools/miri/priroda/src/main.rs

Lines changed: 158 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![feature(rustc_private)]
22

33
extern crate miri;
4+
extern crate rustc_abi;
45
extern crate rustc_codegen_ssa;
56
extern crate rustc_data_structures;
67
extern crate rustc_driver;
@@ -16,13 +17,17 @@ extern crate rustc_type_ir;
1617

1718
use std::collections::{HashMap, HashSet};
1819
use std::io::{self, Write};
20+
use std::num::NonZeroU64;
21+
use std::ops::Range;
1922
use std::path::PathBuf;
2023

21-
use miri::Immediate::{Scalar, ScalarPair, Uninit};
22-
use miri::*;
24+
use miri::Immediate::Uninit;
25+
use miri::{interpret, *};
26+
use rustc_abi::Size;
2327
use rustc_driver::Compilation;
2428
use rustc_hir::attrs::CrateType;
2529
use rustc_interface::interface;
30+
use rustc_middle::mir::interpret::AllocId;
2631
use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment};
2732
use rustc_middle::ty::{TyCtxt, TyKind};
2833
use rustc_session::EarlyDiagCtxt;
@@ -377,10 +382,25 @@ impl<'tcx> PrirodaContext<'tcx> {
377382
DebuggerCommand::ListLocals => interp_ok(CommandResult::Locals(self.list_locals())),
378383
DebuggerCommand::Print(local) =>
379384
interp_ok(CommandResult::SingleLocal(self.get_local(local))),
385+
DebuggerCommand::Follow(alloc_id, offset) =>
386+
self.follow_alloc(alloc_id, offset).map(CommandResult::Memory),
380387
DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession),
381388
}
382389
}
383390

391+
fn follow_alloc(&self, alloc_id: AllocId, offset: usize) -> InterpResult<'tcx, String> {
392+
let alloc = self.ecx.get_alloc_raw(alloc_id)?;
393+
if offset > alloc.len() {
394+
return Err(miri::err_unsup_format!(
395+
"allocation offset {offset} is outside {alloc_id}"
396+
))
397+
.into();
398+
}
399+
400+
let memory = self.render_alloc_bytes(alloc_id, offset..alloc.len())?;
401+
interp_ok(format!("Allocation {alloc_id}+{offset}: {memory}"))
402+
}
403+
384404
fn get_local(&self, local: usize) -> Option<LocalDesc> {
385405
let frame = self.ecx.active_thread_stack().last()?;
386406

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

422+
/// Renders the current byte range of an indirect MIR value.
423+
///
424+
/// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`,
425+
/// and complete pointer-sized provenance as pointer markers.
426+
fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> {
427+
let size = match self.ecx.size_and_align_of_val(mplace)? {
428+
Some((size, _)) => size,
429+
None => {
430+
// Extern types cannot currently be executed as by-value locals,
431+
// so this path cannot yet be covered by a Priroda UI fixture.
432+
// FIXME: Add coverage once Priroda supports printing dereferenced places.
433+
return interp_ok("<unsupported-unsized>".to_string());
434+
}
435+
};
436+
437+
let size = size.bytes_usize();
438+
if size == 0 {
439+
return interp_ok("[]".to_string());
440+
}
441+
442+
let (alloc_id, offset, _) =
443+
self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?;
444+
let offset = offset.bytes_usize();
445+
let range = offset..offset.strict_add(size);
446+
447+
self.render_alloc_bytes(alloc_id, range)
448+
}
449+
450+
/// Render a raw allocation range without requiring a typed memory place.
451+
///
452+
/// This is also used by the future-facing `follow` command, where we have a
453+
/// pointer target but do not yet know the target's type or size.
454+
fn render_alloc_bytes(
455+
&self,
456+
alloc_id: AllocId,
457+
range: Range<usize>,
458+
) -> InterpResult<'tcx, String> {
459+
let alloc = self.ecx.get_alloc_raw(alloc_id)?;
460+
461+
let mut rendered = Vec::with_capacity(range.len());
462+
463+
let ptr_size = self.ecx.tcx.data_layout.pointer_size();
464+
465+
for chunk in alloc.init_mask().range_as_init_chunks(range.into()) {
466+
let chunk_range = chunk.range();
467+
let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize();
468+
469+
if chunk.is_init() {
470+
let ptr_size = ptr_size.bytes_usize();
471+
let mut cursor = chunk_range.start;
472+
473+
while cursor < chunk_range.end {
474+
// Full pointer provenance is rendered as a pointer marker. Bytewise
475+
// provenance fragments are intentionally left as raw bytes here: they do
476+
// not represent a complete pointer-sized value.
477+
if let Some(prov) = alloc.provenance().get_ptr(Size::from_bytes(cursor))
478+
&& cursor + ptr_size <= chunk_range.end
479+
{
480+
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(
481+
cursor..cursor + ptr_size,
482+
);
483+
let offset = read_target_uint(self.ecx.tcx.data_layout.endian, bytes)
484+
.map_err(|err| {
485+
miri::err_unsup_format!("invalid pointer representation: {err}")
486+
})?;
487+
488+
let offset = Size::from_bytes(offset);
489+
rendered.push(format!("{:?}", Pointer::new(Some(prov), offset)));
490+
491+
cursor += ptr_size;
492+
} else {
493+
let byte = alloc
494+
.inspect_with_uninit_and_ptr_outside_interpreter(cursor..cursor + 1)[0];
495+
496+
rendered.push(format!("{byte:02x}"));
497+
cursor += 1;
498+
}
499+
}
500+
} else {
501+
rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len()));
502+
}
503+
}
504+
505+
interp_ok(format!("[{}]", rendered.join(" ")))
506+
}
507+
508+
/// Render an evaluated operand using the same raw representation for
509+
/// whole locals and projected MIR places.
510+
fn render_op(&self, op: OpTy<'tcx>) -> String {
511+
match op.as_mplace_or_imm() {
512+
Either::Right(imm) => format!("{imm}"),
513+
514+
Either::Left(mplace) =>
515+
match self.render_mplace_bytes(&mplace).report_err() {
516+
Ok(bytes) => bytes,
517+
Err(err) => format!("<error: {}>", interpret::format_interp_error(err)),
518+
},
519+
}
520+
}
521+
402522
/// Render the source-side path from composite debug info, such as `.field`.
403523
fn render_source_projection(
404524
fragment: Option<&VarDebugInfoFragment<'tcx>>,
@@ -486,22 +606,14 @@ impl<'tcx> PrirodaContext<'tcx> {
486606
None => {
487607
local_desc.value = "<dead>".to_string();
488608
}
489-
Some(Either::Left(_)) => {
490-
local_desc.value = "<indirect>".to_string();
491-
}
492-
Some(Either::Right(imm)) => {
493-
match imm {
494-
Scalar(_) => {
495-
local_desc.value = "<immediate>".to_string();
496-
}
497-
ScalarPair(_, _) => {
498-
local_desc.value = "<immediate-pair>".to_string();
499-
}
500-
501-
Uninit => {
502-
local_desc.value = "<uninit>".to_string();
503-
}
504-
};
609+
Some(Either::Right(Uninit)) => local_desc.value = "<uninit>".to_string(),
610+
611+
Some(Either::Left(_) | Either::Right(_)) => {
612+
let op = self
613+
.ecx
614+
.local_to_op(local, None)
615+
.expect("this error can only occur in CTFE on generic code");
616+
local_desc.value = self.render_op(op);
505617
}
506618
};
507619

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

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

570690
local_descs.push(LocalDesc {
571691
source_name: Some(var_debug_info.name),
572692
source_projection,
573693
local: Some(place.local),
574694
storage_projection,
575695
ty: place.ty(local_decls, self.ecx.tcx.tcx).ty.to_string(),
576-
// FIXME: projection not handled yet.
577-
value: "<unsupported-projection>".to_string(),
696+
value,
578697
});
579698
}
580699
}
@@ -592,6 +711,7 @@ enum DebuggerCommand {
592711
Breakpoint(PathBuf, usize),
593712
ListLocals,
594713
Print(usize),
714+
Follow(AllocId, usize),
595715
}
596716

597717
enum BreakpointSetResult {
@@ -605,6 +725,7 @@ enum CommandResult {
605725
BreakpointResult(BreakpointSetResult),
606726
Locals(Vec<LocalDesc>),
607727
SingleLocal(Option<LocalDesc>),
728+
Memory(String),
608729
// FIXME: distinguish terminating the debugger session from disconnecting a
609730
// frontend and terminating the interpreted program once multiple frontends exist.
610731
TerminateSession,
@@ -693,6 +814,7 @@ impl Cli {
693814
}
694815
None => println!("no local for this id"),
695816
},
817+
CommandResult::Memory(memory) => println!("{memory}"),
696818
CommandResult::TerminateSession => {
697819
println!("quitting");
698820
return interp_ok(());
@@ -725,6 +847,7 @@ impl Cli {
725847
"b" | "break" => self.parse_breakpoint(args),
726848
"l" | "locals" => Some(DebuggerCommand::ListLocals),
727849
"p" | "print" => self.parse_print_local(args),
850+
"f" | "follow" => self.parse_follow(args),
728851
_ => None,
729852
}
730853
}
@@ -757,4 +880,18 @@ impl Cli {
757880
let local = input.parse().ok()?;
758881
Some(DebuggerCommand::Print(local))
759882
}
883+
884+
fn parse_follow(&self, input: &str) -> Option<DebuggerCommand> {
885+
let mut parts = input.split_whitespace();
886+
let alloc_id = parts.next()?;
887+
let offset = parts.next()?;
888+
if parts.next().is_some() {
889+
return None;
890+
}
891+
892+
let alloc_id = alloc_id.strip_prefix("alloc").unwrap_or(alloc_id).parse().ok()?;
893+
let alloc_id = AllocId(NonZeroU64::new(alloc_id)?);
894+
let offset = offset.parse().ok()?;
895+
Some(DebuggerCommand::Follow(alloc_id, offset))
896+
}
760897
}

src/tools/miri/priroda/tests/cli.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
3232
Regex::new(&regex::escape(&manifest_dir.display().to_string())).unwrap();
3333
let miri_dir_regex = Regex::new(&regex::escape(&miri_dir.display().to_string())).unwrap();
3434
let rustc_sysroot_regex = Regex::new(&regex::escape(&rustc_sysroot)).unwrap();
35-
35+
let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap();
3636
config.comment_defaults.base().normalize_stdout.extend([
3737
(manifest_dir_regex.into(), b"{MANIFEST_DIR}".to_vec()),
3838
(miri_dir_regex.into(), b"{MIRI_DIR}".to_vec()),
3939
(rustc_sysroot_regex.into(), b"{RUSTC_SYSROOT}".to_vec()),
40+
(pointer_regex.into(), b"{ALLOC_PTR}".to_vec()),
4041
]);
4142

4243
// Priroda CLI tests do not currently require annotation comments in the test files

src/tools/miri/priroda/tests/ui/locals_access_field.stdout

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
(priroda) Hit breakpoint
33
{MANIFEST_DIR}/tests/ui/locals_access_field.rs:14
44
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
5-
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: <indirect>
6-
Name: _slice, Id: _2, Ty: &[u8], Value: <immediate-pair>
7-
Name: _extra, Id: _3, Ty: u32, Value: <immediate>
5+
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
6+
Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
7+
Name: _extra, Id: _3, Ty: u32, Value: 0_u32
88
(priroda) Id: _0, Ty: (), Value: <uninit>
9-
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: <indirect>
10-
(priroda) Id: _2, Ty: &[u8], Value: <immediate-pair>
11-
(priroda) Id: _3, Ty: u32, Value: <immediate>
9+
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
10+
(priroda) Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
11+
(priroda) Id: _3, Ty: u32, Value: 0_u32
1212
(priroda) no local for this id
1313
(priroda) no local for this id
1414
(priroda) quitting

src/tools/miri/priroda/tests/ui/locals_corpus_async.stdin

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ continue
1212
locals
1313
continue
1414
locals
15+
follow 2 0
1516
continue
1617
locals
1718
continue

0 commit comments

Comments
 (0)