authorbors <bors@rust-lang.org> 2025-09-03 23:20:07 UTC
committerbors <bors@rust-lang.org> 2025-09-03 23:20:07 UTC
log9385c64c95d971329e62917adc4349c8ccdbafe0
treee3a882c904fa21b5003f75a6da48d44ca6ba5f50
parenta1208bf765ba783ee4ebdc4c29ab0a0c215806ef
parent86670347189962f15061404526acab2a6bb0da8f

Auto merge of #146156 - RalfJung:miri, r=RalfJung

miri subtree update Subtree update of `miri` to https://github.com/rust-lang/miri/commit/883dbf03e2a82ed7922122d8c2c2c60a5844b90d. Created using https://github.com/rust-lang/josh-sync. r? `@ghost`

63 files changed, 1665 insertions(+), 632 deletions(-)

Cargo.lock+2-2
......@@ -3204,9 +3204,9 @@ dependencies = [
32043204
32053205[[package]]
32063206name = "rustc-build-sysroot"
3207version = "0.5.9"
3207version = "0.5.11"
32083208source = "registry+https://github.com/rust-lang/crates.io-index"
3209checksum = "fdb13874a0e55baf4ac3d49d38206aecb31a55b75d6c4d04fd850b53942c8cc8"
3209checksum = "3b881c015c729b43105bbd3702a9bdecee28fafaa21126d1d62e454ec011a4b7"
32103210dependencies = [
32113211 "anyhow",
32123212 "rustc_version",
src/tools/miri/.github/workflows/ci.yml+5
......@@ -41,6 +41,11 @@ jobs:
4141 multiarch: s390x
4242 gcc_cross: s390x-linux-gnu
4343 qemu: true
44 - host_target: powerpc64le-unknown-linux-gnu
45 os: ubuntu-latest
46 multiarch: ppc64el
47 gcc_cross: powerpc64le-linux-gnu
48 qemu: true
4449 - host_target: aarch64-apple-darwin
4550 os: macos-latest
4651 - host_target: i686-pc-windows-msvc
src/tools/miri/CONTRIBUTING.md+6
......@@ -255,6 +255,12 @@ when installing the Miri toolchain. Alternatively, set the `RUSTUP_TOOLCHAIN` en
255255
256256[`etc/rust_analyzer_helix.toml`]: https://github.com/rust-lang/miri/blob/master/etc/rust_analyzer_helix.toml
257257
258### Zed
259
260Copy [`etc/rust_analyzer_zed.json`] to `.zed/settings.json` in the project root directory.
261
262[`etc/rust_analyzer_zed.json`]: https://github.com/rust-lang/miri/blob/master/etc/rust_analyzer_zed.json
263
258264### Advanced configuration
259265
260266If you are building Miri with a locally built rustc, set
src/tools/miri/Cargo.lock+2-2
......@@ -1569,9 +1569,9 @@ dependencies = [
15691569
15701570[[package]]
15711571name = "tracing-subscriber"
1572version = "0.3.19"
1572version = "0.3.20"
15731573source = "registry+https://github.com/rust-lang/crates.io-index"
1574checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
1574checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
15751575dependencies = [
15761576 "sharded-slab",
15771577 "thread_local",
src/tools/miri/Cargo.toml+1-1
......@@ -39,7 +39,7 @@ features = ['unprefixed_malloc_on_supported_platforms']
3939[target.'cfg(unix)'.dependencies]
4040libc = "0.2"
4141# native-lib dependencies
42libffi = { version = "4.0.0", optional = true }
42libffi = { version = "4.1.1", optional = true }
4343libloading = { version = "0.8", optional = true }
4444serde = { version = "1.0.219", features = ["derive"], optional = true }
4545
src/tools/miri/README.md+6
......@@ -319,8 +319,14 @@ environment variable. We first document the most relevant and most commonly used
319319 Can be used without a value; in that case the range defaults to `0..64`.
320320* `-Zmiri-many-seeds-keep-going` tells Miri to really try all the seeds in the given range, even if
321321 a failing seed has already been found. This is useful to determine which fraction of seeds fails.
322* `-Zmiri-max-extra-rounding-error` tells Miri to always apply the maximum error to float operations
323 that do not have a guaranteed precision. The sign of the error is still non-deterministic.
322324* `-Zmiri-no-extra-rounding-error` stops Miri from adding extra rounding errors to float operations
323325 that do not have a guaranteed precision.
326* `-Zmiri-no-short-fd-operations` stops Miri from artificially forcing `read`/`write` operations
327 to only process a part of their buffer. Note that whenever Miri uses host operations to
328 implement `read`/`write` (e.g. for file-backed file descriptors), the host system can still
329 introduce short reads/writes.
324330* `-Zmiri-num-cpus` states the number of available CPUs to be reported by miri. By default, the
325331 number of available CPUs is `1`. Note that this flag does not affect how miri handles threads in
326332 any way.
src/tools/miri/cargo-miri/Cargo.lock+2-2
......@@ -429,9 +429,9 @@ dependencies = [
429429
430430[[package]]
431431name = "rustc-build-sysroot"
432version = "0.5.9"
432version = "0.5.10"
433433source = "registry+https://github.com/rust-lang/crates.io-index"
434checksum = "fdb13874a0e55baf4ac3d49d38206aecb31a55b75d6c4d04fd850b53942c8cc8"
434checksum = "dd41ead66a69880951b2f7df3139db401d44451b4da123344d27eaa791b89c95"
435435dependencies = [
436436 "anyhow",
437437 "rustc_version",
src/tools/miri/cargo-miri/Cargo.toml+1-1
......@@ -18,7 +18,7 @@ directories = "6"
1818rustc_version = "0.4"
1919serde_json = "1.0.40"
2020cargo_metadata = "0.21"
21rustc-build-sysroot = "0.5.8"
21rustc-build-sysroot = "0.5.10"
2222
2323# Enable some feature flags that dev-dependencies need but dependencies
2424# do not. This makes `./miri install` after `./miri build` faster.
src/tools/miri/cargo-miri/src/phases.rs+27-12
......@@ -65,16 +65,6 @@ fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut
6565}
6666
6767pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
68 // Check for version and help flags even when invoked as `cargo-miri`.
69 if has_arg_flag("--help") || has_arg_flag("-h") {
70 show_help();
71 return;
72 }
73 if has_arg_flag("--version") || has_arg_flag("-V") {
74 show_version();
75 return;
76 }
77
7868 // Require a subcommand before any flags.
7969 // We cannot know which of those flags take arguments and which do not,
8070 // so we cannot detect subcommands later.
......@@ -85,11 +75,36 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
8575 "setup" => MiriCommand::Setup,
8676 "test" | "t" | "run" | "r" | "nextest" => MiriCommand::Forward(subcommand),
8777 "clean" => MiriCommand::Clean,
88 _ =>
78 _ => {
79 // Check for version and help flags.
80 if has_arg_flag("--help") || has_arg_flag("-h") {
81 show_help();
82 return;
83 }
84 if has_arg_flag("--version") || has_arg_flag("-V") {
85 show_version();
86 return;
87 }
8988 show_error!(
9089 "`cargo miri` supports the following subcommands: `run`, `test`, `nextest`, `clean`, and `setup`."
91 ),
90 )
91 }
9292 };
93 if has_arg_flag("--help") || has_arg_flag("-h") {
94 match subcommand {
95 MiriCommand::Forward(verb) => {
96 println!("`cargo miri {verb}` supports the same flags as `cargo {verb}`:\n");
97 let mut cmd = cargo();
98 cmd.arg(verb);
99 cmd.arg("--help");
100 exec(cmd);
101 }
102 _ => {
103 show_help();
104 return;
105 }
106 }
107 }
93108 let verbose = num_arg_flag("-v") + num_arg_flag("--verbose");
94109 let quiet = has_arg_flag("-q") || has_arg_flag("--quiet");
95110
src/tools/miri/doc/img/perfetto_aggregate_statistics.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_aggregate_statistics.png differ
src/tools/miri/doc/img/perfetto_aggregate_statistics_sql.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_aggregate_statistics_sql.png differ
src/tools/miri/doc/img/perfetto_span.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_span.png differ
src/tools/miri/doc/img/perfetto_subname_statistics.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_subname_statistics.png differ
src/tools/miri/doc/img/perfetto_timeline.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_timeline.png differ
src/tools/miri/doc/img/perfetto_visualize_argument_values.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_visualize_argument_values.png differ
src/tools/miri/doc/img/perfetto_visualize_argument_values_misbehaving.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_visualize_argument_values_misbehaving.png differ
src/tools/miri/doc/img/perfetto_visualize_argument_values_sql.png
Binary files /dev/null and b/src/tools/miri/doc/img/perfetto_visualize_argument_values_sql.png differ
src/tools/miri/doc/tracing.md created+292
......@@ -0,0 +1,292 @@
1# Documentation for the tracing infrastructure in Miri
2
3Miri can be traced to understand how much time is spent in its various components (e.g. borrow tracker, data race checker, etc.). When tracing is enabled, running Miri will create a `.json` trace file that can be opened and analyzed in [Perfetto](https://ui.perfetto.dev/). For any questions regarding this documentation you may contact [Stypox](https://rust-lang.zulipchat.com/#narrow/dm/627563-Stypox) on Zulip.
4
5## Obtaining a trace file
6
7### From the Miri codebase
8
9All of the tracing functionality in Miri is gated by the `"tracing"` feature flag to ensure it does not create any overhead when unneeded. To compile Miri with this feature enabled, you can pass `--features=tracing` to `./miri`. Then, to make running Miri actually produce a trace file, you also need to set the `MIRI_TRACING` environment variable. For example:
10
11```sh
12MIRI_TRACING=1 ./miri run --features=tracing ./tests/pass/hello.rs
13```
14
15### From the rustc codebase
16
17If you are building Miri from within the rustc tree, you need to enable the `"tracing"` feature by adding this line to `bootstrap.toml`:
18
19```toml
20build.tool.miri.features = ["tracing"]
21```
22
23And then you could run the following:
24
25```sh
26MIRI_TRACING=1 ./x.py run miri --stage 1 --args ./src/tools/miri/tests/pass/hello.rs
27```
28
29### The trace file
30
31After running Miri with tracing enabled you will get a `.json` trace file that contains a list of all events and spans that occurred throughout the execution. The file follows [this format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview).
32
33## Analyzing a trace file
34
35To analyze traces you can use [Perfetto UI](https://ui.perfetto.dev/), a trace analyzer made by Google that was originally a part of the Chrome browser. Just open Perfetto and drag and drop the `.json` file there. Official documentation for the controls in the UI can be found [here](https://perfetto.dev/docs/visualization/perfetto-ui).
36
37### The timeline
38
39You will see the boxes "Global Legacy Events" and "Process 1" on the left of the workspace: after clicking on either of them their timeline will expand and you will be able to zoom in and look at individual spans (and events).
40
41- "Process 1" contains tracing spans for the various components of Miri, all in a single timeline line (e.g. borrow tracker, data race checker, etc.)
42- "Global Legacy Events" contains auxiliary spans on two separate lines that allow understanding what code is being executed at any point in time:
43 - "frame": what is the current stack frame in the interpreted program
44 - "step": what statement/terminator in the MIR of the interpreted program is being executed
45
46Spans are represented as colored boxes in the timeline, while instantaneous events are represented by tiny arrows. (Events exist because rustc and Miri also use the `tracing` crate for debug logging, and those logs turn into events in the trace.)
47
48![](./img/perfetto_timeline.png)
49
50### Span/event data
51
52You can click on a span or an event to get more information about it, including some arguments that were passed when the span/event was entered/fired. In the following screenshot you can see the details of a "layouting" span that was generated by the following line in Miri's code:
53
54```rust
55let _trace = enter_trace_span!(M, layouting::fn_abi_of_instance, ?instance, ?extra_args);
56```
57
58![](./img/perfetto_span.png)
59
60### SQL tables
61
62Perfetto supports querying the span/event database using SQL queries (see the [docs](https://perfetto.dev/docs/analysis/perfetto-sql-syntax)). Just type `:` in the search bar at the top to enter SQL mode, and then you will be able to enter SQL queries there. The relevant SQL tables are:
63- `slices`: contains all spans and events; events can be distinguished from spans since their `dur` is 0. Relevant columns are:
64 - `id`: a unique primary-key ID for the span (assigned by Perfetto, not present in the trace file)
65 - `ts` and `dur`: the beginning and duration of the span, in nanoseconds
66 - `name`: the name of the span
67 - `parent_id`: the parent span ID, or null if there is no parent (assigned by Perfetto based on the timing at which spans occur, i.e. two nested spans must be one the child of the other)
68 - `arg_set_id`: a foreign key into the table of arguments (1-to-N)
69- `args`: contains all of the arguments of the various events/spans. Relevant columns are:
70 - `arg_set_id`: the key used to join the slices and args tables
71 - `key`: the name of the argument prepended with "args."
72 - `display_value`: the value of the argument
73
74Some useful queries are provided in the following sections.
75
76### Enhancing the timeline
77
78On the "Process 1" timeline line there are some spans with the same name, that are actually generated from different places in Miri's code. In those cases the span name indicates the component that was invoked (e.g. the data race checker), but not the specific function that was run. To inspect the specific function, we store a "subname" in an argument with the same name as the span, which unfortunately can be seen only after clicking on the span.
79
80To make it quicker to look at subnames, you can add a new timeline line that specifically shows the subnames for spans with a specific name. To do so:
811. select any span with the name you care about (call this name `$NAME`)
822. click on the dropdown highlighted in blue next on the argument with name `$NAME` (or `args.$NAME`)
833. click on "Visualize argument values"
844. a new timeline line will appear with only spans originally named `$NAME`, but now with the subname displayed instead
85
86The following screenshot shows the 4 steps for spans named "data_race":
87
88![](./img/perfetto_visualize_argument_values.png)
89
90### Visualizing which "frame" or "step" is being executed
91
92Unfortunately the instructions in [Enhancing the timeline](#enhancing-the-timeline) only work well with spans under "Process 1", but misbehave with spans under "Global Legacy Events" (see the screenshot below). This might be a bug in Perfetto, but nevertheless a workaround is available:
93
941. click on the search bar at the top and write `:` to enter SQL mode
952. copy-paste the following SQL, replace "SPAN_NAME" at the end with either "frame" or "step" (i.e. one of the two span names under "Global Legacy Events"), and press Enter to execute it:
96 ```sql
97 select slices.id, ts, dur, track_id, category, args.string_value as name, depth, stack_id, parent_stack_id, parent_id, slices.arg_set_id, thread_ts, thread_instruction_count, thread_instruction_delta, cat, slice_id
98 from slices inner join args using (arg_set_id)
99 where args.key = "args." || name and name = "SPAN_NAME"
100 ```
1013. at the top-right of the box at the bottom, click on "Show debug track"
1024. press on "Show" in the popup that just appeared
1035. a new debug track will appear with the names of steps or frames
104
105What the SQL does is to select only spans with the name "SPAN_NAME" and keep all of the span fields untouched, except for the name which is replaced with the subname. As explained in [Enhancing the timeline](#enhancing-the-timeline), remember that the subname is stored in an argument with the same name as the span.
106
107![](./img/perfetto_visualize_argument_values_sql.png)
108
109<img src="./img/perfetto_visualize_argument_values_misbehaving.png" width="300px">
110
111### Compute aggregate statistics
112
113The simplest way to get aggregate statistics about a time range is to:
114
1151. select a time range by drag-clicking along a trace line
1162. click on the "Current Selection" tab at the bottom if it's not already open
1173. see various tables/visualizations of how much time is spent in each span by clicking on "Slices", "Pivot Table" or "Slice Flamegraph"
118
119Note that the numbers shown in the "Slices" and "Pivot Table" tabs also include nested spans, so they cannot be used to compute statistics such as "X% of time is spent in spans named Y" because two spans named Y might be nested and their duration would be counted twice. For such statistics use the method in [Compute aggregate statistics (enhanced)](#compute-aggregate-statistics-enhanced).
120
121![](./img/perfetto_aggregate_statistics.png)
122
123### Compute aggregate statistics (enhanced)
124
125The following (long but not complicated) query can be used to find out how much time is spent in spans (grouped by their name). Only spans without a parent are considered towards the computations (see `where parent_id is null`): so for example if `validate_operand` in turn calls `layouting` (which generates a nested/child span), only the `validate_operand` statistics are increased. This query also excludes auxiliary spans (see `name != "frame" and name != "step"`).
126
127Note that this query does not allow selecting a time range, but that can be done by adding a condition, e.g. `ts + dur > MIN_T and ts < MAX_T` would match only spans that intersect the range `(MIN_T, MAX_T)`. Remember that the time unit is nanoseconds.
128
129```sql
130select "TOTAL PROGRAM DURATION" as name, count(*), max(ts + dur) as "sum(dur)", 100.0 as "%", null as "min(dur)", null as "max(dur)", null as "avg(dur)", null as "stddev(dur)"
131from slices
132
133union select "TOTAL OVER ALL SPANS (excluding events)" as name, count(*), sum(dur), cast(cast(sum(dur) as float) / (select max(ts + dur) from slices) * 1000 as int) / 10.0 as "%", min(dur), max(dur), cast(avg(dur) as int) as "avg(dur)", cast(sqrt(avg(dur*dur)-avg(dur)*avg(dur)) as int) as "stddev(dur)"
134from slices
135where parent_id is null and name != "frame" and name != "step" and dur > 0
136
137union select name, count(*), sum(dur), cast(cast(sum(dur) as float) / (select max(ts + dur) from slices) * 1000 as int) / 10.0 as "%", min(dur), max(dur), cast(avg(dur) as int) as "avg(dur)", cast(sqrt(avg(dur*dur)-avg(dur)*avg(dur)) as int) as "stddev(dur)"
138from slices
139where parent_id is null and name != "frame" and name != "step"
140group by name
141order by sum(dur) desc, count(*) desc
142```
143
144This is the kind of table you would get out:
145
146![](./img/perfetto_aggregate_statistics_sql.png)
147
148### Statistics about subnames of a span
149
150Use the following SQL to see statistics about the subnames of spans with the same name (replace "SPAN_NAME" with the name of the span you want to see subname statistics of):
151
152```sql
153select args.string_value as name, count(*), sum(dur), min(dur), max(dur), cast(avg(dur) as int) as "avg(dur)", cast(sqrt(avg(dur*dur)-avg(dur)*avg(dur)) as int) as "stddev(dur)"
154from slices inner join args using (arg_set_id)
155where args.key = "args." || name and name = "SPAN_NAME"
156group by args.string_value
157order by count(*) desc
158```
159
160For example, this is the table of how much time is spent in each borrow tracker function:
161
162![](./img/perfetto_subname_statistics.png)
163
164### Finding long periods of time without any tracing
165
166The following SQL finds the longest periods of time where time is being spent, with the ability to click on IDs in the table of results to quickly reach the corresponding place. This can be useful to spot things that use up a significant amount of time but that are not yet covered by tracing calls.
167
168```sql
169with ordered as (
170 select s1.*, row_number() over (order by s1.ts) as rn
171 from slices as s1
172 where s1.parent_id is null and s1.dur > 0 and s1.name != "frame" and s1.name != "step"
173)
174select a.ts+a.dur as ts, b.ts-a.ts-a.dur as dur, a.id, a.track_id, a.category, a.depth, a.stack_id, a.parent_stack_id, a.parent_id, a.arg_set_id, a.thread_ts, a.thread_instruction_count, a.thread_instruction_delta, a.cat, a.slice_id, "empty" as name
175from ordered as a inner join ordered as b on a.rn=b.rn-1
176order by b.ts-a.ts-a.dur desc
177```
178
179### Saving Perfetto's state as a preset
180
181Unfortunately Perfetto does not seem to support saving the UI state as a preset that can be used to repeat the same analysis on multiple traces. You have to click through the various menus or run the various SQL queries every time to setup the UI as you want.
182
183## Adding new tracing calls to the code
184
185### The "tracing" feature
186
187Miri is highly interconnected with `rustc_const_eval`, and therefore collecting proper trace data about Miri also involves adding some tracing calls within `rustc_const_eval`'s codebase. As explained in [Obtaining a trace file](#obtaining-a-trace-file), tracing calls are disabled (and optimized out) when Miri's "tracing" feature is not enabled. However, while it is possible to check for the feature from Miri's codebase, it's not possible to do so from `rustc_const_eval` (since it's a separate crate, and it's even in a precompiled `.rlib` in case of out-of-tree builds).
188
189The solution to make it possible to check whether tracing is enabled at compile time even in `rustc_const_eval` was to add a function with this signature to the `Machine` trait:
190```rust
191fn enter_trace_span(span: impl FnOnce() -> tracing::Span) -> impl EnteredTraceSpan
192```
193
194where `EnteredTraceSpan` is just a marker trait implemented by `()` and `tracing::span::EnteredSpan`. This function returns `()` by default (without calling the `span` closure), except in `MiriMachine` where if tracing is enabled it will return `span().entered()`.
195
196The code in `rustc_const_eval` calls this function when it wants to do tracing, and the compiler will (hopefully) optimize out tracing calls when tracing is disabled.
197
198### The `enter_trace_span!()` macro
199
200To add tracing to a section of code in Miri or in `rustc_const_eval`, you can use the `enter_trace_span!()` macro, which takes care of the details explained in [The "tracing" feature](#the-tracing-feature).
201
202The `enter_trace_span!()` macro accepts the same syntax as `tracing::span!()` ([documentation](https://docs.rs/tracing/latest/tracing/#using-the-macros)) except for a few customizations, and returns an already entered trace span. The returned value is a drop guard that will exit the span when dropped, so **make sure to give it a proper scope** by storing it in a variable like this:
203
204```rust
205let _trace = enter_trace_span!("My span");
206```
207
208When calling this macro from `rustc_const_eval` you need to pass a type implementing the `Machine` trait as the first argument (since it will be used to call `Machine::enter_trace_span()`). This is usually available in various parts of `rustc_const_eval` under the name `M`, since most of `rustc_const_eval`'s code is `Machine`-agnostic.
209
210```rust
211let _trace = enter_trace_span!("My span"); // from Miri
212let _trace = enter_trace_span!(M, "My span"); // from rustc_const_eval
213```
214
215You can make sense of the syntaxes explained below also by looking at this Perfetto screenshot from [Span/event data](#spanevent-data).
216
217![](./img/perfetto_span.png)
218
219### Syntax accepted by `tracing::span!()`
220
221The full documentation for the `tracing::span!()` syntax can be found [here](https://docs.rs/tracing/latest/tracing/#using-the-macros) under "Using the Macros". A few possibly confusing syntaxes are listed here:
222```rust
223// logs a span named "hello" with a field named "arg" of value 42 (works only because
224// 42 implements the tracing::Value trait, otherwise use one of the options below)
225let _trace = enter_trace_span!(M, "hello", arg = 42);
226// logs a field called "my_display_var" using the Display implementation
227let _trace = enter_trace_span!(M, "hello", %my_display_var);
228// logs a field called "my_debug_var" using the Debug implementation
229let _trace = enter_trace_span!(M, "hello", ?my_debug_var);
230```
231
232### `NAME::SUBNAME` syntax
233
234In addition to the syntax accepted by `tracing::span!()`, the `enter_trace_span!()` macro optionally allows passing the span name (i.e. the first macro argument) in the form `NAME::SUBNAME` (without quotes) to indicate that the span has name "NAME" (usually the name of the component) and has an additional more specific name "SUBNAME" (usually the function name). The latter is passed to the tracing crate as a span field with the name "NAME". This allows not being distracted by subnames when looking at the trace in Perfetto, but when deeper introspection is needed within a component, it's still possible to view the subnames directly with a few steps (see [Enhancing the timeline](#enhancing-the-timeline)).
235```rust
236// for example, the first will expand to the second
237let _trace = enter_trace_span!(M, borrow_tracker::on_stack_pop);
238let _trace = enter_trace_span!(M, "borrow_tracker", borrow_tracker = "on_stack_pop");
239```
240
241### `tracing_separate_thread` parameter
242
243Miri saves traces using the the `tracing_chrome` `tracing::Layer` so that they can be visualized in Perfetto. To instruct `tracing_chrome` to put some spans on a separate trace thread/line than other spans when viewed in Perfetto, you can pass `tracing_separate_thread = tracing::field::Empty` to the tracing macros. This is useful to separate out spans which just indicate the current step or program frame being processed by the interpreter. As explained in [The timeline](#the-timeline), those spans end up under the "Global Legacy Events" track. You should use a value of `tracing::field::Empty` so that other tracing layers (e.g. the logger) will ignore the `tracing_separate_thread` field. For example:
244```rust
245let _trace = enter_trace_span!(M, step::eval_statement, tracing_separate_thread = tracing::field::Empty);
246```
247
248### Executing something else when tracing is disabled
249
250The `EnteredTraceSpan` trait contains a `or_if_tracing_disabled()` function that you can use to e.g. log a line as an alternative to the tracing span for when tracing is disabled. For example:
251```rust
252let _trace = enter_trace_span!(M, step::eval_statement)
253 .or_if_tracing_disabled(|| tracing::info!("eval_statement"));
254```
255
256## Implementation details
257
258Here we explain how tracing is implemented internally.
259
260The events and spans generated throughout the codebase are collected by [the `tracing` crate](https://crates.io/crates/tracing), which then dispatches them to the code that writes to the trace file, but also to the logger if logging is enabled.
261
262### Choice of tracing library
263
264The crate that was chosen for collecting traces is [tracing](https://crates.io/crates/tracing), since:
265- it is very well maintained
266- it supports various different trace formats through plug-and-play `Layer`s (in Miri we are using `tracing_chrome` to export traces for perfetto, see [The `tracing_chrome` layer](#the-tracing_chrome-layer))
267- spans and events are collected with not just their name, but also file, line, module, and any number of custom arguments
268- it was already used in Miri and rustc as a logging framework
269
270One major drawback of the tracing crate is, however, its big overhead. Entering and exiting a span takes on the order of 100ns, and many of Miri's spans are shorter than that, so their measurements are completely off and the program execution increases significantly. E.g. at the point of writing this documentation, enabling tracing makes Miri 5x slower. Note that this used to be even worse, see [Time measurements](#time-measurements).
271
272### The `tracing_chrome` layer
273
274Miri uses [tracing-chrome](https://github.com/thoren-d/tracing-chrome) as the `Layer` that collects spans and events from the tracing crate and saves them to a file that can be opened in Perfetto. Although the crate [is published](https://crates.io/crates/tracing-chrome) on crates.io, it was not possible to depend on it from Miri, because it would bring in a separate compilation of the `tracing` crate. This is because Miri does not directly depend on `tracing`, and instead uses rustc's version through rustc-private, and apparently cargo can't realize that the same library is being built again when rustc-private is involved.
275
276So the solution was to copy-paste [the only file](https://github.com/thoren-d/tracing-chrome/blob/develop/src/lib.rs) in tracing-chrome into Miri. Nevertheless, this gave the possibility to make some changes to tracing-chrome, which you can read about in documentation at the top of [the file](https://github.com/rust-lang/miri/blob/master/src/bin/log/tracing_chrome.rs) that was copied to Miri.
277
278### Time measurements
279
280tracing-chrome originally used `std::time::Instant` to measure time, however on some x86/x86_64 Linux systems it might be unbearably slow since the underlying system call (`clock_gettime`) would take ≈1.3µs. Read more [here](https://btorpey.github.io/blog/2014/02/18/clock-sources-in-linux/) about how the Linux kernel chooses the clock source.
281
282Therefore, on x86/x86_64 Linux systems with a CPU that has an invariant TSC counter, we read from that instead to measure time, which takes only ≈13ns. There are unfortunately a lot of caveats to this approach though, as explained [in the code](https://github.com/rust-lang/miri/blob/master/src/bin/log/tracing_chrome_instant.rs) and [in the PR](https://github.com/rust-lang/miri/pull/4524). The most impactful one is that: every thread spawned in Miri that wants to trace something (which requires measuring time) needs to pin itself to a single CPU core (using `sched_setaffinity`).
283
284## Other useful stuff
285
286### Making a flamegraph
287
288After compiling Miri, you can run the following command to make a flamegraph using Linux' `perf`. It can be useful to spot functions that use up a significant amount of time but that are not yet covered by tracing calls.
289
290```sh
291perf record --call-graph dwarf -F 999 ./miri/target/debug/miri --edition 2021 --sysroot ~/.cache/miri ./tests/pass/hashmap.rs && perf script | inferno-collapse-perf | inferno-flamegraph > flamegraph.svg
292```
src/tools/miri/etc/rust_analyzer_zed.json created+41
......@@ -0,0 +1,41 @@
1{
2 "lsp": {
3 "rust-analyzer": {
4 "initialization_options": {
5 "rustc": {
6 "source": "discover"
7 },
8 "linkedProjects": [
9 "./Cargo.toml",
10 "./cargo-miri/Cargo.toml",
11 "./genmc-sys/Cargo.toml",
12 "./miri-script/Cargo.toml"
13 ],
14 "check": {
15 "invocationStrategy": "once",
16 "overrideCommand": [
17 "./miri",
18 "clippy", // make this `check` when working with a locally built rustc
19 "--message-format=json"
20 ]
21 },
22 "cargo": {
23 "extraEnv": {
24 "MIRI_AUTO_OPS": "no",
25 "MIRI_IN_RA": "1"
26 },
27 // Contrary to what the name suggests, this also affects proc macros.
28 "buildScripts": {
29 "invocationStrategy": "once",
30 "overrideCommand": [
31 "./miri",
32 "check",
33 "--no-default-features",
34 "--message-format=json"
35 ]
36 }
37 }
38 }
39 }
40 }
41}
src/tools/miri/rust-version+1-1
......@@ -1 +1 @@
1f605b57042ffeb320d7ae44490113a827139b766
151ff895062ba60a7cba53f57af928c3fb7b0f2f4
src/tools/miri/src/bin/log/mod.rs+1
......@@ -1,2 +1,3 @@
11pub mod setup;
22mod tracing_chrome;
3mod tracing_chrome_instant;
src/tools/miri/src/bin/log/tracing_chrome.rs+99-78
......@@ -7,12 +7,15 @@
77//! (`git log -- path/to/tracing_chrome.rs`), but in summary:
88//! - the file attributes were changed and `extern crate` was added at the top
99//! - if a tracing span has a field called "tracing_separate_thread", it will be given a separate
10//! span ID even in [TraceStyle::Threaded] mode, to make it appear on a separate line when viewing
11//! the trace in <https://ui.perfetto.dev>. This is the syntax to trigger this behavior:
10//! span ID even in [TraceStyle::Threaded] mode, to make it appear on a separate line when viewing
11//! the trace in <https://ui.perfetto.dev>. This is the syntax to trigger this behavior:
1212//! ```rust
1313//! tracing::info_span!("my_span", tracing_separate_thread = tracing::field::Empty, /* ... */)
1414//! ```
15//! - use i64 instead of u64 for the "id" in [ChromeLayer::get_root_id] to be compatible with Perfetto
15//! - use i64 instead of u64 for the "id" in [ChromeLayer::get_root_id] to be compatible with
16//! Perfetto
17//! - use [ChromeLayer::with_elapsed_micros_subtracting_tracing] to make time measurements faster on
18//! Linux x86/x86_64 and to subtract time spent tracing from the timestamps in the trace file
1619//!
1720//! Depending on the tracing-chrome crate from crates.io is unfortunately not possible, since it
1821//! depends on `tracing_core` which conflicts with rustc_private's `tracing_core` (meaning it would
......@@ -50,9 +53,22 @@ use std::{
5053 thread::JoinHandle,
5154};
5255
56use crate::log::tracing_chrome_instant::TracingChromeInstant;
57
58/// Contains thread-local data for threads that send tracing spans or events.
59struct ThreadData {
60 /// A unique ID for this thread, will populate "tid" field in the output trace file.
61 tid: usize,
62 /// A clone of [ChromeLayer::out] to avoid the expensive operation of accessing a mutex
63 /// every time. This is used to send [Message]s to the thread that saves trace data to file.
64 out: Sender<Message>,
65 /// The instant in time this thread was started. All events happening on this thread will be
66 /// saved to the trace file with a timestamp (the "ts" field) measured relative to this instant.
67 start: TracingChromeInstant,
68}
69
5370thread_local! {
54 static OUT: RefCell<Option<Sender<Message>>> = const { RefCell::new(None) };
55 static TID: RefCell<Option<usize>> = const { RefCell::new(None) };
71 static THREAD_DATA: RefCell<Option<ThreadData>> = const { RefCell::new(None) };
5672}
5773
5874type NameFn<S> = Box<dyn Fn(&EventOrSpan<'_, '_, S>) -> String + Send + Sync>;
......@@ -64,7 +80,6 @@ where
6480 S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
6581{
6682 out: Arc<Mutex<Sender<Message>>>,
67 start: std::time::Instant,
6883 max_tid: AtomicUsize,
6984 include_args: bool,
7085 include_locations: bool,
......@@ -323,7 +338,6 @@ where
323338{
324339 fn new(mut builder: ChromeLayerBuilder<S>) -> (ChromeLayer<S>, FlushGuard) {
325340 let (tx, rx) = mpsc::channel();
326 OUT.with(|val| val.replace(Some(tx.clone())));
327341
328342 let out_writer = builder
329343 .out_writer
......@@ -443,7 +457,6 @@ where
443457 };
444458 let layer = ChromeLayer {
445459 out: Arc::new(Mutex::new(tx)),
446 start: std::time::Instant::now(),
447460 max_tid: AtomicUsize::new(0),
448461 name_fn: builder.name_fn.take(),
449462 cat_fn: builder.cat_fn.take(),
......@@ -456,22 +469,7 @@ where
456469 (layer, guard)
457470 }
458471
459 fn get_tid(&self) -> (usize, bool) {
460 TID.with(|value| {
461 let tid = *value.borrow();
462 match tid {
463 Some(tid) => (tid, false),
464 None => {
465 let tid = self.max_tid.fetch_add(1, Ordering::SeqCst);
466 value.replace(Some(tid));
467 (tid, true)
468 }
469 }
470 })
471 }
472
473 fn get_callsite(&self, data: EventOrSpan<S>) -> Callsite {
474 let (tid, new_thread) = self.get_tid();
472 fn get_callsite(&self, data: EventOrSpan<S>, tid: usize) -> Callsite {
475473 let name = self.name_fn.as_ref().map(|name_fn| name_fn(&data));
476474 let target = self.cat_fn.as_ref().map(|cat_fn| cat_fn(&data));
477475 let meta = match data {
......@@ -502,14 +500,6 @@ where
502500 (None, None)
503501 };
504502
505 if new_thread {
506 let name = match std::thread::current().name() {
507 Some(name) => name.to_owned(),
508 None => tid.to_string(),
509 };
510 self.send_message(Message::NewThread(tid, name));
511 }
512
513503 Callsite {
514504 tid,
515505 name,
......@@ -548,31 +538,55 @@ where
548538 }
549539 }
550540
551 fn enter_span(&self, span: SpanRef<S>, ts: f64) {
552 let callsite = self.get_callsite(EventOrSpan::Span(&span));
541 fn enter_span(&self, span: SpanRef<S>, ts: f64, tid: usize, out: &Sender<Message>) {
542 let callsite = self.get_callsite(EventOrSpan::Span(&span), tid);
553543 let root_id = self.get_root_id(span);
554 self.send_message(Message::Enter(ts, callsite, root_id));
544 let _ignored = out.send(Message::Enter(ts, callsite, root_id));
555545 }
556546
557 fn exit_span(&self, span: SpanRef<S>, ts: f64) {
558 let callsite = self.get_callsite(EventOrSpan::Span(&span));
547 fn exit_span(&self, span: SpanRef<S>, ts: f64, tid: usize, out: &Sender<Message>) {
548 let callsite = self.get_callsite(EventOrSpan::Span(&span), tid);
559549 let root_id = self.get_root_id(span);
560 self.send_message(Message::Exit(ts, callsite, root_id));
550 let _ignored = out.send(Message::Exit(ts, callsite, root_id));
561551 }
562552
563 fn get_ts(&self) -> f64 {
564 self.start.elapsed().as_nanos() as f64 / 1000.0
565 }
553 /// Helper function that measures how much time is spent while executing `f` and accounts for it
554 /// in subsequent calls, with the aim to reduce biases in the data collected by `tracing_chrome`
555 /// by subtracting the time spent inside tracing functions from the timeline. This makes it so
556 /// that the time spent inside the `tracing_chrome` functions does not impact the timestamps
557 /// inside the trace file (i.e. `ts`), even if such functions are slow (e.g. because they need
558 /// to format arguments on the same thread those arguments are collected on, otherwise memory
559 /// safety would be broken).
560 ///
561 /// `f` is called with the microseconds elapsed since the current thread was started (**not**
562 /// since the program start!), with the current thread ID (i.e. `tid`), and with a [Sender] that
563 /// can be used to send a [Message] to the thread that collects [Message]s and saves them to the
564 /// trace file.
565 #[inline(always)]
566 fn with_elapsed_micros_subtracting_tracing(&self, f: impl Fn(f64, usize, &Sender<Message>)) {
567 THREAD_DATA.with(|value| {
568 let mut thread_data = value.borrow_mut();
569 let (ThreadData { tid, out, start }, new_thread) = match thread_data.as_mut() {
570 Some(thread_data) => (thread_data, false),
571 None => {
572 let tid = self.max_tid.fetch_add(1, Ordering::SeqCst);
573 let out = self.out.lock().unwrap().clone();
574 let start = TracingChromeInstant::setup_for_thread_and_start(tid);
575 *thread_data = Some(ThreadData { tid, out, start });
576 (thread_data.as_mut().unwrap(), true)
577 }
578 };
566579
567 fn send_message(&self, message: Message) {
568 OUT.with(move |val| {
569 if val.borrow().is_some() {
570 let _ignored = val.borrow().as_ref().unwrap().send(message);
571 } else {
572 let out = self.out.lock().unwrap().clone();
573 let _ignored = out.send(message);
574 val.replace(Some(out));
575 }
580 start.with_elapsed_micros_subtracting_tracing(|ts| {
581 if new_thread {
582 let name = match std::thread::current().name() {
583 Some(name) => name.to_owned(),
584 None => tid.to_string(),
585 };
586 let _ignored = out.send(Message::NewThread(*tid, name));
587 }
588 f(ts, *tid, out);
589 });
576590 });
577591 }
578592}
......@@ -586,52 +600,58 @@ where
586600 return;
587601 }
588602
589 let ts = self.get_ts();
590 self.enter_span(ctx.span(id).expect("Span not found."), ts);
603 self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
604 self.enter_span(ctx.span(id).expect("Span not found."), ts, tid, out);
605 });
591606 }
592607
593608 fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
594609 if self.include_args {
595 let span = ctx.span(id).unwrap();
596 let mut exts = span.extensions_mut();
610 self.with_elapsed_micros_subtracting_tracing(|_, _, _| {
611 let span = ctx.span(id).unwrap();
612 let mut exts = span.extensions_mut();
597613
598 let args = exts.get_mut::<ArgsWrapper>();
614 let args = exts.get_mut::<ArgsWrapper>();
599615
600 if let Some(args) = args {
601 let args = Arc::make_mut(&mut args.args);
602 values.record(&mut JsonVisitor { object: args });
603 }
616 if let Some(args) = args {
617 let args = Arc::make_mut(&mut args.args);
618 values.record(&mut JsonVisitor { object: args });
619 }
620 });
604621 }
605622 }
606623
607624 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
608 let ts = self.get_ts();
609 let callsite = self.get_callsite(EventOrSpan::Event(event));
610 self.send_message(Message::Event(ts, callsite));
625 self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
626 let callsite = self.get_callsite(EventOrSpan::Event(event), tid);
627 let _ignored = out.send(Message::Event(ts, callsite));
628 });
611629 }
612630
613631 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
614632 if let TraceStyle::Async = self.trace_style {
615633 return;
616634 }
617 let ts = self.get_ts();
618 self.exit_span(ctx.span(id).expect("Span not found."), ts);
635 self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
636 self.exit_span(ctx.span(id).expect("Span not found."), ts, tid, out);
637 });
619638 }
620639
621640 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
622 if self.include_args {
623 let mut args = Object::new();
624 attrs.record(&mut JsonVisitor { object: &mut args });
625 ctx.span(id).unwrap().extensions_mut().insert(ArgsWrapper {
626 args: Arc::new(args),
627 });
628 }
629 if let TraceStyle::Threaded = self.trace_style {
630 return;
631 }
641 self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
642 if self.include_args {
643 let mut args = Object::new();
644 attrs.record(&mut JsonVisitor { object: &mut args });
645 ctx.span(id).unwrap().extensions_mut().insert(ArgsWrapper {
646 args: Arc::new(args),
647 });
648 }
649 if let TraceStyle::Threaded = self.trace_style {
650 return;
651 }
632652
633 let ts = self.get_ts();
634 self.enter_span(ctx.span(id).expect("Span not found."), ts);
653 self.enter_span(ctx.span(id).expect("Span not found."), ts, tid, out);
654 });
635655 }
636656
637657 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
......@@ -639,8 +659,9 @@ where
639659 return;
640660 }
641661
642 let ts = self.get_ts();
643 self.exit_span(ctx.span(&id).expect("Span not found."), ts);
662 self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
663 self.exit_span(ctx.span(&id).expect("Span not found."), ts, tid, out);
664 });
644665 }
645666}
646667
src/tools/miri/src/bin/log/tracing_chrome_instant.rs created+183
......@@ -0,0 +1,183 @@
1//! Code in this class was in part inspired by
2//! <https://github.com/tikv/minstant/blob/27c9ec5ec90b5b67113a748a4defee0d2519518c/src/tsc_now.rs>.
3//! A useful resource is also
4//! <https://www.pingcap.com/blog/how-we-trace-a-kv-database-with-less-than-5-percent-performance-impact/>,
5//! although this file does not implement TSC synchronization but insteads pins threads to CPUs,
6//! since the former is not reliable (i.e. it might lead to non-monotonic time measurements).
7//! Another useful resource for future improvements might be measureme's time measurement utils:
8//! <https://github.com/rust-lang/measureme/blob/master/measureme/src/counters.rs>.
9//! Documentation about how the Linux kernel chooses a clock source can be found here:
10//! <https://btorpey.github.io/blog/2014/02/18/clock-sources-in-linux/>.
11#![cfg(feature = "tracing")]
12
13/// This alternative `TracingChromeInstant` implementation was made entirely to suit the needs of
14/// [crate::log::tracing_chrome], and shouldn't be used for anything else. It featues two functions:
15/// - [TracingChromeInstant::setup_for_thread_and_start], which sets up the current thread to do
16/// proper time tracking and returns a point in time to use as "t=0", and
17/// - [TracingChromeInstant::with_elapsed_micros_subtracting_tracing], which allows
18/// obtaining how much time elapsed since [TracingChromeInstant::setup_for_thread_and_start] was
19/// called while accounting for (and subtracting) the time spent inside tracing-related functions.
20///
21/// This measures time using [std::time::Instant], except for x86/x86_64 Linux machines, where
22/// [std::time::Instant] is too slow (~1.5us) and thus `rdtsc` is used instead (~5ns).
23pub enum TracingChromeInstant {
24 WallTime {
25 /// The time at which this instant was created, shifted forward to account
26 /// for time spent in tracing functions as explained in
27 /// [TracingChromeInstant::with_elapsed_micros_subtracting_tracing]'s comments.
28 start_instant: std::time::Instant,
29 },
30 #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
31 Tsc {
32 /// The value in the TSC counter when this instant was created, shifted forward to account
33 /// for time spent in tracing functions as explained in
34 /// [TracingChromeInstant::with_elapsed_micros_subtracting_tracing]'s comments.
35 start_tsc: u64,
36 /// The period of the TSC counter in microseconds.
37 tsc_to_microseconds: f64,
38 },
39}
40
41impl TracingChromeInstant {
42 /// Can be thought of as the same as [std::time::Instant::now()], but also does some setup to
43 /// make TSC stable in case TSC is available. This is supposed to be called (at most) once per
44 /// thread since the thread setup takes a few milliseconds.
45 ///
46 /// WARNING: If TSC is available, `incremental_thread_id` is used to pick to which CPU to pin
47 /// the current thread. Thread IDs should be assigned contiguously starting from 0. Be aware
48 /// that the current thread will be restricted to one CPU for the rest of the execution!
49 pub fn setup_for_thread_and_start(incremental_thread_id: usize) -> TracingChromeInstant {
50 #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
51 if *tsc::IS_TSC_AVAILABLE.get_or_init(tsc::is_tsc_available) {
52 // We need to lock this thread to a specific CPU, because CPUs' TSC timers might be out
53 // of sync.
54 tsc::set_cpu_affinity(incremental_thread_id);
55
56 // Can only use tsc_to_microseconds() and rdtsc() after having set the CPU affinity!
57 // We compute tsc_to_microseconds anew for every new thread just in case some CPU core
58 // has a different TSC frequency.
59 let tsc_to_microseconds = tsc::tsc_to_microseconds();
60 let start_tsc = tsc::rdtsc();
61 return TracingChromeInstant::Tsc { start_tsc, tsc_to_microseconds };
62 }
63
64 let _ = incremental_thread_id; // otherwise we get a warning when the TSC branch is disabled
65 TracingChromeInstant::WallTime { start_instant: std::time::Instant::now() }
66 }
67
68 /// Calls `f` with the time elapsed in microseconds since this [TracingChromeInstant] was built
69 /// by [TracingChromeInstant::setup_for_thread_and_start], while subtracting all time previously
70 /// spent executing other `f`s passed to this function. This behavior allows subtracting time
71 /// spent in functions that log tracing data (which `f` is supposed to be) from the tracing time
72 /// measurements.
73 ///
74 /// Note: microseconds are used as the time unit since that's what Chrome trace files should
75 /// contain, see the definition of the "ts" field in
76 /// <https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview>.
77 #[inline(always)]
78 pub fn with_elapsed_micros_subtracting_tracing(&mut self, f: impl Fn(f64)) {
79 match self {
80 TracingChromeInstant::WallTime { start_instant } => {
81 // Obtain the current time (before executing `f`).
82 let instant_before_f = std::time::Instant::now();
83
84 // Using the current time (`instant_before_f`) and the `start_instant` stored in
85 // `self`, calculate the elapsed time (in microseconds) since this instant was
86 // instantiated, accounting for any time that was previously spent executing `f`.
87 // The "accounting" part is not computed in this line, but is rather done by
88 // shifting forward the `start_instant` down below.
89 let ts = (instant_before_f - *start_instant).as_nanos() as f64 / 1000.0;
90
91 // Run the function (supposedly a function internal to the tracing infrastructure).
92 f(ts);
93
94 // Measure how much time was spent executing `f` and shift `start_instant` forward
95 // by that amount. This "removes" that time from the trace.
96 *start_instant += std::time::Instant::now() - instant_before_f;
97 }
98
99 #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
100 TracingChromeInstant::Tsc { start_tsc, tsc_to_microseconds } => {
101 // the comments above also apply here, since it's the same logic
102 let tsc_before_f = tsc::rdtsc();
103 let ts = ((tsc_before_f - *start_tsc) as f64) * (*tsc_to_microseconds);
104 f(ts);
105 *start_tsc += tsc::rdtsc() - tsc_before_f;
106 }
107 }
108 }
109}
110
111#[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
112mod tsc {
113
114 pub static IS_TSC_AVAILABLE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
115
116 /// Reads the timestamp-counter register. Will give monotonic answers only when called from the
117 /// same thread, because the TSC of different CPUs might be out of sync.
118 #[inline(always)]
119 pub(super) fn rdtsc() -> u64 {
120 #[cfg(target_arch = "x86")]
121 use core::arch::x86::_rdtsc;
122 #[cfg(target_arch = "x86_64")]
123 use core::arch::x86_64::_rdtsc;
124
125 unsafe { _rdtsc() }
126 }
127
128 /// Estimates the frequency of the TSC counter by waiting 10ms in a busy loop and
129 /// looking at how much the TSC increased in the meantime.
130 pub(super) fn tsc_to_microseconds() -> f64 {
131 const BUSY_WAIT: std::time::Duration = std::time::Duration::from_millis(10);
132 let tsc_start = rdtsc();
133 let instant_start = std::time::Instant::now();
134 while instant_start.elapsed() < BUSY_WAIT {
135 // `thread::sleep()` is not very precise at waking up the program at the right time,
136 // so use a busy loop instead.
137 core::hint::spin_loop();
138 }
139 let tsc_end = rdtsc();
140 (BUSY_WAIT.as_nanos() as f64) / 1000.0 / ((tsc_end - tsc_start) as f64)
141 }
142
143 /// Checks whether the TSC counter is available and runs at a constant rate independently
144 /// of CPU frequency even across different power states of the CPU (i.e. checks for the
145 /// `invariant_tsc` CPUID flag).
146 pub(super) fn is_tsc_available() -> bool {
147 #[cfg(target_arch = "x86")]
148 use core::arch::x86::__cpuid;
149 #[cfg(target_arch = "x86_64")]
150 use core::arch::x86_64::__cpuid;
151
152 // implemented like https://docs.rs/raw-cpuid/latest/src/raw_cpuid/extended.rs.html#965-967
153 const LEAF: u32 = 0x80000007; // this is the leaf for "advanced power management info"
154 let cpuid = unsafe { __cpuid(LEAF) };
155 (cpuid.edx & (1 << 8)) != 0 // EDX bit 8 indicates invariant TSC
156 }
157
158 /// Forces the current thread to run on a single CPU, which ensures the TSC counter is monotonic
159 /// (since TSCs of different CPUs might be out-of-sync). `incremental_thread_id` is used to pick
160 /// to which CPU to pin the current thread, and should be an incremental number that starts from
161 /// 0.
162 pub(super) fn set_cpu_affinity(incremental_thread_id: usize) {
163 let cpu_id = match std::thread::available_parallelism() {
164 Ok(available_parallelism) => incremental_thread_id % available_parallelism,
165 _ => panic!("Could not determine CPU count to properly set CPU affinity"),
166 };
167
168 let mut set = unsafe { std::mem::zeroed::<libc::cpu_set_t>() };
169 unsafe { libc::CPU_SET(cpu_id, &mut set) };
170
171 // Set the current thread's core affinity.
172 if unsafe {
173 libc::sched_setaffinity(
174 0, // Defaults to current thread
175 size_of::<libc::cpu_set_t>(),
176 &set as *const _,
177 )
178 } != 0
179 {
180 panic!("Could not set CPU affinity")
181 }
182 }
183}
src/tools/miri/src/bin/miri.rs+5-1
......@@ -556,7 +556,11 @@ fn main() {
556556 } else if arg == "-Zmiri-deterministic-floats" {
557557 miri_config.float_nondet = false;
558558 } else if arg == "-Zmiri-no-extra-rounding-error" {
559 miri_config.float_rounding_error = false;
559 miri_config.float_rounding_error = miri::FloatRoundingErrorMode::None;
560 } else if arg == "-Zmiri-max-extra-rounding-error" {
561 miri_config.float_rounding_error = miri::FloatRoundingErrorMode::Max;
562 } else if arg == "-Zmiri-no-short-fd-operations" {
563 miri_config.short_fd_operations = false;
560564 } else if arg == "-Zmiri-strict-provenance" {
561565 miri_config.provenance_mode = ProvenanceMode::Strict;
562566 } else if arg == "-Zmiri-permissive-provenance" {
src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs+89-136
......@@ -125,81 +125,64 @@ pub struct NewPermission {
125125 /// Whether a read access should be performed on the non-frozen
126126 /// part on a retag.
127127 nonfreeze_access: bool,
128 /// Permission for memory outside the range.
129 outside_perm: Permission,
128130 /// Whether this pointer is part of the arguments of a function call.
129131 /// `protector` is `Some(_)` for all pointers marked `noalias`.
130132 protector: Option<ProtectorKind>,
131133}
132134
133135impl<'tcx> NewPermission {
134 /// Determine NewPermission of the reference from the type of the pointee.
135 fn from_ref_ty(
136 /// Determine NewPermission of the reference/Box from the type of the pointee.
137 ///
138 /// A `ref_mutability` of `None` indicates a `Box` type.
139 fn new(
136140 pointee: Ty<'tcx>,
137 mutability: Mutability,
138 kind: RetagKind,
141 ref_mutability: Option<Mutability>,
142 retag_kind: RetagKind,
139143 cx: &crate::MiriInterpCx<'tcx>,
140144 ) -> Option<Self> {
141145 let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env());
142 let is_protected = kind == RetagKind::FnEntry;
143 let protector = is_protected.then_some(ProtectorKind::StrongProtector);
144
145 Some(match mutability {
146 Mutability::Mut if ty_is_unpin =>
147 NewPermission {
148 freeze_perm: Permission::new_reserved(
149 /* ty_is_freeze */ true,
150 is_protected,
151 ),
152 freeze_access: true,
153 nonfreeze_perm: Permission::new_reserved(
154 /* ty_is_freeze */ false,
155 is_protected,
156 ),
157 // If we have a mutable reference, then the non-frozen part will
158 // have state `ReservedIM` or `Reserved`, which can have an initial read access
159 // performed on it because you cannot have multiple mutable borrows.
160 nonfreeze_access: true,
161 protector,
162 },
163 Mutability::Not =>
164 NewPermission {
165 freeze_perm: Permission::new_frozen(),
166 freeze_access: true,
167 nonfreeze_perm: Permission::new_cell(),
168 // If it is a shared reference, then the non-frozen
169 // part will have state `Cell`, which should not have an initial access,
170 // as this can cause data races when using thread-safe data types like
171 // `Mutex<T>`.
172 nonfreeze_access: false,
173 protector,
174 },
175 _ => return None,
176 })
177 }
146 let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
147 let is_protected = retag_kind == RetagKind::FnEntry;
178148
179 /// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
180 /// These pointers allow deallocation so need a different kind of protector not handled
181 /// by `from_ref_ty`.
182 fn from_unique_ty(
183 ty: Ty<'tcx>,
184 kind: RetagKind,
185 cx: &crate::MiriInterpCx<'tcx>,
186 ) -> Option<Self> {
187 let pointee = ty.builtin_deref(true).unwrap();
188 pointee.is_unpin(*cx.tcx, cx.typing_env()).then_some(()).map(|()| {
189 // Regular `Unpin` box, give it `noalias` but only a weak protector
190 // because it is valid to deallocate it within the function.
191 let is_protected = kind == RetagKind::FnEntry;
192 let protector = is_protected.then_some(ProtectorKind::WeakProtector);
193 NewPermission {
194 freeze_perm: Permission::new_reserved(/* ty_is_freeze */ true, is_protected),
195 freeze_access: true,
196 nonfreeze_perm: Permission::new_reserved(
197 /* ty_is_freeze */ false,
198 is_protected,
199 ),
200 nonfreeze_access: true,
201 protector,
202 }
149 if matches!(ref_mutability, Some(Mutability::Mut) | None if !ty_is_unpin) {
150 // Mutable reference / Box to pinning type: retagging is a NOP.
151 // FIXME: with `UnsafePinned`, this should do proper per-byte tracking.
152 return None;
153 }
154
155 let freeze_perm = match ref_mutability {
156 // Shared references are frozen.
157 Some(Mutability::Not) => Permission::new_frozen(),
158 // Mutable references and Boxes are reserved.
159 _ => Permission::new_reserved_frz(),
160 };
161 let nonfreeze_perm = match ref_mutability {
162 // Shared references are "transparent".
163 Some(Mutability::Not) => Permission::new_cell(),
164 // *Protected* mutable references and boxes are reserved without regarding for interior mutability.
165 _ if is_protected => Permission::new_reserved_frz(),
166 // Unprotected mutable references and boxes start in `ReservedIm`.
167 _ => Permission::new_reserved_im(),
168 };
169
170 // Everything except for `Cell` gets an initial access.
171 let initial_access = |perm: &Permission| !perm.is_cell();
172
173 Some(NewPermission {
174 freeze_perm,
175 freeze_access: initial_access(&freeze_perm),
176 nonfreeze_perm,
177 nonfreeze_access: initial_access(&nonfreeze_perm),
178 outside_perm: if ty_is_freeze { freeze_perm } else { nonfreeze_perm },
179 protector: is_protected.then_some(if ref_mutability.is_some() {
180 // Strong protector for references
181 ProtectorKind::StrongProtector
182 } else {
183 // Weak protector for boxes
184 ProtectorKind::WeakProtector
185 }),
203186 })
204187 }
205188}
......@@ -313,30 +296,20 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
313296
314297 let span = this.machine.current_span();
315298
316 // Store initial permissions and their corresponding range.
317 let mut perms_map: DedupRangeMap<LocationState> = DedupRangeMap::new(
318 ptr_size,
319 LocationState::new_accessed(Permission::new_disabled(), IdempotentForeignAccess::None), // this will be overwritten
320 );
321 // Keep track of whether the node has any part that allows for interior mutability.
322 // FIXME: This misses `PhantomData<UnsafeCell<T>>` which could be considered a marker
323 // for requesting interior mutability.
324 let mut has_unsafe_cell = false;
325
326299 // When adding a new node, the SIFA of its parents needs to be updated, potentially across
327300 // the entire memory range. For the parts that are being accessed below, the access itself
328 // trivially takes care of that. However, we have to do some more work to also deal with
329 // the parts that are not being accessed. Specifically what we do is that we
330 // call `update_last_accessed_after_retag` on the SIFA of the permission set for the part of
331 // memory outside `perm_map` -- so that part is definitely taken care of. The remaining concern
332 // is the part of memory that is in the range of `perms_map`, but not accessed below.
333 // There we have two cases:
334 // * If we do have an `UnsafeCell` (`has_unsafe_cell` becomes true), then the non-accessed part
335 // uses `nonfreeze_perm`, so the `nonfreeze_perm` initialized parts are also fine. We enforce
336 // the `freeze_perm` parts to be accessed, and thus everything is taken care of.
337 // * If there is no `UnsafeCell`, then `freeze_perm` is used everywhere (both inside and outside the initial range),
338 // and we update everything to have the `freeze_perm`'s SIFA, so there are no issues. (And this assert below is not
339 // actually needed in this case).
301 // trivially takes care of that. However, we have to do some more work to also deal with the
302 // parts that are not being accessed. Specifically what we do is that we call
303 // `update_last_accessed_after_retag` on the SIFA of the permission set for the part of
304 // memory outside `perm_map` -- so that part is definitely taken care of. The remaining
305 // concern is the part of memory that is in the range of `perms_map`, but not accessed
306 // below. There we have two cases:
307 // * If the type is `!Freeze`, then the non-accessed part uses `nonfreeze_perm`, so the
308 // `nonfreeze_perm` initialized parts are also fine. We enforce the `freeze_perm` parts to
309 // be accessed via the assert below, and thus everything is taken care of.
310 // * If the type is `Freeze`, then `freeze_perm` is used everywhere (both inside and outside
311 // the initial range), and we update everything to have the `freeze_perm`'s SIFA, so there
312 // are no issues. (And this assert below is not actually needed in this case).
340313 assert!(new_perm.freeze_access);
341314
342315 let protected = new_perm.protector.is_some();
......@@ -350,66 +323,48 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
350323 .get_tree_borrows_params()
351324 .precise_interior_mut;
352325
353 let default_perm = if !precise_interior_mut {
354 // NOTE: Using `ty_is_freeze` doesn't give the same result as going through the range
355 // and computing `has_unsafe_cell`. This is because of zero-sized `UnsafeCell`, for which
356 // `has_unsafe_cell` is false, but `!ty_is_freeze` is true.
357 let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
358 let (perm, access) = if ty_is_freeze {
326 // Compute initial "inside" permissions.
327 let loc_state = |frozen: bool| -> LocationState {
328 let (perm, access) = if frozen {
359329 (new_perm.freeze_perm, new_perm.freeze_access)
360330 } else {
361331 (new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
362332 };
363333 let sifa = perm.strongest_idempotent_foreign_access(protected);
364 let new_loc = if access {
334 if access {
365335 LocationState::new_accessed(perm, sifa)
366336 } else {
367337 LocationState::new_non_accessed(perm, sifa)
368 };
369
370 for (_loc_range, loc) in perms_map.iter_mut_all() {
371 *loc = new_loc;
372338 }
373
374 perm
339 };
340 let perms_map = if !precise_interior_mut {
341 // For `!Freeze` types, just pretend the entire thing is an `UnsafeCell`.
342 let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
343 let state = loc_state(ty_is_freeze);
344 DedupRangeMap::new(ptr_size, state)
375345 } else {
346 // The initial state will be overwritten by the visitor below.
347 let mut perms_map: DedupRangeMap<LocationState> = DedupRangeMap::new(
348 ptr_size,
349 LocationState::new_accessed(
350 Permission::new_disabled(),
351 IdempotentForeignAccess::None,
352 ),
353 );
376354 this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
377 has_unsafe_cell = has_unsafe_cell || !frozen;
378
379 // We are only ever `Frozen` inside the frozen bits.
380 let (perm, access) = if frozen {
381 (new_perm.freeze_perm, new_perm.freeze_access)
382 } else {
383 (new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
384 };
385 let sifa = perm.strongest_idempotent_foreign_access(protected);
386 // NOTE: Currently, `access` is false if and only if `perm` is Cell, so this `if`
387 // doesn't not change whether any code is UB or not. We could just always use
388 // `new_accessed` and everything would stay the same. But that seems conceptually
389 // odd, so we keep the initial "accessed" bit of the `LocationState` in sync with whether
390 // a read access is performed below.
391 let new_loc = if access {
392 LocationState::new_accessed(perm, sifa)
393 } else {
394 LocationState::new_non_accessed(perm, sifa)
395 };
396
397 // Store initial permissions.
355 let state = loc_state(frozen);
398356 for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
399 *loc = new_loc;
357 *loc = state;
400358 }
401
402359 interp_ok(())
403360 })?;
404
405 // Allow lazily writing to surrounding data if we found an `UnsafeCell`.
406 if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm }
361 perms_map
407362 };
408363
409364 let alloc_extra = this.get_alloc_extra(alloc_id)?;
410365 let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
411366
412 for (perm_range, perm) in perms_map.iter_mut_all() {
367 for (perm_range, perm) in perms_map.iter_all() {
413368 if perm.is_accessed() {
414369 // Some reborrows incur a read access to the parent.
415370 // Adjust range to be relative to allocation start (rather than to `place`).
......@@ -447,7 +402,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
447402 orig_tag,
448403 new_tag,
449404 perms_map,
450 default_perm,
405 new_perm.outside_perm,
451406 protected,
452407 span,
453408 )?;
......@@ -514,7 +469,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
514469 let this = self.eval_context_mut();
515470 let new_perm = match val.layout.ty.kind() {
516471 &ty::Ref(_, pointee, mutability) =>
517 NewPermission::from_ref_ty(pointee, mutability, kind, this),
472 NewPermission::new(pointee, Some(mutability), kind, this),
518473 _ => None,
519474 };
520475 if let Some(new_perm) = new_perm {
......@@ -571,8 +526,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
571526 fn visit_box(&mut self, box_ty: Ty<'tcx>, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
572527 // Only boxes for the global allocator get any special treatment.
573528 if box_ty.is_box_global(*self.ecx.tcx) {
529 let pointee = place.layout.ty.builtin_deref(true).unwrap();
574530 let new_perm =
575 NewPermission::from_unique_ty(place.layout.ty, self.kind, self.ecx);
531 NewPermission::new(pointee, /* not a ref */ None, self.kind, self.ecx);
576532 self.retag_ptr_inplace(place, new_perm)?;
577533 }
578534 interp_ok(())
......@@ -591,7 +547,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
591547 match place.layout.ty.kind() {
592548 &ty::Ref(_, pointee, mutability) => {
593549 let new_perm =
594 NewPermission::from_ref_ty(pointee, mutability, self.kind, self.ecx);
550 NewPermission::new(pointee, Some(mutability), self.kind, self.ecx);
595551 self.retag_ptr_inplace(place, new_perm)?;
596552 }
597553 ty::RawPtr(_, _) => {
......@@ -643,14 +599,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
643599 // never be ReservedIM, the value of the `ty_is_freeze`
644600 // argument doesn't matter
645601 // (`ty_is_freeze || true` in `new_reserved` will always be `true`).
646 freeze_perm: Permission::new_reserved(
647 /* ty_is_freeze */ true, /* protected */ true,
648 ),
602 freeze_perm: Permission::new_reserved_frz(),
649603 freeze_access: true,
650 nonfreeze_perm: Permission::new_reserved(
651 /* ty_is_freeze */ false, /* protected */ true,
652 ),
604 nonfreeze_perm: Permission::new_reserved_frz(),
653605 nonfreeze_access: true,
606 outside_perm: Permission::new_reserved_frz(),
654607 protector: Some(ProtectorKind::StrongProtector),
655608 };
656609 this.tb_retag_place(place, new_perm)
src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs+2-15
......@@ -272,28 +272,15 @@ impl Permission {
272272
273273 /// Default initial permission of a reborrowed mutable reference that is either
274274 /// protected or not interior mutable.
275 fn new_reserved_frz() -> Self {
275 pub fn new_reserved_frz() -> Self {
276276 Self { inner: ReservedFrz { conflicted: false } }
277277 }
278278
279279 /// Default initial permission of an unprotected interior mutable reference.
280 fn new_reserved_im() -> Self {
280 pub fn new_reserved_im() -> Self {
281281 Self { inner: ReservedIM }
282282 }
283283
284 /// Wrapper around `new_reserved_frz` and `new_reserved_im` that decides
285 /// which to call based on the interior mutability and the retag kind (whether there
286 /// is a protector is relevant because being protected takes priority over being
287 /// interior mutable)
288 pub fn new_reserved(ty_is_freeze: bool, protected: bool) -> Self {
289 // As demonstrated by `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
290 // interior mutability and protectors interact poorly.
291 // To eliminate the case of Protected Reserved IM we override interior mutability
292 // in the case of a protected reference: protected references are always considered
293 // "freeze" in their reservation phase.
294 if ty_is_freeze || protected { Self::new_reserved_frz() } else { Self::new_reserved_im() }
295 }
296
297284 /// Default initial permission of a reborrowed shared reference.
298285 pub fn new_frozen() -> Self {
299286 Self { inner: Frozen }
src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs+1-1
......@@ -610,7 +610,7 @@ mod spurious_read {
610610 },
611611 y: LocStateProt {
612612 state: LocationState::new_non_accessed(
613 Permission::new_reserved(/* freeze */ true, /* protected */ true),
613 Permission::new_reserved_frz(),
614614 IdempotentForeignAccess::default(),
615615 ),
616616 prot: true,
src/tools/miri/src/eval.rs+5-61
......@@ -32,65 +32,6 @@ pub enum MiriEntryFnType {
3232/// will hang the program.
3333const MAIN_THREAD_YIELDS_AT_SHUTDOWN: u32 = 256;
3434
35#[derive(Copy, Clone, Debug, PartialEq)]
36pub enum AlignmentCheck {
37 /// Do not check alignment.
38 None,
39 /// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
40 Symbolic,
41 /// Check alignment on the actual physical integer address.
42 Int,
43}
44
45#[derive(Copy, Clone, Debug, PartialEq)]
46pub enum RejectOpWith {
47 /// Isolated op is rejected with an abort of the machine.
48 Abort,
49
50 /// If not Abort, miri returns an error for an isolated op.
51 /// Following options determine if user should be warned about such error.
52 /// Do not print warning about rejected isolated op.
53 NoWarning,
54
55 /// Print a warning about rejected isolated op, with backtrace.
56 Warning,
57
58 /// Print a warning about rejected isolated op, without backtrace.
59 WarningWithoutBacktrace,
60}
61
62#[derive(Copy, Clone, Debug, PartialEq)]
63pub enum IsolatedOp {
64 /// Reject an op requiring communication with the host. By
65 /// default, miri rejects the op with an abort. If not, it returns
66 /// an error code, and prints a warning about it. Warning levels
67 /// are controlled by `RejectOpWith` enum.
68 Reject(RejectOpWith),
69
70 /// Execute op requiring communication with the host, i.e. disable isolation.
71 Allow,
72}
73
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
75pub enum BacktraceStyle {
76 /// Prints a terser backtrace which ideally only contains relevant information.
77 Short,
78 /// Prints a backtrace with all possible information.
79 Full,
80 /// Prints only the frame that the error occurs in.
81 Off,
82}
83
84#[derive(Debug, Copy, Clone, PartialEq, Eq)]
85pub enum ValidationMode {
86 /// Do not perform any kind of validation.
87 No,
88 /// Validate the interior of the value, but not things behind references.
89 Shallow,
90 /// Fully recursively validate references.
91 Deep,
92}
93
9435/// Configuration needed to spawn a Miri instance.
9536#[derive(Clone)]
9637pub struct MiriConfig {
......@@ -171,7 +112,9 @@ pub struct MiriConfig {
171112 /// Whether floating-point operations can behave non-deterministically.
172113 pub float_nondet: bool,
173114 /// Whether floating-point operations can have a non-deterministic rounding error.
174 pub float_rounding_error: bool,
115 pub float_rounding_error: FloatRoundingErrorMode,
116 /// Whether Miri artifically introduces short reads/writes on file descriptors.
117 pub short_fd_operations: bool,
175118}
176119
177120impl Default for MiriConfig {
......@@ -213,7 +156,8 @@ impl Default for MiriConfig {
213156 fixed_scheduling: false,
214157 force_intrinsic_fallback: false,
215158 float_nondet: true,
216 float_rounding_error: true,
159 float_rounding_error: FloatRoundingErrorMode::Random,
160 short_fd_operations: true,
217161 }
218162 }
219163}
src/tools/miri/src/intrinsics/mod.rs+8-41
......@@ -10,7 +10,7 @@ use rustc_abi::Size;
1010use rustc_apfloat::ieee::{IeeeFloat, Semantics};
1111use rustc_apfloat::{self, Float, Round};
1212use rustc_middle::mir;
13use rustc_middle::ty::{self, FloatTy, ScalarInt};
13use rustc_middle::ty::{self, FloatTy};
1414use rustc_span::{Symbol, sym};
1515
1616use self::atomic::EvalContextExt as _;
......@@ -230,7 +230,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
230230 let res = apply_random_float_error_ulp(
231231 this,
232232 res,
233 2, // log2(4)
233 4,
234234 );
235235
236236 // Clamp the result to the guaranteed range of this function according to the C standard,
......@@ -274,7 +274,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
274274 let res = apply_random_float_error_ulp(
275275 this,
276276 res,
277 2, // log2(4)
277 4,
278278 );
279279
280280 // Clamp the result to the guaranteed range of this function according to the C standard,
......@@ -336,9 +336,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
336336
337337 // Apply a relative error of 4ULP to introduce some non-determinism
338338 // simulating imprecise implementations and optimizations.
339 apply_random_float_error_ulp(
340 this, res, 2, // log2(4)
341 )
339 apply_random_float_error_ulp(this, res, 4)
342340 });
343341 let res = this.adjust_nan(res, &[f1, f2]);
344342 this.write_scalar(res, dest)?;
......@@ -354,9 +352,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
354352
355353 // Apply a relative error of 4ULP to introduce some non-determinism
356354 // simulating imprecise implementations and optimizations.
357 apply_random_float_error_ulp(
358 this, res, 2, // log2(4)
359 )
355 apply_random_float_error_ulp(this, res, 4)
360356 });
361357 let res = this.adjust_nan(res, &[f1, f2]);
362358 this.write_scalar(res, dest)?;
......@@ -373,9 +369,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
373369
374370 // Apply a relative error of 4ULP to introduce some non-determinism
375371 // simulating imprecise implementations and optimizations.
376 apply_random_float_error_ulp(
377 this, res, 2, // log2(4)
378 )
372 apply_random_float_error_ulp(this, res, 4)
379373 });
380374 let res = this.adjust_nan(res, &[f]);
381375 this.write_scalar(res, dest)?;
......@@ -391,9 +385,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
391385
392386 // Apply a relative error of 4ULP to introduce some non-determinism
393387 // simulating imprecise implementations and optimizations.
394 apply_random_float_error_ulp(
395 this, res, 2, // log2(4)
396 )
388 apply_random_float_error_ulp(this, res, 4)
397389 });
398390 let res = this.adjust_nan(res, &[f]);
399391 this.write_scalar(res, dest)?;
......@@ -448,7 +440,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
448440 }
449441 // Apply a relative error of 4ULP to simulate non-deterministic precision loss
450442 // due to optimizations.
451 let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
443 let res = crate::math::apply_random_float_error_to_imm(this, res, 4)?;
452444 this.write_immediate(*res, dest)?;
453445 }
454446
......@@ -486,31 +478,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
486478 }
487479}
488480
489/// Applies a random ULP floating point error to `val` and returns the new value.
490/// So if you want an X ULP error, `ulp_exponent` should be log2(X).
491///
492/// Will fail if `val` is not a floating point number.
493fn apply_random_float_error_to_imm<'tcx>(
494 ecx: &mut MiriInterpCx<'tcx>,
495 val: ImmTy<'tcx>,
496 ulp_exponent: u32,
497) -> InterpResult<'tcx, ImmTy<'tcx>> {
498 let scalar = val.to_scalar_int()?;
499 let res: ScalarInt = match val.layout.ty.kind() {
500 ty::Float(FloatTy::F16) =>
501 apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
502 ty::Float(FloatTy::F32) =>
503 apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
504 ty::Float(FloatTy::F64) =>
505 apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
506 ty::Float(FloatTy::F128) =>
507 apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
508 _ => bug!("intrinsic called with non-float input type"),
509 };
510
511 interp_ok(ImmTy::from_scalar_int(res, val.layout))
512}
513
514481/// For the intrinsics:
515482/// - sinf32, sinf64
516483/// - cosf32, cosf64
src/tools/miri/src/lib.rs+5-6
......@@ -138,15 +138,14 @@ pub use crate::data_structures::mono_hash_map::MonoHashMap;
138138pub use crate::diagnostics::{
139139 EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, report_error,
140140};
141pub use crate::eval::{
142 AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, MiriEntryFnType, RejectOpWith,
143 ValidationMode, create_ecx, eval_entry,
144};
141pub use crate::eval::{MiriConfig, MiriEntryFnType, create_ecx, eval_entry};
145142pub use crate::helpers::{AccessKind, EvalContextExt as _, ToU64 as _, ToUsize as _};
146143pub use crate::intrinsics::EvalContextExt as _;
147144pub use crate::machine::{
148 AllocExtra, DynMachineCallback, FrameExtra, MachineCallback, MemoryKind, MiriInterpCx,
149 MiriInterpCxExt, MiriMachine, MiriMemoryKind, PrimitiveLayouts, Provenance, ProvenanceExtra,
145 AlignmentCheck, AllocExtra, BacktraceStyle, DynMachineCallback, FloatRoundingErrorMode,
146 FrameExtra, IsolatedOp, MachineCallback, MemoryKind, MiriInterpCx, MiriInterpCxExt,
147 MiriMachine, MiriMemoryKind, PrimitiveLayouts, Provenance, ProvenanceExtra, RejectOpWith,
148 ValidationMode,
150149};
151150pub use crate::operator::EvalContextExt as _;
152151pub use crate::provenance_gc::{EvalContextExt as _, LiveAllocs, VisitProvenance, VisitWith};
src/tools/miri/src/machine.rs+78-3
......@@ -49,6 +49,75 @@ pub const SIGRTMAX: i32 = 42;
4949/// base address for each evaluation would produce unbounded memory usage.
5050const ADDRS_PER_ANON_GLOBAL: usize = 32;
5151
52#[derive(Copy, Clone, Debug, PartialEq)]
53pub enum AlignmentCheck {
54 /// Do not check alignment.
55 None,
56 /// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
57 Symbolic,
58 /// Check alignment on the actual physical integer address.
59 Int,
60}
61
62#[derive(Copy, Clone, Debug, PartialEq)]
63pub enum RejectOpWith {
64 /// Isolated op is rejected with an abort of the machine.
65 Abort,
66
67 /// If not Abort, miri returns an error for an isolated op.
68 /// Following options determine if user should be warned about such error.
69 /// Do not print warning about rejected isolated op.
70 NoWarning,
71
72 /// Print a warning about rejected isolated op, with backtrace.
73 Warning,
74
75 /// Print a warning about rejected isolated op, without backtrace.
76 WarningWithoutBacktrace,
77}
78
79#[derive(Copy, Clone, Debug, PartialEq)]
80pub enum IsolatedOp {
81 /// Reject an op requiring communication with the host. By
82 /// default, miri rejects the op with an abort. If not, it returns
83 /// an error code, and prints a warning about it. Warning levels
84 /// are controlled by `RejectOpWith` enum.
85 Reject(RejectOpWith),
86
87 /// Execute op requiring communication with the host, i.e. disable isolation.
88 Allow,
89}
90
91#[derive(Debug, Copy, Clone, PartialEq, Eq)]
92pub enum BacktraceStyle {
93 /// Prints a terser backtrace which ideally only contains relevant information.
94 Short,
95 /// Prints a backtrace with all possible information.
96 Full,
97 /// Prints only the frame that the error occurs in.
98 Off,
99}
100
101#[derive(Debug, Copy, Clone, PartialEq, Eq)]
102pub enum ValidationMode {
103 /// Do not perform any kind of validation.
104 No,
105 /// Validate the interior of the value, but not things behind references.
106 Shallow,
107 /// Fully recursively validate references.
108 Deep,
109}
110
111#[derive(Debug, Copy, Clone, PartialEq, Eq)]
112pub enum FloatRoundingErrorMode {
113 /// Apply a random error (the default).
114 Random,
115 /// Don't apply any error.
116 None,
117 /// Always apply the maximum error (with a random sign).
118 Max,
119}
120
52121/// Extra data stored with each stack frame
53122pub struct FrameExtra<'tcx> {
54123 /// Extra data for the Borrow Tracker.
......@@ -599,7 +668,10 @@ pub struct MiriMachine<'tcx> {
599668 /// Whether floating-point operations can behave non-deterministically.
600669 pub float_nondet: bool,
601670 /// Whether floating-point operations can have a non-deterministic rounding error.
602 pub float_rounding_error: bool,
671 pub float_rounding_error: FloatRoundingErrorMode,
672
673 /// Whether Miri artifically introduces short reads/writes on file descriptors.
674 pub short_fd_operations: bool,
603675}
604676
605677impl<'tcx> MiriMachine<'tcx> {
......@@ -761,6 +833,7 @@ impl<'tcx> MiriMachine<'tcx> {
761833 force_intrinsic_fallback: config.force_intrinsic_fallback,
762834 float_nondet: config.float_nondet,
763835 float_rounding_error: config.float_rounding_error,
836 short_fd_operations: config.short_fd_operations,
764837 }
765838 }
766839
......@@ -937,6 +1010,7 @@ impl VisitProvenance for MiriMachine<'_> {
9371010 force_intrinsic_fallback: _,
9381011 float_nondet: _,
9391012 float_rounding_error: _,
1013 short_fd_operations: _,
9401014 } = self;
9411015
9421016 threads.visit_provenance(visit);
......@@ -1077,7 +1151,8 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
10771151 .target_features
10781152 .iter()
10791153 .filter(|&feature| {
1080 feature.kind != TargetFeatureKind::Implied && !ecx.tcx.sess.target_features.contains(&feature.name)
1154 feature.kind != TargetFeatureKind::Implied
1155 && !ecx.tcx.sess.target_features.contains(&feature.name)
10811156 })
10821157 .fold(String::new(), |mut s, feature| {
10831158 if !s.is_empty() {
......@@ -1208,7 +1283,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
12081283 ecx: &mut InterpCx<'tcx, Self>,
12091284 val: ImmTy<'tcx>,
12101285 ) -> InterpResult<'tcx, ImmTy<'tcx>> {
1211 crate::math::apply_random_float_error_to_imm(ecx, val, 2 /* log2(4) */)
1286 crate::math::apply_random_float_error_to_imm(ecx, val, 4)
12121287 }
12131288
12141289 #[inline(always)]
src/tools/miri/src/math.rs+57-25
......@@ -6,67 +6,99 @@ use rustc_middle::ty::{self, FloatTy, ScalarInt};
66use crate::*;
77
88/// Disturbes a floating-point result by a relative error in the range (-2^scale, 2^scale).
9///
10/// For a 2^N ULP error, you can use an `err_scale` of `-(F::PRECISION - 1 - N)`.
11/// In other words, a 1 ULP (absolute) error is the same as a `2^-(F::PRECISION-1)` relative error.
12/// (Subtracting 1 compensates for the integer bit.)
139pub(crate) fn apply_random_float_error<F: rustc_apfloat::Float>(
1410 ecx: &mut crate::MiriInterpCx<'_>,
1511 val: F,
1612 err_scale: i32,
1713) -> F {
18 if !ecx.machine.float_nondet || !ecx.machine.float_rounding_error {
14 if !ecx.machine.float_nondet
15 || matches!(ecx.machine.float_rounding_error, FloatRoundingErrorMode::None)
16 // relative errors don't do anything to zeros... avoid messing up the sign
17 || val.is_zero()
18 // The logic below makes no sense if the input is already non-finite.
19 || !val.is_finite()
20 {
1921 return val;
2022 }
21
2223 let rng = ecx.machine.rng.get_mut();
24
2325 // Generate a random integer in the range [0, 2^PREC).
2426 // (When read as binary, the position of the first `1` determines the exponent,
2527 // and the remaining bits fill the mantissa. `PREC` is one plus the size of the mantissa,
2628 // so this all works out.)
27 let r = F::from_u128(rng.random_range(0..(1 << F::PRECISION))).value;
29 let r = F::from_u128(match ecx.machine.float_rounding_error {
30 FloatRoundingErrorMode::Random => rng.random_range(0..(1 << F::PRECISION)),
31 FloatRoundingErrorMode::Max => (1 << F::PRECISION) - 1, // force max error
32 FloatRoundingErrorMode::None => unreachable!(),
33 })
34 .value;
2835 // Multiply this with 2^(scale - PREC). The result is between 0 and
2936 // 2^PREC * 2^(scale - PREC) = 2^scale.
3037 let err = r.scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap()));
3138 // give it a random sign
3239 let err = if rng.random() { -err } else { err };
33 // multiple the value with (1+err)
34 (val * (F::from_u128(1).value + err).value).value
40 // Compute `val*(1+err)`, distributed out as `val + val*err` to avoid the imprecise addition
41 // error being amplified by multiplication.
42 (val + (val * err).value).value
3543}
3644
37/// [`apply_random_float_error`] gives instructions to apply a 2^N ULP error.
38/// This function implements these instructions such that applying a 2^N ULP error is less error prone.
39/// So for a 2^N ULP error, you would pass N as the `ulp_exponent` argument.
45/// Applies an error of `[-N, +N]` ULP to the given value.
4046pub(crate) fn apply_random_float_error_ulp<F: rustc_apfloat::Float>(
4147 ecx: &mut crate::MiriInterpCx<'_>,
4248 val: F,
43 ulp_exponent: u32,
49 max_error: u32,
4450) -> F {
45 let n = i32::try_from(ulp_exponent)
46 .expect("`err_scale_for_ulp`: exponent is too large to create an error scale");
47 // we know this fits
48 let prec = i32::try_from(F::PRECISION).unwrap();
49 let err_scale = -(prec - n - 1);
50 apply_random_float_error(ecx, val, err_scale)
51 // We could try to be clever and reuse `apply_random_float_error`, but that is hard to get right
52 // (see <https://github.com/rust-lang/miri/pull/4558#discussion_r2316838085> for why) so we
53 // implement the logic directly instead.
54 if !ecx.machine.float_nondet
55 || matches!(ecx.machine.float_rounding_error, FloatRoundingErrorMode::None)
56 // FIXME: also disturb zeros? That requires a lot more cases in `fixed_float_value`
57 // and might make the std test suite quite unhappy.
58 || val.is_zero()
59 // The logic below makes no sense if the input is already non-finite.
60 || !val.is_finite()
61 {
62 return val;
63 }
64 let rng = ecx.machine.rng.get_mut();
65
66 let max_error = i64::from(max_error);
67 let error = match ecx.machine.float_rounding_error {
68 FloatRoundingErrorMode::Random => rng.random_range(-max_error..=max_error),
69 FloatRoundingErrorMode::Max =>
70 if rng.random() {
71 max_error
72 } else {
73 -max_error
74 },
75 FloatRoundingErrorMode::None => unreachable!(),
76 };
77 // If upwards ULP and downwards ULP differ, we take the average.
78 let ulp = (((val.next_up().value - val).value + (val - val.next_down().value).value).value
79 / F::from_u128(2).value)
80 .value;
81 // Shift the value by N times the ULP
82 (val + (ulp * F::from_i128(error.into()).value).value).value
5183}
5284
53/// Applies a random 16ULP floating point error to `val` and returns the new value.
85/// Applies an error of `[-N, +N]` ULP to the given value.
5486/// Will fail if `val` is not a floating point number.
5587pub(crate) fn apply_random_float_error_to_imm<'tcx>(
5688 ecx: &mut MiriInterpCx<'tcx>,
5789 val: ImmTy<'tcx>,
58 ulp_exponent: u32,
90 max_error: u32,
5991) -> InterpResult<'tcx, ImmTy<'tcx>> {
6092 let scalar = val.to_scalar_int()?;
6193 let res: ScalarInt = match val.layout.ty.kind() {
6294 ty::Float(FloatTy::F16) =>
63 apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
95 apply_random_float_error_ulp(ecx, scalar.to_f16(), max_error).into(),
6496 ty::Float(FloatTy::F32) =>
65 apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
97 apply_random_float_error_ulp(ecx, scalar.to_f32(), max_error).into(),
6698 ty::Float(FloatTy::F64) =>
67 apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
99 apply_random_float_error_ulp(ecx, scalar.to_f64(), max_error).into(),
68100 ty::Float(FloatTy::F128) =>
69 apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
101 apply_random_float_error_ulp(ecx, scalar.to_f128(), max_error).into(),
70102 _ => bug!("intrinsic called with non-float input type"),
71103 };
72104
src/tools/miri/src/shims/files.rs+10-2
......@@ -168,8 +168,9 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
168168 }
169169
170170 /// Determines whether this FD non-deterministically has its reads and writes shortened.
171 fn nondet_short_accesses(&self) -> bool {
172 true
171 fn short_fd_operations(&self) -> bool {
172 // We only enable this for FD kinds where we think short accesses gain useful test coverage.
173 false
173174 }
174175
175176 /// Seeks to the given offset (which can be relative to the beginning, end, or current position).
......@@ -395,6 +396,13 @@ impl FileDescription for FileHandle {
395396 communicate_allowed && self.file.is_terminal()
396397 }
397398
399 fn short_fd_operations(&self) -> bool {
400 // While short accesses on file-backed FDs are very rare (at least for sufficiently small
401 // accesses), they can realistically happen when a signal interrupts the syscall.
402 // FIXME: we should return `false` if this is a named pipe...
403 true
404 }
405
398406 fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
399407 assert!(
400408 ecx.target_os_is_unix(),
src/tools/miri/src/shims/native_lib/ffi.rs created+46
......@@ -0,0 +1,46 @@
1//! Support code for dealing with libffi.
2
3use libffi::low::CodePtr;
4use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType};
5
6/// Perform the actual FFI call.
7///
8/// # Safety
9///
10/// The safety invariants of the foreign function being called must be upheld (if any).
11pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &mut [OwnedArg]) -> R {
12 let arg_ptrs: Vec<_> = args.iter().map(|arg| arg.ptr()).collect();
13 let cif = Cif::new(args.iter_mut().map(|arg| arg.ty.take().unwrap()), R::reify().into_middle());
14 // SAFETY: Caller upholds that the function is safe to call, and since we
15 // were passed a slice reference we know the `OwnedArg`s won't have been
16 // dropped by this point.
17 unsafe { cif.call(fun, &arg_ptrs) }
18}
19
20/// An argument for an FFI call.
21#[derive(Debug, Clone)]
22pub struct OwnedArg {
23 /// The type descriptor for this argument.
24 ty: Option<FfiType>,
25 /// Corresponding bytes for the value.
26 bytes: Box<[u8]>,
27}
28
29impl OwnedArg {
30 /// Instantiates an argument from a type descriptor and bytes.
31 pub fn new(ty: FfiType, bytes: Box<[u8]>) -> Self {
32 Self { ty: Some(ty), bytes }
33 }
34
35 /// Creates a libffi argument pointer pointing to this argument's bytes.
36 /// NB: Since `libffi::middle::Arg` ignores the lifetime of the reference
37 /// it's derived from, it is up to the caller to ensure the `OwnedArg` is
38 /// not dropped before unsafely calling `libffi::middle::Cif::call()`!
39 fn ptr(&self) -> ArgPtr {
40 // FIXME: Using `&self.bytes[0]` to reference the whole array is
41 // definitely unsound under SB, but we're waiting on
42 // https://github.com/libffi-rs/libffi-rs/commit/112a37b3b6ffb35bd75241fbcc580de40ba74a73
43 // to land in a release so that we don't need to do this.
44 ArgPtr::new(&self.bytes[0])
45 }
46}
src/tools/miri/src/shims/native_lib/mod.rs+175-132
......@@ -2,14 +2,15 @@
22
33use std::ops::Deref;
44
5use libffi::high::call as ffi;
65use libffi::low::CodePtr;
7use rustc_abi::{BackendRepr, HasDataLayout, Size};
8use rustc_middle::mir::interpret::Pointer;
9use rustc_middle::ty::{self as ty, IntTy, UintTy};
6use libffi::middle::Type as FfiType;
7use rustc_abi::{HasDataLayout, Size};
8use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
109use rustc_span::Symbol;
1110use serde::{Deserialize, Serialize};
1211
12mod ffi;
13
1314#[cfg_attr(
1415 not(all(
1516 target_os = "linux",
......@@ -20,6 +21,7 @@ use serde::{Deserialize, Serialize};
2021)]
2122pub mod trace;
2223
24use self::ffi::OwnedArg;
2325use crate::*;
2426
2527/// The final results of an FFI trace, containing every relevant event detected
......@@ -70,12 +72,12 @@ impl AccessRange {
7072impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
7173trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
7274 /// Call native host function and return the output as an immediate.
73 fn call_native_with_args<'a>(
75 fn call_native_with_args(
7476 &mut self,
7577 link_name: Symbol,
7678 dest: &MPlaceTy<'tcx>,
77 ptr: CodePtr,
78 libffi_args: Vec<libffi::high::Arg<'a>>,
79 fun: CodePtr,
80 libffi_args: &mut [OwnedArg],
7981 ) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
8082 let this = self.eval_context_mut();
8183 #[cfg(target_os = "linux")]
......@@ -93,55 +95,55 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
9395 // Unsafe because of the call to native code.
9496 // Because this is calling a C function it is not necessarily sound,
9597 // but there is no way around this and we've checked as much as we can.
96 let x = unsafe { ffi::call::<i8>(ptr, libffi_args.as_slice()) };
98 let x = unsafe { ffi::call::<i8>(fun, libffi_args) };
9799 Scalar::from_i8(x)
98100 }
99101 ty::Int(IntTy::I16) => {
100 let x = unsafe { ffi::call::<i16>(ptr, libffi_args.as_slice()) };
102 let x = unsafe { ffi::call::<i16>(fun, libffi_args) };
101103 Scalar::from_i16(x)
102104 }
103105 ty::Int(IntTy::I32) => {
104 let x = unsafe { ffi::call::<i32>(ptr, libffi_args.as_slice()) };
106 let x = unsafe { ffi::call::<i32>(fun, libffi_args) };
105107 Scalar::from_i32(x)
106108 }
107109 ty::Int(IntTy::I64) => {
108 let x = unsafe { ffi::call::<i64>(ptr, libffi_args.as_slice()) };
110 let x = unsafe { ffi::call::<i64>(fun, libffi_args) };
109111 Scalar::from_i64(x)
110112 }
111113 ty::Int(IntTy::Isize) => {
112 let x = unsafe { ffi::call::<isize>(ptr, libffi_args.as_slice()) };
114 let x = unsafe { ffi::call::<isize>(fun, libffi_args) };
113115 Scalar::from_target_isize(x.try_into().unwrap(), this)
114116 }
115117 // uints
116118 ty::Uint(UintTy::U8) => {
117 let x = unsafe { ffi::call::<u8>(ptr, libffi_args.as_slice()) };
119 let x = unsafe { ffi::call::<u8>(fun, libffi_args) };
118120 Scalar::from_u8(x)
119121 }
120122 ty::Uint(UintTy::U16) => {
121 let x = unsafe { ffi::call::<u16>(ptr, libffi_args.as_slice()) };
123 let x = unsafe { ffi::call::<u16>(fun, libffi_args) };
122124 Scalar::from_u16(x)
123125 }
124126 ty::Uint(UintTy::U32) => {
125 let x = unsafe { ffi::call::<u32>(ptr, libffi_args.as_slice()) };
127 let x = unsafe { ffi::call::<u32>(fun, libffi_args) };
126128 Scalar::from_u32(x)
127129 }
128130 ty::Uint(UintTy::U64) => {
129 let x = unsafe { ffi::call::<u64>(ptr, libffi_args.as_slice()) };
131 let x = unsafe { ffi::call::<u64>(fun, libffi_args) };
130132 Scalar::from_u64(x)
131133 }
132134 ty::Uint(UintTy::Usize) => {
133 let x = unsafe { ffi::call::<usize>(ptr, libffi_args.as_slice()) };
135 let x = unsafe { ffi::call::<usize>(fun, libffi_args) };
134136 Scalar::from_target_usize(x.try_into().unwrap(), this)
135137 }
136138 // Functions with no declared return type (i.e., the default return)
137139 // have the output_type `Tuple([])`.
138140 ty::Tuple(t_list) if (*t_list).deref().is_empty() => {
139 unsafe { ffi::call::<()>(ptr, libffi_args.as_slice()) };
141 unsafe { ffi::call::<()>(fun, libffi_args) };
140142 return interp_ok(ImmTy::uninit(dest.layout));
141143 }
142144 ty::RawPtr(..) => {
143 let x = unsafe { ffi::call::<*const ()>(ptr, libffi_args.as_slice()) };
144 let ptr = Pointer::new(Provenance::Wildcard, Size::from_bytes(x.addr()));
145 let x = unsafe { ffi::call::<*const ()>(fun, libffi_args) };
146 let ptr = StrictPointer::new(Provenance::Wildcard, Size::from_bytes(x.addr()));
145147 Scalar::from_pointer(ptr, this)
146148 }
147149 _ =>
......@@ -267,6 +269,150 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
267269
268270 interp_ok(())
269271 }
272
273 /// Extract the value from the result of reading an operand from the machine
274 /// and convert it to a `OwnedArg`.
275 fn op_to_ffi_arg(&self, v: &OpTy<'tcx>, tracing: bool) -> InterpResult<'tcx, OwnedArg> {
276 let this = self.eval_context_ref();
277
278 // This should go first so that we emit unsupported before doing a bunch
279 // of extra work for types that aren't supported yet.
280 let ty = this.ty_to_ffitype(v.layout.ty)?;
281
282 // Helper to print a warning when a pointer is shared with the native code.
283 let expose = |prov: Provenance| -> InterpResult<'tcx> {
284 // The first time this happens, print a warning.
285 if !this.machine.native_call_mem_warned.replace(true) {
286 // Newly set, so first time we get here.
287 this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
288 }
289
290 this.expose_provenance(prov)?;
291 interp_ok(())
292 };
293
294 // Compute the byte-level representation of the argument. If there's a pointer in there, we
295 // expose it inside the AM. Later in `visit_reachable_allocs`, the "meta"-level provenance
296 // for accessing the pointee gets exposed; this is crucial to justify the C code effectively
297 // casting the integer in `byte` to a pointer and using that.
298 let bytes = match v.as_mplace_or_imm() {
299 either::Either::Left(mplace) => {
300 // Get the alloc id corresponding to this mplace, alongside
301 // a pointer that's offset to point to this particular
302 // mplace (not one at the base addr of the allocation).
303 let sz = mplace.layout.size.bytes_usize();
304 if sz == 0 {
305 throw_unsup_format!("attempting to pass a ZST over FFI");
306 }
307 let (id, ofs, _) = this.ptr_get_alloc_id(mplace.ptr(), sz.try_into().unwrap())?;
308 let ofs = ofs.bytes_usize();
309 let range = ofs..ofs.strict_add(sz);
310 // Expose all provenances in the allocation within the byte range of the struct, if
311 // any. These pointers are being directly passed to native code by-value.
312 let alloc = this.get_alloc_raw(id)?;
313 for prov in alloc.provenance().get_range(this, range.clone().into()) {
314 expose(prov)?;
315 }
316 // Read the bytes that make up this argument. We cannot use the normal getter as
317 // those would fail if any part of the argument is uninitialized. Native code
318 // is kind of outside the interpreter, after all...
319 Box::from(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range))
320 }
321 either::Either::Right(imm) => {
322 let mut bytes: Box<[u8]> = vec![0; imm.layout.size.bytes_usize()].into();
323
324 // A little helper to write scalars to our byte array.
325 let mut write_scalar = |this: &MiriInterpCx<'tcx>, sc: Scalar, pos: usize| {
326 // If a scalar is a pointer, then expose its provenance.
327 if let interpret::Scalar::Ptr(p, _) = sc {
328 expose(p.provenance)?;
329 }
330 write_target_uint(
331 this.data_layout().endian,
332 &mut bytes[pos..][..sc.size().bytes_usize()],
333 sc.to_scalar_int()?.to_bits_unchecked(),
334 )
335 .unwrap();
336 interp_ok(())
337 };
338
339 // Write the scalar into the `bytes` buffer.
340 match *imm {
341 Immediate::Scalar(sc) => write_scalar(this, sc, 0)?,
342 Immediate::ScalarPair(sc_first, sc_second) => {
343 // The first scalar has an offset of zero; compute the offset of the 2nd.
344 let ofs_second = {
345 let rustc_abi::BackendRepr::ScalarPair(a, b) = imm.layout.backend_repr
346 else {
347 span_bug!(
348 this.cur_span(),
349 "op_to_ffi_arg: invalid scalar pair layout: {:#?}",
350 imm.layout
351 )
352 };
353 a.size(this).align_to(b.align(this).abi).bytes_usize()
354 };
355
356 write_scalar(this, sc_first, 0)?;
357 write_scalar(this, sc_second, ofs_second)?;
358 }
359 Immediate::Uninit => {
360 // Nothing to write.
361 }
362 }
363
364 bytes
365 }
366 };
367 interp_ok(OwnedArg::new(ty, bytes))
368 }
369
370 /// Parses an ADT to construct the matching libffi type.
371 fn adt_to_ffitype(
372 &self,
373 orig_ty: Ty<'_>,
374 adt_def: ty::AdtDef<'tcx>,
375 args: &'tcx ty::List<ty::GenericArg<'tcx>>,
376 ) -> InterpResult<'tcx, FfiType> {
377 // TODO: Certain non-C reprs should be okay also.
378 if !adt_def.repr().c() {
379 throw_unsup_format!("passing a non-#[repr(C)] struct over FFI: {orig_ty}")
380 }
381 // TODO: unions, etc.
382 if !adt_def.is_struct() {
383 throw_unsup_format!(
384 "unsupported argument type for native call: {orig_ty} is an enum or union"
385 );
386 }
387
388 let this = self.eval_context_ref();
389 let mut fields = vec![];
390 for field in &adt_def.non_enum_variant().fields {
391 fields.push(this.ty_to_ffitype(field.ty(*this.tcx, args))?);
392 }
393
394 interp_ok(FfiType::structure(fields))
395 }
396
397 /// Gets the matching libffi type for a given Ty.
398 fn ty_to_ffitype(&self, ty: Ty<'tcx>) -> InterpResult<'tcx, FfiType> {
399 interp_ok(match ty.kind() {
400 ty::Int(IntTy::I8) => FfiType::i8(),
401 ty::Int(IntTy::I16) => FfiType::i16(),
402 ty::Int(IntTy::I32) => FfiType::i32(),
403 ty::Int(IntTy::I64) => FfiType::i64(),
404 ty::Int(IntTy::Isize) => FfiType::isize(),
405 // the uints
406 ty::Uint(UintTy::U8) => FfiType::u8(),
407 ty::Uint(UintTy::U16) => FfiType::u16(),
408 ty::Uint(UintTy::U32) => FfiType::u32(),
409 ty::Uint(UintTy::U64) => FfiType::u64(),
410 ty::Uint(UintTy::Usize) => FfiType::usize(),
411 ty::RawPtr(..) => FfiType::pointer(),
412 ty::Adt(adt_def, args) => self.adt_to_ffitype(ty, *adt_def, args)?,
413 _ => throw_unsup_format!("unsupported argument type for native call: {}", ty),
414 })
415 }
270416}
271417
272418impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
......@@ -295,36 +441,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
295441 // Do we have ptrace?
296442 let tracing = trace::Supervisor::is_enabled();
297443
298 // Get the function arguments, and convert them to `libffi`-compatible form.
299 let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
444 // Get the function arguments, copy them, and prepare the type descriptions.
445 let mut libffi_args = Vec::<OwnedArg>::with_capacity(args.len());
300446 for arg in args.iter() {
301 if !matches!(arg.layout.backend_repr, BackendRepr::Scalar(_)) {
302 throw_unsup_format!("only scalar argument types are supported for native calls")
303 }
304 let imm = this.read_immediate(arg)?;
305 libffi_args.push(imm_to_carg(&imm, this)?);
306 // If we are passing a pointer, expose its provenance. Below, all exposed memory
307 // (previously exposed and new exposed) will then be properly prepared.
308 if matches!(arg.layout.ty.kind(), ty::RawPtr(..)) {
309 let ptr = imm.to_scalar().to_pointer(this)?;
310 let Some(prov) = ptr.provenance else {
311 // Pointer without provenance may not access any memory anyway, skip.
312 continue;
313 };
314 // The first time this happens, print a warning.
315 if !this.machine.native_call_mem_warned.replace(true) {
316 // Newly set, so first time we get here.
317 this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
318 }
319
320 this.expose_provenance(prov)?;
321 }
447 libffi_args.push(this.op_to_ffi_arg(arg, tracing)?);
322448 }
323 // Convert arguments to `libffi::high::Arg` type.
324 let libffi_args = libffi_args
325 .iter()
326 .map(|arg| arg.arg_downcast())
327 .collect::<Vec<libffi::high::Arg<'_>>>();
328449
329450 // Prepare all exposed memory (both previously exposed, and just newly exposed since a
330451 // pointer was passed as argument). Uninitialised memory is left as-is, but any data
......@@ -343,8 +464,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
343464 std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
344465
345466 if !tracing {
346 // Expose all provenances in this allocation, since the native code can do $whatever.
347 // Can be skipped when tracing; in that case we'll expose just the actually-read parts later.
467 // Expose all provenances in this allocation, since the native code can do
468 // $whatever. Can be skipped when tracing; in that case we'll expose just the
469 // actually-read parts later.
348470 for prov in alloc.provenance().provenances() {
349471 this.expose_provenance(prov)?;
350472 }
......@@ -354,7 +476,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
354476 if info.mutbl.is_mut() {
355477 let (alloc, cx) = this.get_alloc_raw_mut(alloc_id)?;
356478 // These writes could initialize everything and wreck havoc with the pointers.
357 // We can skip that when tracing; in that case we'll later do that only for the memory that got actually written.
479 // We can skip that when tracing; in that case we'll later do that only for the
480 // memory that got actually written.
358481 if !tracing {
359482 alloc.process_native_write(&cx.tcx, None);
360483 }
......@@ -367,7 +490,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
367490
368491 // Call the function and store output, depending on return type in the function signature.
369492 let (ret, maybe_memevents) =
370 this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?;
493 this.call_native_with_args(link_name, dest, code_ptr, &mut libffi_args)?;
371494
372495 if tracing {
373496 this.tracing_apply_accesses(maybe_memevents.unwrap())?;
......@@ -377,83 +500,3 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
377500 interp_ok(true)
378501 }
379502}
380
381#[derive(Debug, Clone)]
382/// Enum of supported arguments to external C functions.
383// We introduce this enum instead of just calling `ffi::arg` and storing a list
384// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
385// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
386// and we need to store a copy of the value, and pass a reference to this copy to C instead.
387enum CArg {
388 /// 8-bit signed integer.
389 Int8(i8),
390 /// 16-bit signed integer.
391 Int16(i16),
392 /// 32-bit signed integer.
393 Int32(i32),
394 /// 64-bit signed integer.
395 Int64(i64),
396 /// isize.
397 ISize(isize),
398 /// 8-bit unsigned integer.
399 UInt8(u8),
400 /// 16-bit unsigned integer.
401 UInt16(u16),
402 /// 32-bit unsigned integer.
403 UInt32(u32),
404 /// 64-bit unsigned integer.
405 UInt64(u64),
406 /// usize.
407 USize(usize),
408 /// Raw pointer, stored as C's `void*`.
409 RawPtr(*mut std::ffi::c_void),
410}
411
412impl<'a> CArg {
413 /// Convert a `CArg` to a `libffi` argument type.
414 fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
415 match self {
416 CArg::Int8(i) => ffi::arg(i),
417 CArg::Int16(i) => ffi::arg(i),
418 CArg::Int32(i) => ffi::arg(i),
419 CArg::Int64(i) => ffi::arg(i),
420 CArg::ISize(i) => ffi::arg(i),
421 CArg::UInt8(i) => ffi::arg(i),
422 CArg::UInt16(i) => ffi::arg(i),
423 CArg::UInt32(i) => ffi::arg(i),
424 CArg::UInt64(i) => ffi::arg(i),
425 CArg::USize(i) => ffi::arg(i),
426 CArg::RawPtr(i) => ffi::arg(i),
427 }
428 }
429}
430
431/// Extract the scalar value from the result of reading a scalar from the machine,
432/// and convert it to a `CArg`.
433fn imm_to_carg<'tcx>(v: &ImmTy<'tcx>, cx: &impl HasDataLayout) -> InterpResult<'tcx, CArg> {
434 interp_ok(match v.layout.ty.kind() {
435 // If the primitive provided can be converted to a type matching the type pattern
436 // then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
437 // the ints
438 ty::Int(IntTy::I8) => CArg::Int8(v.to_scalar().to_i8()?),
439 ty::Int(IntTy::I16) => CArg::Int16(v.to_scalar().to_i16()?),
440 ty::Int(IntTy::I32) => CArg::Int32(v.to_scalar().to_i32()?),
441 ty::Int(IntTy::I64) => CArg::Int64(v.to_scalar().to_i64()?),
442 ty::Int(IntTy::Isize) =>
443 CArg::ISize(v.to_scalar().to_target_isize(cx)?.try_into().unwrap()),
444 // the uints
445 ty::Uint(UintTy::U8) => CArg::UInt8(v.to_scalar().to_u8()?),
446 ty::Uint(UintTy::U16) => CArg::UInt16(v.to_scalar().to_u16()?),
447 ty::Uint(UintTy::U32) => CArg::UInt32(v.to_scalar().to_u32()?),
448 ty::Uint(UintTy::U64) => CArg::UInt64(v.to_scalar().to_u64()?),
449 ty::Uint(UintTy::Usize) =>
450 CArg::USize(v.to_scalar().to_target_usize(cx)?.try_into().unwrap()),
451 ty::RawPtr(..) => {
452 let s = v.to_scalar().to_pointer(cx)?.addr();
453 // This relies on the `expose_provenance` in the `visit_reachable_allocs` callback
454 // above.
455 CArg::RawPtr(std::ptr::with_exposed_provenance_mut(s.bytes_usize()))
456 }
457 _ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty),
458 })
459}
src/tools/miri/src/shims/native_lib/trace/child.rs+13-17
......@@ -90,14 +90,6 @@ impl Supervisor {
9090 // Unwinding might be messed up due to partly protected memory, so let's abort if something
9191 // breaks inside here.
9292 let res = std::panic::abort_unwind(|| {
93 // SAFETY: We do not access machine memory past this point until the
94 // supervisor is ready to allow it.
95 // FIXME: this is sketchy, as technically the memory is still in the Rust Abstract Machine,
96 // and the compiler would be allowed to reorder accesses below this block...
97 unsafe {
98 Self::protect_pages(alloc.pages(), mman::ProtFlags::PROT_NONE).unwrap();
99 }
100
10193 // Send over the info.
10294 // NB: if we do not wait to receive a blank confirmation response, it is
10395 // possible that the supervisor is alerted of the SIGSTOP *before* it has
......@@ -110,16 +102,14 @@ impl Supervisor {
110102 // count.
111103 signal::raise(signal::SIGSTOP).unwrap();
112104
113 let res = f();
105 // SAFETY: We have coordinated with the supervisor to ensure that this memory will keep
106 // working as normal, just with extra tracing. So even if the compiler moves memory
107 // accesses down to after the `mprotect`, they won't actually segfault.
108 unsafe {
109 Self::protect_pages(alloc.pages(), mman::ProtFlags::PROT_NONE).unwrap();
110 }
114111
115 // We can't use IPC channels here to signal that FFI mode has ended,
116 // since they might allocate memory which could get us stuck in a SIGTRAP
117 // with no easy way out! While this could be worked around, it is much
118 // simpler and more robust to simply use the signals which are left for
119 // arbitrary usage. Since this will block until we are continued by the
120 // supervisor, we can assume past this point that everything is back to
121 // normal.
122 signal::raise(signal::SIGUSR1).unwrap();
112 let res = f();
123113
124114 // SAFETY: We set memory back to normal, so this is safe.
125115 unsafe {
......@@ -130,6 +120,12 @@ impl Supervisor {
130120 .unwrap();
131121 }
132122
123 // Signal the supervisor that we are done. Will block until the supervisor continues us.
124 // This will also shut down the segfault handler, so it's important that all memory is
125 // reset back to normal above. There must not be a window in time where accessing the
126 // pages we protected above actually causes the program to abort.
127 signal::raise(signal::SIGUSR1).unwrap();
128
133129 res
134130 });
135131
src/tools/miri/src/shims/native_lib/trace/parent.rs+34-48
......@@ -18,6 +18,11 @@ const ARCH_WORD_SIZE: usize = 4;
1818#[cfg(target_arch = "x86_64")]
1919const ARCH_WORD_SIZE: usize = 8;
2020
21// x86 max instruction length is 15 bytes:
22// https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
23// See vol. 3B section 24.25.
24const ARCH_MAX_INSTR_SIZE: usize = 15;
25
2126/// The address of the page set to be edited, initialised to a sentinel null
2227/// pointer.
2328static PAGE_ADDR: AtomicPtr<u8> = AtomicPtr::new(std::ptr::null_mut());
......@@ -132,10 +137,10 @@ impl Iterator for ChildListener {
132137 return Some(ExecEvent::Syscall(pid));
133138 },
134139 // Child with the given pid was stopped by the given signal.
135 // It's somewhat dubious when this is returned instead of
136 // WaitStatus::Stopped, but for our purposes they are the
137 // same thing.
138 wait::WaitStatus::PtraceEvent(pid, signal, _) =>
140 // It's somewhat unclear when which of these two is returned;
141 // we just treat them the same.
142 wait::WaitStatus::Stopped(pid, signal)
143 | wait::WaitStatus::PtraceEvent(pid, signal, _) =>
139144 if self.attached {
140145 // This is our end-of-FFI signal!
141146 if signal == signal::SIGUSR1 {
......@@ -148,19 +153,6 @@ impl Iterator for ChildListener {
148153 // Just pass along the signal.
149154 ptrace::cont(pid, signal).unwrap();
150155 },
151 // Child was stopped at the given signal. Same logic as for
152 // WaitStatus::PtraceEvent.
153 wait::WaitStatus::Stopped(pid, signal) =>
154 if self.attached {
155 if signal == signal::SIGUSR1 {
156 self.attached = false;
157 return Some(ExecEvent::End);
158 } else {
159 return Some(ExecEvent::Status(pid, signal));
160 }
161 } else {
162 ptrace::cont(pid, signal).unwrap();
163 },
164156 _ => (),
165157 },
166158 // This case should only trigger when all children died.
......@@ -250,7 +242,7 @@ pub fn sv_loop(
250242 // We can't trust simply calling `Pid::this()` in the child process to give the right
251243 // PID for us, so we get it this way.
252244 curr_pid = wait_for_signal(None, signal::SIGSTOP, InitialCont::No).unwrap();
253
245 // Continue until next syscall.
254246 ptrace::syscall(curr_pid, None).unwrap();
255247 }
256248 // Child wants to end tracing.
......@@ -289,8 +281,7 @@ pub fn sv_loop(
289281 }
290282 }
291283 },
292 // Child entered a syscall; we wait for exits inside of this, so it
293 // should never trigger on return from a syscall we care about.
284 // Child entered or exited a syscall. For now we ignore this and just continue.
294285 ExecEvent::Syscall(pid) => {
295286 ptrace::syscall(pid, None).unwrap();
296287 }
......@@ -344,8 +335,8 @@ fn wait_for_signal(
344335 return Err(ExecEnd(Some(code)));
345336 }
346337 wait::WaitStatus::Signaled(_, _, _) => return Err(ExecEnd(None)),
347 wait::WaitStatus::Stopped(pid, signal) => (signal, pid),
348 wait::WaitStatus::PtraceEvent(pid, signal, _) => (signal, pid),
338 wait::WaitStatus::Stopped(pid, signal)
339 | wait::WaitStatus::PtraceEvent(pid, signal, _) => (signal, pid),
349340 // This covers PtraceSyscall and variants that are impossible with
350341 // the flags set (e.g. WaitStatus::StillAlive).
351342 _ => {
......@@ -486,7 +477,27 @@ fn handle_segfault(
486477 let stack_ptr = ch_stack.strict_add(CALLBACK_STACK_SIZE / 2);
487478 let regs_bak = ptrace::getregs(pid).unwrap();
488479 let mut new_regs = regs_bak;
489 let ip_prestep = regs_bak.ip();
480
481 // Read at least one instruction from the ip. It's possible that the instruction
482 // that triggered the segfault was short and at the end of the mapped text area,
483 // so some of these reads may fail; in that case, just write empty bytes. If all
484 // reads failed, the disassembler will report an error.
485 let instr = (0..(ARCH_MAX_INSTR_SIZE.div_ceil(ARCH_WORD_SIZE)))
486 .flat_map(|ofs| {
487 // This reads one word of memory; we divided by `ARCH_WORD_SIZE` above to compensate for that.
488 ptrace::read(
489 pid,
490 std::ptr::without_provenance_mut(
491 regs_bak.ip().strict_add(ARCH_WORD_SIZE.strict_mul(ofs)),
492 ),
493 )
494 .unwrap_or_default()
495 .to_ne_bytes()
496 })
497 .collect::<Vec<_>>();
498
499 // Now figure out the size + type of access and log it down.
500 capstone_disassemble(&instr, addr, cs, acc_events).expect("Failed to disassemble instruction");
490501
491502 // Move the instr ptr into the deprotection code.
492503 #[expect(clippy::as_conversions)]
......@@ -526,33 +537,8 @@ fn handle_segfault(
526537 ptrace::write(pid, std::ptr::with_exposed_provenance_mut(a), 0).unwrap();
527538 }
528539
529 // Save registers and grab the bytes that were executed. This would
530 // be really nasty if it was a jump or similar but those thankfully
531 // won't do memory accesses and so can't trigger this!
532540 let regs_bak = ptrace::getregs(pid).unwrap();
533541 new_regs = regs_bak;
534 let ip_poststep = regs_bak.ip();
535
536 // Ensure that we've actually gone forwards.
537 assert!(ip_poststep > ip_prestep);
538 // But not by too much. 64 bytes should be "big enough" on ~any architecture.
539 assert!(ip_prestep.strict_add(64) > ip_poststep);
540
541 // We need to do reads/writes in word-sized chunks.
542 let diff = (ip_poststep.strict_sub(ip_prestep)).div_ceil(ARCH_WORD_SIZE);
543 let instr = (ip_prestep..ip_prestep.strict_add(diff)).fold(vec![], |mut ret, ip| {
544 // This only needs to be a valid pointer in the child process, not ours.
545 ret.append(
546 &mut ptrace::read(pid, std::ptr::without_provenance_mut(ip))
547 .unwrap()
548 .to_ne_bytes()
549 .to_vec(),
550 );
551 ret
552 });
553
554 // Now figure out the size + type of access and log it down.
555 capstone_disassemble(&instr, addr, cs, acc_events).expect("Failed to disassemble instruction");
556542
557543 // Reprotect everything and continue.
558544 #[expect(clippy::as_conversions)]
src/tools/miri/src/shims/unix/fd.rs+41-15
......@@ -264,14 +264,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
264264 return this.set_last_error_and_return(LibcError("EBADF"), dest);
265265 };
266266
267 // Handle the zero-sized case. The man page says:
268 // > If count is zero, read() may detect the errors described below. In the absence of any
269 // > errors, or if read() does not check for errors, a read() with a count of 0 returns zero
270 // > and has no other effects.
271 if count == 0 {
272 this.write_null(dest)?;
273 return interp_ok(());
274 }
267275 // Non-deterministically decide to further reduce the count, simulating a partial read (but
268 // never to 0, that has different behavior).
269 let count =
270 if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
271 count / 2
272 } else {
273 count
274 };
276 // never to 0, that would indicate EOF).
277 let count = if this.machine.short_fd_operations
278 && fd.short_fd_operations()
279 && count >= 2
280 && this.machine.rng.get_mut().random()
281 {
282 count / 2 // since `count` is at least 2, the result is still at least 1
283 } else {
284 count
285 };
275286
276287 trace!("read: FD mapped to {fd:?}");
277288 // We want to read at most `count` bytes. We are sure that `count` is not negative
......@@ -338,14 +349,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
338349 return this.set_last_error_and_return(LibcError("EBADF"), dest);
339350 };
340351
341 // Non-deterministically decide to further reduce the count, simulating a partial write (but
342 // never to 0, that has different behavior).
343 let count =
344 if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
345 count / 2
346 } else {
347 count
348 };
352 // Handle the zero-sized case. The man page says:
353 // > If count is zero and fd refers to a regular file, then write() may return a failure
354 // > status if one of the errors below is detected. If no errors are detected, or error
355 // > detection is not performed, 0 is returned without causing any other effect. If count
356 // > is zero and fd refers to a file other than a regular file, the results are not
357 // > specified.
358 if count == 0 {
359 // For now let's not open the can of worms of what exactly "not specified" could mean...
360 this.write_null(dest)?;
361 return interp_ok(());
362 }
363 // Non-deterministically decide to further reduce the count, simulating a partial write.
364 // We avoid reducing the write size to 0: the docs seem to be entirely fine with that,
365 // but the standard library is not (https://github.com/rust-lang/rust/issues/145959).
366 let count = if this.machine.short_fd_operations
367 && fd.short_fd_operations()
368 && count >= 2
369 && this.machine.rng.get_mut().random()
370 {
371 count / 2
372 } else {
373 count
374 };
349375
350376 let finish = {
351377 let dest = dest.clone();
src/tools/miri/src/shims/unix/linux_like/eventfd.rs-5
......@@ -37,11 +37,6 @@ impl FileDescription for EventFd {
3737 "event"
3838 }
3939
40 fn nondet_short_accesses(&self) -> bool {
41 // We always read and write exactly one `u64`.
42 false
43 }
44
4540 fn close<'tcx>(
4641 self,
4742 _communicate_allowed: bool,
src/tools/miri/src/shims/unix/unnamed_socket.rs+8
......@@ -123,6 +123,14 @@ impl FileDescription for AnonSocket {
123123 anonsocket_write(self, ptr, len, ecx, finish)
124124 }
125125
126 fn short_fd_operations(&self) -> bool {
127 // Pipes guarantee that sufficiently small accesses are not broken apart:
128 // <https://pubs.opengroup.org/onlinepubs/9799919799/functions/write.html#tag_17_699_08>.
129 // For now, we don't bother checking for the size, and just entirely disable
130 // short accesses on pipes.
131 matches!(self.fd_type, AnonSocketType::Socketpair)
132 }
133
126134 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
127135 self
128136 }
src/tools/miri/src/shims/windows/handle.rs+5-3
......@@ -289,9 +289,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
289289 }
290290
291291 if this.ptr_is_null(target_handle_ptr)? {
292 throw_unsup_format!(
293 "`DuplicateHandle` `lpTargetHandle` parameter is null, which is unsupported"
294 );
292 throw_machine_stop!(TerminationInfo::Abort(
293 "`DuplicateHandle` `lpTargetHandle` parameter must not be null, as otherwise the \
294 newly created handle is leaked"
295 .to_string()
296 ));
295297 }
296298
297299 if options != this.eval_windows("c", "DUPLICATE_SAME_ACCESS") {
src/tools/miri/tests/deps/Cargo.lock+2-2
......@@ -296,9 +296,9 @@ dependencies = [
296296
297297[[package]]
298298name = "slab"
299version = "0.4.10"
299version = "0.4.11"
300300source = "registry+https://github.com/rust-lang/crates.io-index"
301checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
301checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
302302
303303[[package]]
304304name = "socket2"
src/tools/miri/tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.rs created+9
......@@ -0,0 +1,9 @@
1//@compile-flags: -Zmiri-tree-borrows
2
3fn main() {
4 // Since the "inside" part is `!Freeze`, the permission to mutate is gone.
5 let pair = ((), 1);
6 let x = &pair.0;
7 let ptr = (&raw const *x).cast::<i32>().cast_mut();
8 unsafe { ptr.write(0) }; //~ERROR: /write access .* forbidden/
9}
src/tools/miri/tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.stderr created+21
......@@ -0,0 +1,21 @@
1error: Undefined Behavior: write access through <TAG> at ALLOC[0x0] is forbidden
2 --> tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.rs:LL:CC
3 |
4LL | unsafe { ptr.write(0) };
5 | ^^^^^^^^^^^^ Undefined Behavior occurred here
6 |
7 = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
8 = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
9 = help: the accessed tag <TAG> has state Frozen which forbids this child write access
10help: the accessed tag <TAG> was created here, in the initial state Frozen
11 --> tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.rs:LL:CC
12 |
13LL | let x = &pair.0;
14 | ^^^^^^^
15 = note: BACKTRACE (of the first span):
16 = note: inside `main` at tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.rs:LL:CC
17
18note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
19
20error: aborting due to 1 previous error
21
src/tools/miri/tests/native-lib/aggregate_arguments.c created+52
......@@ -0,0 +1,52 @@
1#include <stdint.h>
2
3// See comments in build_native_lib()
4#define EXPORT __attribute__((visibility("default")))
5
6/* Test: fail/pass_struct_expose_only_range */
7
8typedef struct HasPointer {
9 uint8_t *ptr;
10} HasPointer;
11
12EXPORT uint8_t access_struct_ptr(const HasPointer s) {
13 return *s.ptr;
14}
15
16/* Test: test_pass_struct */
17
18typedef struct PassMe {
19 int32_t value;
20 int64_t other_value;
21} PassMe;
22
23EXPORT int64_t pass_struct(const PassMe pass_me) {
24 return pass_me.value + pass_me.other_value;
25}
26
27/* Test: test_pass_struct_complex */
28
29typedef struct Part1 {
30 uint16_t high;
31 uint16_t low;
32} Part1;
33
34typedef struct Part2 {
35 uint32_t bits;
36} Part2;
37
38typedef struct ComplexStruct {
39 Part1 part_1;
40 Part2 part_2;
41 uint32_t part_3;
42} ComplexStruct;
43
44EXPORT int32_t pass_struct_complex(const ComplexStruct complex, uint16_t high, uint16_t low, uint32_t bits) {
45 if (complex.part_1.high == high && complex.part_1.low == low
46 && complex.part_2.bits == bits
47 && complex.part_3 == bits)
48 return 0;
49 else {
50 return 1;
51 }
52}
src/tools/miri/tests/native-lib/fail/pass_struct_expose_only_range.rs created+23
......@@ -0,0 +1,23 @@
1//@compile-flags: -Zmiri-permissive-provenance
2
3#[repr(C)]
4#[derive(Copy, Clone)]
5struct HasPointer {
6 ptr: *const u8,
7}
8
9extern "C" {
10 fn access_struct_ptr(s: HasPointer) -> u8;
11}
12
13fn main() {
14 let structs = vec![HasPointer { ptr: &0 }, HasPointer { ptr: &1 }];
15 unsafe {
16 let r = access_struct_ptr(structs[1]);
17 assert_eq!(r, 1);
18 // There are two pointers stored in the allocation backing `structs`; ensure
19 // we only exposed the one that was actually passed to C.
20 let _val = *std::ptr::with_exposed_provenance::<u8>(structs[1].ptr.addr()); // fine, ptr got sent to C
21 let _val = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr()); //~ ERROR: memory access failed
22 };
23}
src/tools/miri/tests/native-lib/fail/pass_struct_expose_only_range.stderr created+28
......@@ -0,0 +1,28 @@
1warning: sharing memory with a native function called via FFI
2 --> tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
3 |
4LL | let r = access_struct_ptr(structs[1]);
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ sharing memory with a native function
6 |
7 = help: when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory
8 = help: in particular, Miri assumes that the native call initializes all memory it has access to
9 = help: Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory
10 = help: what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free
11 = note: BACKTRACE:
12 = note: inside `main` at tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
13
14error: Undefined Behavior: memory access failed: attempting to access 1 byte, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
15 --> tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
16 |
17LL | let _val = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr());
18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
19 |
20 = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
21 = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
22 = note: BACKTRACE:
23 = note: inside `main` at tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
24
25note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
26
27error: aborting due to 1 previous error; 1 warning emitted
28
src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs created+19
......@@ -0,0 +1,19 @@
1// Only works on Unix targets
2//@ignore-target: windows wasm
3//@only-on-host
4
5#![allow(improper_ctypes)]
6
7pub struct PassMe {
8 pub value: i32,
9 pub other_value: i64,
10}
11
12extern "C" {
13 fn pass_struct(s: PassMe) -> i64;
14}
15
16fn main() {
17 let pass_me = PassMe { value: 42, other_value: 1337 };
18 unsafe { pass_struct(pass_me) }; //~ ERROR: unsupported operation: passing a non-#[repr(C)] struct over FFI
19}
src/tools/miri/tests/native-lib/fail/struct_not_extern_c.stderr created+14
......@@ -0,0 +1,14 @@
1error: unsupported operation: passing a non-#[repr(C)] struct over FFI: PassMe
2 --> tests/native-lib/fail/struct_not_extern_c.rs:LL:CC
3 |
4LL | unsafe { pass_struct(pass_me) };
5 | ^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
6 |
7 = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
8 = note: BACKTRACE:
9 = note: inside `main` at tests/native-lib/fail/struct_not_extern_c.rs:LL:CC
10
11note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
12
13error: aborting due to 1 previous error
14
src/tools/miri/tests/native-lib/fail/uninit_struct.rs created+27
......@@ -0,0 +1,27 @@
1#[repr(C)]
2#[derive(Copy, Clone)]
3struct ComplexStruct {
4 part_1: Part1,
5 part_2: Part2,
6 part_3: u32,
7}
8#[repr(C)]
9#[derive(Copy, Clone)]
10struct Part1 {
11 high: u16,
12 low: u16,
13}
14#[repr(C)]
15#[derive(Copy, Clone)]
16struct Part2 {
17 bits: u32,
18}
19
20extern "C" {
21 fn pass_struct_complex(s: ComplexStruct, high: u16, low: u16, bits: u32) -> i32;
22}
23
24fn main() {
25 let arg = std::mem::MaybeUninit::<ComplexStruct>::uninit();
26 unsafe { pass_struct_complex(*arg.as_ptr(), 0, 0, 0) }; //~ ERROR: Undefined Behavior: constructing invalid value
27}
src/tools/miri/tests/native-lib/fail/uninit_struct.stderr created+15
......@@ -0,0 +1,15 @@
1error: Undefined Behavior: constructing invalid value at .part_1.high: encountered uninitialized memory, but expected an integer
2 --> tests/native-lib/fail/uninit_struct.rs:LL:CC
3 |
4LL | unsafe { pass_struct_complex(*arg.as_ptr(), 0, 0, 0) };
5 | ^^^^^^^^^^^^^ Undefined Behavior occurred here
6 |
7 = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8 = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9 = note: BACKTRACE:
10 = note: inside `main` at tests/native-lib/fail/uninit_struct.rs:LL:CC
11
12note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13
14error: aborting due to 1 previous error
15
src/tools/miri/tests/native-lib/pass/aggregate_arguments.rs created+52
......@@ -0,0 +1,52 @@
1fn main() {
2 test_pass_struct();
3 test_pass_struct_complex();
4}
5
6/// Test passing a basic struct as an argument.
7fn test_pass_struct() {
8 // Exactly two fields, so that we hit the ScalarPair case.
9 #[repr(C)]
10 struct PassMe {
11 value: i32,
12 other_value: i64,
13 }
14
15 extern "C" {
16 fn pass_struct(s: PassMe) -> i64;
17 }
18
19 let pass_me = PassMe { value: 42, other_value: 1337 };
20 assert_eq!(unsafe { pass_struct(pass_me) }, 42 + 1337);
21}
22
23/// Test passing a more complex struct as an argument.
24fn test_pass_struct_complex() {
25 #[repr(C)]
26 struct ComplexStruct {
27 part_1: Part1,
28 part_2: Part2,
29 part_3: u32,
30 }
31 #[repr(C)]
32 struct Part1 {
33 high: u16,
34 low: u16,
35 }
36 #[repr(C)]
37 struct Part2 {
38 bits: u32,
39 }
40
41 extern "C" {
42 fn pass_struct_complex(s: ComplexStruct, high: u16, low: u16, bits: u32) -> i32;
43 }
44
45 let high = 0xabcd;
46 let low = 0xef01;
47 let bits = 0xabcdef01;
48
49 let complex =
50 ComplexStruct { part_1: Part1 { high, low }, part_2: Part2 { bits }, part_3: bits };
51 assert_eq!(unsafe { pass_struct_complex(complex, high, low, bits) }, 0);
52}
src/tools/miri/tests/native-lib/pass/ptr_read_access.rs+1
......@@ -1,6 +1,7 @@
11//@revisions: trace notrace
22//@[trace] only-target: x86_64-unknown-linux-gnu i686-unknown-linux-gnu
33//@[trace] compile-flags: -Zmiri-native-lib-enable-tracing
4//@compile-flags: -Zmiri-permissive-provenance
45
56fn main() {
67 test_access_pointer();
src/tools/miri/tests/pass-dep/shims/windows-fs.rs+41-6
......@@ -2,20 +2,20 @@
22//@compile-flags: -Zmiri-disable-isolation
33#![allow(nonstandard_style)]
44
5use std::io::{ErrorKind, Read, Write};
5use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
66use std::os::windows::ffi::OsStrExt;
7use std::os::windows::io::AsRawHandle;
7use std::os::windows::io::{AsRawHandle, FromRawHandle};
88use std::path::Path;
9use std::{fs, ptr};
9use std::{fs, mem, ptr};
1010
1111#[path = "../../utils/mod.rs"]
1212mod utils;
1313
1414use windows_sys::Wdk::Storage::FileSystem::{NtReadFile, NtWriteFile};
1515use windows_sys::Win32::Foundation::{
16 CloseHandle, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, ERROR_IO_DEVICE, GENERIC_READ,
17 GENERIC_WRITE, GetLastError, RtlNtStatusToDosError, STATUS_ACCESS_DENIED,
18 STATUS_IO_DEVICE_ERROR, STATUS_SUCCESS, SetLastError,
16 CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS,
17 ERROR_IO_DEVICE, FALSE, GENERIC_READ, GENERIC_WRITE, GetLastError, RtlNtStatusToDosError,
18 STATUS_ACCESS_DENIED, STATUS_IO_DEVICE_ERROR, STATUS_SUCCESS, SetLastError,
1919};
2020use windows_sys::Win32::Storage::FileSystem::{
2121 BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, DeleteFileW,
......@@ -24,6 +24,7 @@ use windows_sys::Win32::Storage::FileSystem::{
2424 FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING, SetFilePointerEx,
2525};
2626use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
27use windows_sys::Win32::System::Threading::GetCurrentProcess;
2728
2829fn main() {
2930 unsafe {
......@@ -36,6 +37,7 @@ fn main() {
3637 test_ntstatus_to_dos();
3738 test_file_read_write();
3839 test_file_seek();
40 test_dup_handle();
3941 }
4042}
4143
......@@ -273,6 +275,39 @@ unsafe fn test_file_read_write() {
273275 assert_eq!(GetLastError(), 1234);
274276}
275277
278unsafe fn test_dup_handle() {
279 let temp = utils::tmp().join("test_dup.txt");
280
281 let mut file1 = fs::File::options().read(true).write(true).create(true).open(&temp).unwrap();
282
283 file1.write_all(b"Hello, World!\n").unwrap();
284 file1.seek(SeekFrom::Start(0)).unwrap();
285
286 let first_handle = file1.as_raw_handle();
287
288 let cur_proc = GetCurrentProcess();
289 let mut second_handle = mem::zeroed();
290 let res = DuplicateHandle(
291 cur_proc,
292 first_handle,
293 cur_proc,
294 &mut second_handle,
295 0,
296 FALSE,
297 DUPLICATE_SAME_ACCESS,
298 );
299 assert!(res != 0);
300
301 let mut buf1 = [0; 5];
302 file1.read(&mut buf1).unwrap();
303 assert_eq!(&buf1, b"Hello");
304
305 let mut file2 = fs::File::from_raw_handle(second_handle);
306 let mut buf2 = [0; 5];
307 file2.read(&mut buf2).unwrap();
308 assert_eq!(&buf2, b", Wor");
309}
310
276311unsafe fn test_file_seek() {
277312 let temp = utils::tmp().join("test_file_seek.txt");
278313 let mut file = fs::File::options().create(true).write(true).read(true).open(&temp).unwrap();
src/tools/miri/tests/pass/both_borrows/basic_aliasing_model.rs+21-1
......@@ -23,7 +23,8 @@ fn main() {
2323 not_unpin_not_protected();
2424 write_does_not_invalidate_all_aliases();
2525 box_into_raw_allows_interior_mutable_alias();
26 cell_inside_struct()
26 cell_inside_struct();
27 zst();
2728}
2829
2930// Make sure that reading from an `&mut` does, like reborrowing to `&`,
......@@ -287,3 +288,22 @@ fn cell_inside_struct() {
287288 // Writing to `field1`, which is reserved, should also be allowed.
288289 (*a).field1 = 88;
289290}
291
292/// ZST reborrows on various kinds of dangling pointers are valid.
293fn zst() {
294 unsafe {
295 // Integer pointer.
296 let ptr = ptr::without_provenance_mut::<()>(15);
297 let _ref = &mut *ptr;
298
299 // Out-of-bounds pointer.
300 let mut b = Box::new(0u8);
301 let ptr = (&raw mut *b).wrapping_add(15) as *mut ();
302 let _ref = &mut *ptr;
303
304 // Deallocated pointer.
305 let ptr = &raw mut *b as *mut ();
306 drop(b);
307 let _ref = &mut *ptr;
308 }
309}
src/tools/miri/tests/pass/concurrency/sync.rs+2-1
......@@ -9,7 +9,8 @@ use std::time::{Duration, Instant};
99
1010// We are expecting to sleep for 10ms. How long of a sleep we are accepting?
1111// Even with 1000ms we still see this test fail on macOS runners.
12const MAX_SLEEP_TIME_MS: u64 = 2000;
12// On a aarch64-pc-windows-msvc runner, we saw 2.7s!
13const MAX_SLEEP_TIME_MS: u64 = 4000;
1314
1415// Check if Rust barriers are working.
1516
src/tools/miri/tests/pass/float.rs+8-3
......@@ -39,9 +39,8 @@ macro_rules! assert_approx_eq {
3939 }};
4040
4141 ($a:expr, $b: expr) => {
42 // accept up to 12ULP (4ULP for host floats and 4ULP for miri artificial error and 4 for any additional effects
43 // due to having multiple error sources.
44 assert_approx_eq!($a, $b, 12);
42 // accept up to 8ULP (4ULP for host floats and 4ULP for miri artificial error).
43 assert_approx_eq!($a, $b, 8);
4544 };
4645}
4746
......@@ -176,6 +175,7 @@ fn assert_eq_msg<T: PartialEq + Debug>(x: T, y: T, msg: impl Display) {
176175}
177176
178177/// Check that floats have bitwise equality
178#[track_caller]
179179fn assert_biteq<F: Float>(a: F, b: F, msg: impl Display) {
180180 let ab = a.to_bits();
181181 let bb = b.to_bits();
......@@ -189,6 +189,7 @@ fn assert_biteq<F: Float>(a: F, b: F, msg: impl Display) {
189189}
190190
191191/// Check that two floats have equality
192#[track_caller]
192193fn assert_feq<F: Float>(a: F, b: F, msg: impl Display) {
193194 let ab = a.to_bits();
194195 let bb = b.to_bits();
......@@ -1051,6 +1052,10 @@ pub fn libm() {
10511052 assert_eq!(42f64.powf(0.0), 1.0);
10521053 assert_eq!(f32::INFINITY.powf(0.0), 1.0);
10531054 assert_eq!(f64::INFINITY.powf(0.0), 1.0);
1055 assert_eq!(f32::NEG_INFINITY.powi(3), f32::NEG_INFINITY);
1056 assert_eq!(f32::NEG_INFINITY.powi(2), f32::INFINITY);
1057 assert_eq!(f64::INFINITY.powi(3), f64::INFINITY);
1058 assert_eq!(f64::INFINITY.powi(2), f64::INFINITY);
10541059
10551060 // f*::NAN is a quiet NAN and should return 1 as well.
10561061 assert_eq!(f32::NAN.powi(0), 1.0);
src/tools/miri/tests/pass/float_extra_rounding_error.rs created+31
......@@ -0,0 +1,31 @@
1//! Check that the flags to control the extra rounding error work.
2//@revisions: random max none
3//@[max]compile-flags: -Zmiri-max-extra-rounding-error
4//@[none]compile-flags: -Zmiri-no-extra-rounding-error
5#![feature(cfg_select)]
6
7use std::collections::HashSet;
8use std::hint::black_box;
9
10fn main() {
11 let expected = cfg_select! {
12 random => 9, // -4 ..= +4 ULP error
13 max => 2,
14 none => 1,
15 };
16 // Call `sin(0.5)` a bunch of times and see how many different values we get.
17 let mut values = HashSet::new();
18 for _ in 0..(expected * 16) {
19 let val = black_box(0.5f64).sin();
20 values.insert(val.to_bits());
21 }
22 assert_eq!(values.len(), expected);
23
24 if !cfg!(none) {
25 // Ensure the smallest and biggest value are 8 ULP apart.
26 // We can just subtract the raw bit representations for this.
27 let min = *values.iter().min().unwrap();
28 let max = *values.iter().max().unwrap();
29 assert_eq!(min.abs_diff(max), 8);
30 }
31}
src/tools/miri/tests/pass/shims/fs.rs+3-1
......@@ -72,7 +72,9 @@ fn test_file() {
7272
7373 // Writing to a file opened for reading should error (and not stop interpretation). std does not
7474 // categorize the error so we don't check for details.
75 file.write(&[]).unwrap_err();
75 file.write(&[0]).unwrap_err();
76 // However, writing 0 bytes can succeed or fail.
77 let _ignore = file.write(&[]);
7678
7779 // Removing file should succeed.
7880 remove_file(&path).unwrap();
src/tools/miri/tests/pass/shims/x86/rounding-error.rs created+37
......@@ -0,0 +1,37 @@
1// We're testing x86 target specific features
2//@only-target: x86_64 i686
3
4//! rsqrt and rcp SSE/AVX operations are approximate. We use that as license to treat them as
5//! non-deterministic. Ensure that we do indeed see random results within the expected error bounds.
6
7#[cfg(target_arch = "x86")]
8use std::arch::x86::*;
9#[cfg(target_arch = "x86_64")]
10use std::arch::x86_64::*;
11use std::collections::HashSet;
12
13fn main() {
14 let mut vals = HashSet::new();
15 for _ in 0..50 {
16 unsafe {
17 // Compute the inverse square root of 4.0, four times.
18 let a = _mm_setr_ps(4.0, 4.0, 4.0, 4.0);
19 let exact = 0.5;
20 let r = _mm_rsqrt_ps(a);
21 let r: [f32; 4] = std::mem::transmute(r);
22 // Check the results.
23 for r in r {
24 vals.insert(r.to_bits());
25 // Ensure the relative error is less than 2^-12.
26 let rel_error = (r - exact) / exact;
27 let log_error = rel_error.abs().log2();
28 assert!(
29 rel_error == 0.0 || log_error < -12.0,
30 "got an error of {rel_error} = 2^{log_error}"
31 );
32 }
33 }
34 }
35 // Ensure we saw a bunch of different results.
36 assert!(vals.len() >= 50);
37}
src/tools/miri/tests/pass/tree_borrows/cell-lazy-write-to-surrounding.rs+6-4
......@@ -14,9 +14,11 @@ fn main() {
1414 foo(&arr[0]);
1515
1616 let pair = (Cell::new(1), 1);
17 // TODO: Ideally, this would result in UB since the second element
18 // in `pair` is Frozen. We would need some way to express a
19 // "shared reference with permission to access surrounding
20 // interior mutable data".
2117 foo(&pair.0);
18
19 // As long as the "inside" part is `!Freeze`, the permission to mutate the "outside" is preserved.
20 let pair = (Cell::new(()), 1);
21 let x = &pair.0;
22 let ptr = (&raw const *x).cast::<i32>().cast_mut();
23 unsafe { ptr.write(0) };
2224}
src/tools/miri/tests/ui.rs+1
......@@ -60,6 +60,7 @@ fn build_native_lib(target: &str) -> PathBuf {
6060 native_lib_path.to_str().unwrap(),
6161 // FIXME: Automate gathering of all relevant C source files in the directory.
6262 "tests/native-lib/scalar_arguments.c",
63 "tests/native-lib/aggregate_arguments.c",
6364 "tests/native-lib/ptr_read_access.c",
6465 "tests/native-lib/ptr_write_access.c",
6566 // Ensure we notice serious problems in the C code.
src/tools/miri/tests/utils/libc.rs+1-4
......@@ -34,10 +34,7 @@ pub unsafe fn write_all(
3434 if res < 0 {
3535 return res;
3636 }
37 if res == 0 {
38 // EOF?
39 break;
40 }
37 // Apparently a return value of 0 is just a short write, nothing special (unlike reads).
4138 written_so_far += res as libc::size_t;
4239 }
4340 return written_so_far as libc::ssize_t;