Skip to content
This repository was archived by the owner on Jan 7, 2022. It is now read-only.

Commit 727080f

Browse files
bors[bot]vext01
andcommitted
Merge #4
4: Software Trace Function in C. r=ltratt a=vext01 This is a DNMY (do no merge yet) PR. It needs some discussion. As discussed earlier this week, this is an attempt to implement the tracing function in C so that our MIR pass doesn't see the innards of the tracing mechanisms and it's dependencies. This means we can trace a lot more than we could have before, where e.g. thread locals and vectors used in the tracing mechanism were blocked from being traced. I printed the trace from one of my tests, and it looks highly plausable (tm): ``` MirLoc { crate_hash: 7687613001418581878, def_idx: 14, bb_idx: 1 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 0 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 1 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 2 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 3 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 6 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 7 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 8 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 9 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 2 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 3 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 6 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 7 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 8 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 9 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 2 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 3 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 6 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 7 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 8 } MirLoc { crate_hash: 7687613001418581878, def_idx: 16, bb_idx: 9 } ... ``` I can see a loop! # Discussion Points ## Low-level of abstraction. Because the tracing function is now in C, and because we no longer need `Vec` or `std::thread` from `libstd` to trace, I opted to move the whole mechanism into `libcore`. This is better in the sense that we can trace stuff even if libstd isn't present (a dependency of the crate being traced). On the other hand, working in libcore is limiting and it's evident from the low level of abstraction in the API you see. I'd have liked to, for example, have given the user an iterator and a `Drop` implementation for the trace struct, but these are `libstd` concepts. I propose that in the next PR we make the libcore interface even more low-level -- make `stop_tracing` return a tuple, not a struct -- and then move the trace struct up into `libstd` where it can have better abstraction. This should mean that VM authors get a nice API to start and stop tracing via libstd, but they can still trace `no_std` crates that their VMs might depend upon. ## Traits Looking to the future, we will eventually have two backends (sw/hw) and a compile time switch to choose one. I suspect that we will want to offer the same API regardless of the backend, so we probably want a place to keep the common parts (e.g. `struct MirLoc` can probably be shared) and a trait for the API which includes stuff like getting an iterator over a trace. This should be a PR in the near future. ## C Errors Currently the C tracing code will crash hard (with `err()` or `errx()`) if there is an error. We could implement something like we did in hwtracer where each FFI call passes down an error struct pointer, which can be read by Rust after the call. Since this change can be hidden from the user in abstractions, I think this is low-priority for now. Do you agree with the above plan? Cheers Co-authored-by: Edd Barrett <vext01@gmail.com>
2 parents bde262e + 12a2499 commit 727080f

39 files changed

Lines changed: 492 additions & 99 deletions

src/Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ dependencies = [
444444
name = "core"
445445
version = "0.0.0"
446446
dependencies = [
447+
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
447448
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
448449
]
449450

src/libcore/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ path = "../libcore/benches/lib.rs"
2121

2222
[dev-dependencies]
2323
rand = "0.5"
24+
25+
[build-dependencies]
26+
cc = "1.0"

src/libcore/build.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2019 King's College London.
2+
// Created by the Software Development Team <http://soft-dev.org/>.
3+
//
4+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7+
// option. This file may not be copied, modified, or distributed
8+
// except according to those terms.
9+
10+
extern crate cc;
11+
12+
fn main() {
13+
let mut c_build = cc::Build::new();
14+
15+
c_build.file("yk_swt_impl.c");
16+
c_build.compile("yk_swt_impl");
17+
c_build.flag("-std=c11");
18+
c_build.warnings(true);
19+
c_build.extra_warnings(true);
20+
21+
println!("cargo:rerun-if-changed=yk_swt_impl.c");
22+
}

src/libcore/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ mod nonzero;
229229
mod tuple;
230230
mod unit;
231231

232-
mod yk_swt;
232+
/// Yorick software tracing.
233+
#[unstable(feature = "yk_swt", issue = "0")]
234+
pub mod yk_swt;
233235

234236
// Pull in the `coresimd` crate directly into libcore. This is where all the
235237
// architecture-specific (and vendor-specific) intrinsics are defined. AKA

src/libcore/yk_swt.rs

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,91 @@
77
// option. This file may not be copied, modified, or distributed
88
// except according to those terms.
99

10-
/// The software trace recorder function.
11-
/// This is a weak language item, it actually resides in libstd. It has to be weak to allow libcore
12-
/// to call up to libstd (libstd is not a dependency of libcore).
13-
extern "Rust" {
14-
#[cfg_attr(not(stage0), lang="yk_swt_rec_loc")]
15-
fn yk_swt_rec_loc(crate_hash: u64, def_idx: u32, bb: u32);
10+
/// The result of `yk_swt_stop_tracing_impl()` is an array of this C struct.
11+
#[repr(C)]
12+
#[derive(Debug)]
13+
pub struct MirLoc {
14+
/// Unique identifier for the crate.
15+
pub crate_hash: u64,
16+
/// The definition index.
17+
pub def_idx: u32,
18+
/// The basic block index.
19+
pub bb_idx: u32,
20+
}
21+
22+
/// Wraps the raw C trace buffer and exposes a more "Rusty" interface to it.
23+
#[derive(Debug)]
24+
#[allow(dead_code)]
25+
pub struct SWTrace {
26+
/// A heap allocated array of `MirLoc` structs, which the consumer must free. Ideally we'd
27+
/// have this struct implement `std::ops::Drop` or at least provide a method to do the freeing,
28+
/// but we can do neither due to libcore restrictions.
29+
buf: *mut MirLoc,
30+
/// The number of items in the above array.
31+
len: usize,
1632
}
1733

18-
/// Wrapper lang item to call the above wrapper function.
19-
/// This has to be a lang item too, as a MIR terminator cannot call a weak language item directly.
34+
impl SWTrace {
35+
/// Returns the number of MIR locations recorded in the trace.
36+
pub fn len(&self) -> usize {
37+
self.len
38+
}
39+
40+
/// Return a pointer to the raw trace buffer. The consumer *must* free this pointer.
41+
pub fn buf(self) -> *mut MirLoc {
42+
self.buf
43+
}
44+
45+
/// Returns the location at index `idx` or `None` if the index is out of bounds.
46+
pub fn loc<'a>(&'a self, idx: usize) -> &'a MirLoc {
47+
if idx >= self.len {
48+
panic!("software trace index out of bounds: len={}, idx={}", self.len, idx);
49+
} else {
50+
if idx > isize::max_value() as usize {
51+
panic!("index too large for ptr arithmetic");
52+
}
53+
unsafe { &*self.buf.offset(idx as isize) }
54+
}
55+
}
56+
}
57+
58+
/// The software trace recorder function.
59+
/// This is implemented in C so that: the `yk_swt_calls` MIR pass doesn't see inside.
2060
#[allow(dead_code)] // Used only indirectly in a MIR pass.
21-
#[cfg_attr(not(stage0), lang="yk_swt_rec_loc_wrap")]
61+
#[cfg_attr(not(stage0), lang="yk_swt_rec_loc")]
62+
#[cfg_attr(not(stage0), no_trace)]
63+
#[cfg(not(test))]
64+
fn yk_swt_rec_loc(crate_hash: u64, def_idx: u32, bb_idx: u32) {
65+
extern "C" { fn yk_swt_rec_loc_impl(crate_hash: u64, def_idx: u32, bb_idx: u32); }
66+
unsafe { yk_swt_rec_loc_impl(crate_hash, def_idx, bb_idx); }
67+
}
68+
69+
/// Start software tracing on the current thread. The current thread must not already be tracing.
2270
#[cfg_attr(not(stage0), no_trace)]
23-
fn yk_swt_rec_loc_wrap(crate_hash: u64, def_idx: u32, bb: u32) {
24-
unsafe { yk_swt_rec_loc(crate_hash, def_idx, bb) };
71+
pub fn start_tracing() {
72+
extern "C" { fn yk_swt_start_tracing_impl(); }
73+
unsafe { yk_swt_start_tracing_impl(); }
2574
}
2675

76+
/// Stop software tracing and return the trace, or `None` if the trace was invalidated.
77+
/// The current thread must already be tracing.
78+
#[cfg_attr(not(stage0), no_trace)]
79+
pub fn stop_tracing() -> Option<SWTrace> {
80+
let len: usize = 0;
81+
82+
extern "C" { fn yk_swt_stop_tracing_impl(ret_len: &usize) -> *mut MirLoc; }
83+
let buf = unsafe { yk_swt_stop_tracing_impl(&len) };
84+
85+
if buf.is_null() {
86+
None
87+
} else {
88+
Some(SWTrace { buf, len })
89+
}
90+
}
91+
92+
/// Invalidate the software trace, if one is being collected.
93+
#[cfg_attr(not(stage0), no_trace)]
94+
pub fn invalidate_trace() {
95+
extern "C" { fn yk_swt_invalidate_trace_impl(); }
96+
unsafe { yk_swt_invalidate_trace_impl(); }
97+
}

src/libcore/yk_swt_impl.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2019 King's College London.
2+
// Created by the Software Development Team <http://soft-dev.org/>.
3+
//
4+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7+
// option. This file may not be copied, modified, or distributed
8+
// except according to those terms.
9+
10+
#include <stdint.h>
11+
#include <stdlib.h>
12+
#include <err.h>
13+
#include <stdbool.h>
14+
#include <stdatomic.h>
15+
16+
struct mir_loc {
17+
uint64_t crate_hash;
18+
uint32_t def_idx;
19+
uint32_t bb_idx;
20+
};
21+
22+
#define TL_TRACE_INIT_CAP 1024
23+
#define TL_TRACE_REALLOC_CAP 1024
24+
25+
void yk_swt_start_tracing_impl(void);
26+
void yk_swt_rec_loc_impl(uint64_t crate_hash, uint32_t def_idx, uint32_t bb_idx);
27+
struct mir_loc *yk_swt_stop_tracing_impl(size_t *ret_trace_len);
28+
void yk_swt_invalidate_trace_impl(void);
29+
30+
// The trace buffer.
31+
static __thread struct mir_loc *trace_buf = NULL;
32+
// The number of elements in the trace buffer.
33+
static __thread size_t trace_buf_len = 0;
34+
// The allocation capacity of the trace buffer (in elements).
35+
static __thread size_t trace_buf_cap = 0;
36+
// Is the current thread tracing?
37+
// true = we are tracing, false = we are not tracing or an error occurred.
38+
static __thread volatile atomic_bool tracing = false;
39+
40+
// Start tracing on the current thread.
41+
// A new trace buffer is allocated and MIR locations will be written into it on
42+
// subsequent calls to `yk_swt_rec_loc_impl`. If the current thread is already
43+
// tracing, calling this will lead to undefined behaviour.
44+
void
45+
yk_swt_start_tracing_impl(void) {
46+
trace_buf = calloc(TL_TRACE_INIT_CAP, sizeof(struct mir_loc));
47+
if (trace_buf == NULL) {
48+
err(EXIT_FAILURE, "%s: calloc: ", __func__);
49+
}
50+
51+
trace_buf_cap = TL_TRACE_INIT_CAP;
52+
atomic_store_explicit(&tracing, true, memory_order_relaxed);
53+
}
54+
55+
// Record a location into the trace buffer if tracing is enabled on the current thread.
56+
void
57+
yk_swt_rec_loc_impl(uint64_t crate_hash, uint32_t def_idx, uint32_t bb_idx)
58+
{
59+
if (!atomic_load_explicit(&tracing, memory_order_relaxed)) {
60+
return;
61+
}
62+
63+
// Check if we need more space and reallocate if necessary.
64+
if (trace_buf_len == trace_buf_cap) {
65+
if (trace_buf_cap >= SIZE_MAX - TL_TRACE_REALLOC_CAP) {
66+
// Trace capacity would overflow.
67+
atomic_store_explicit(&tracing, false, memory_order_relaxed);
68+
return;
69+
}
70+
size_t new_cap = trace_buf_cap + TL_TRACE_REALLOC_CAP;
71+
72+
if (new_cap > SIZE_MAX / sizeof(struct mir_loc)) {
73+
// New buffer size would overflow.
74+
atomic_store_explicit(&tracing, false, memory_order_relaxed);
75+
return;
76+
}
77+
size_t new_size = new_cap * sizeof(struct mir_loc);
78+
79+
trace_buf = realloc(trace_buf, new_size);
80+
if (trace_buf == NULL) {
81+
atomic_store_explicit(&tracing, false, memory_order_relaxed);
82+
return;
83+
}
84+
85+
trace_buf_cap = new_cap;
86+
}
87+
88+
struct mir_loc loc = { crate_hash, def_idx, bb_idx };
89+
trace_buf[trace_buf_len] = loc;
90+
trace_buf_len ++;
91+
}
92+
93+
94+
// Stop tracing on the current thread.
95+
// On success the trace buffer is returned and the number of locations it
96+
// holds is written to `*ret_trace_len`. It is the responsibility of the caller
97+
// to free the returned trace buffer. A NULL pointer is returned on error.
98+
// Calling this function when tracing was not started with
99+
// `yk_swt_start_tracing_impl()` results in undefined behaviour.
100+
struct mir_loc *
101+
yk_swt_stop_tracing_impl(size_t *ret_trace_len) {
102+
if (!atomic_load_explicit(&tracing, memory_order_relaxed)) {
103+
free(trace_buf);
104+
trace_buf = NULL;
105+
trace_buf_len = 0;
106+
}
107+
108+
// We hand ownership of the trace to Rust now. Rust is responsible for
109+
// freeing the trace.
110+
struct mir_loc *ret_trace = trace_buf;
111+
*ret_trace_len = trace_buf_len;
112+
113+
// Now reset all off the recorder's state.
114+
// We reset `trace_invalid` when tracing is restarted, because signals
115+
// handlers which set this flag may arrive in the meantime.
116+
trace_buf = NULL;
117+
tracing = false;
118+
trace_buf_len = 0;
119+
trace_buf_cap = 0;
120+
121+
return ret_trace;
122+
}
123+
124+
// Call this to safely mark the trace invalid.
125+
void
126+
yk_swt_invalidate_trace_impl(void) {
127+
// We don't free the trace buffer here, as this may be called in a signal
128+
// handler and thus needs to be reentrant.
129+
atomic_store_explicit(&tracing, false, memory_order_relaxed);
130+
}

src/librustc/middle/lang_items.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,6 @@ language_item_table! {
377377
U128ShroFnLangItem, "u128_shro", u128_shro_fn, Target::Fn;
378378

379379
YkSwtRecLocLangItem, "yk_swt_rec_loc", yk_swt_rec_loc, Target::Fn;
380-
YkSwtRecLocWrapLangItem, "yk_swt_rec_loc_wrap",yk_swt_rec_loc_wrap, Target::Fn;
381380

382381
// Align offset for stride != 1, must not panic.
383382
AlignOffsetLangItem, "align_offset", align_offset_fn, Target::Fn;

src/librustc/middle/weak_lang_items.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,4 @@ weak_lang_items! {
168168
eh_personality, EhPersonalityLangItem, rust_eh_personality;
169169
eh_unwind_resume, EhUnwindResumeLangItem, rust_eh_unwind_resume;
170170
oom, OomLangItem, rust_oom;
171-
yk_swt_rec_loc, YkSwtRecLocLangItem, rust_yk_swt_rec_loc;
172171
}

src/librustc_mir/transform/add_yk_swt_calls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl MirPass for AddYkSWTCalls {
5151
return;
5252
}
5353

54-
let rec_fn_defid = tcx.get_lang_items(LOCAL_CRATE).yk_swt_rec_loc_wrap()
54+
let rec_fn_defid = tcx.get_lang_items(LOCAL_CRATE).yk_swt_rec_loc()
5555
.expect("couldn't find software trace recorder function");
5656

5757
let unit_ty = tcx.mk_unit();

src/libstd/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@
310310
#![feature(panic_info_message)]
311311
#![feature(non_exhaustive)]
312312
#![feature(alloc_layout_extra)]
313+
#![feature(yk_swt)]
313314

314315
#![default_lib_allocator]
315316

@@ -518,10 +519,6 @@ mod coresimd {
518519
#[cfg(all(not(stage0), not(test)))]
519520
pub use stdsimd::arch;
520521

521-
/// Yorick software tracing.
522-
#[unstable(feature = "yk_swt", issue = "0")]
523-
pub mod yk_swt;
524-
525522
// Include a number of private modules that exist solely to provide
526523
// the rustdoc documentation for primitive types. Using `include!`
527524
// because rustdoc only looks for these modules at the crate level.

0 commit comments

Comments
 (0)