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

Software Trace Function in C. #4

Merged
merged 2 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ dependencies = [
name = "core"
version = "0.0.0"
dependencies = [
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

Expand Down
3 changes: 3 additions & 0 deletions src/libcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ path = "../libcore/benches/lib.rs"

[dev-dependencies]
rand = "0.5"

[build-dependencies]
cc = "1.0"
22 changes: 22 additions & 0 deletions src/libcore/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2019 King's College London.
// Created by the Software Development Team <http://soft-dev.org/>.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate cc;

fn main() {
let mut c_build = cc::Build::new();

c_build.file("yk_swt_impl.c");
c_build.compile("yk_swt_impl");
c_build.flag("-std=c11");
c_build.warnings(true);
c_build.extra_warnings(true);

println!("cargo:rerun-if-changed=yk_swt_impl.c");
}
4 changes: 3 additions & 1 deletion src/libcore/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,9 @@ mod nonzero;
mod tuple;
mod unit;

mod yk_swt;
/// Yorick software tracing.
#[unstable(feature = "yk_swt", issue = "0")]
pub mod yk_swt;

// Pull in the `coresimd` crate directly into libcore. This is where all the
// architecture-specific (and vendor-specific) intrinsics are defined. AKA
Expand Down
93 changes: 82 additions & 11 deletions src/libcore/yk_swt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,91 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

/// The software trace recorder function.
/// This is a weak language item, it actually resides in libstd. It has to be weak to allow libcore
/// to call up to libstd (libstd is not a dependency of libcore).
extern "Rust" {
#[cfg_attr(not(stage0), lang="yk_swt_rec_loc")]
fn yk_swt_rec_loc(crate_hash: u64, def_idx: u32, bb: u32);
/// The result of `yk_swt_stop_tracing_impl()` is an array of this C struct.
#[repr(C)]
#[derive(Debug)]
pub struct MirLoc {
/// Unique identifier for the crate.
pub crate_hash: u64,
/// The definition index.
pub def_idx: u32,
/// The basic block index.
pub bb_idx: u32,
}

/// Wraps the raw C trace buffer and exposes a more "Rusty" interface to it.
#[derive(Debug)]
#[allow(dead_code)]
pub struct SWTrace {
/// A heap allocated array of `MirLoc` structs, which the consumer must free. Ideally we'd
/// have this struct implement `std::ops::Drop` or at least provide a method to do the freeing,
/// but we can do neither due to libcore restrictions.
buf: *mut MirLoc,
/// The number of items in the above array.
len: usize,
}

/// Wrapper lang item to call the above wrapper function.
/// This has to be a lang item too, as a MIR terminator cannot call a weak language item directly.
impl SWTrace {
/// Returns the number of MIR locations recorded in the trace.
pub fn len(&self) -> usize {
self.len
}

/// Return a pointer to the raw trace buffer. The consumer *must* free this pointer.
pub fn buf(self) -> *mut MirLoc {
self.buf
}

/// Returns the location at index `idx` or `None` if the index is out of bounds.
pub fn loc<'a>(&'a self, idx: usize) -> &'a MirLoc {
if idx >= self.len {
panic!("software trace index out of bounds: len={}, idx={}", self.len, idx);
} else {
if idx > isize::max_value() as usize {
panic!("index too large for ptr arithmetic");
}
unsafe { &*self.buf.offset(idx as isize) }
}
}
}

/// The software trace recorder function.
/// This is implemented in C so that: the `yk_swt_calls` MIR pass doesn't see inside.
#[allow(dead_code)] // Used only indirectly in a MIR pass.
#[cfg_attr(not(stage0), lang="yk_swt_rec_loc_wrap")]
#[cfg_attr(not(stage0), lang="yk_swt_rec_loc")]
#[cfg_attr(not(stage0), no_trace)]
#[cfg(not(test))]
fn yk_swt_rec_loc(crate_hash: u64, def_idx: u32, bb_idx: u32) {
extern "C" { fn yk_swt_rec_loc_impl(crate_hash: u64, def_idx: u32, bb_idx: u32); }
unsafe { yk_swt_rec_loc_impl(crate_hash, def_idx, bb_idx); }
}

/// Start software tracing on the current thread. The current thread must not already be tracing.
#[cfg_attr(not(stage0), no_trace)]
fn yk_swt_rec_loc_wrap(crate_hash: u64, def_idx: u32, bb: u32) {
unsafe { yk_swt_rec_loc(crate_hash, def_idx, bb) };
pub fn start_tracing() {
extern "C" { fn yk_swt_start_tracing_impl(); }
unsafe { yk_swt_start_tracing_impl(); }
}

/// Stop software tracing and return the trace, or `None` if the trace was invalidated.
/// The current thread must already be tracing.
#[cfg_attr(not(stage0), no_trace)]
pub fn stop_tracing() -> Option<SWTrace> {
let len: usize = 0;

extern "C" { fn yk_swt_stop_tracing_impl(ret_len: &usize) -> *mut MirLoc; }
let buf = unsafe { yk_swt_stop_tracing_impl(&len) };

if buf.is_null() {
None
} else {
Some(SWTrace { buf, len })
}
}

/// Invalidate the software trace, if one is being collected.
#[cfg_attr(not(stage0), no_trace)]
pub fn invalidate_trace() {
extern "C" { fn yk_swt_invalidate_trace_impl(); }
unsafe { yk_swt_invalidate_trace_impl(); }
}
130 changes: 130 additions & 0 deletions src/libcore/yk_swt_impl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2019 King's College London.
// Created by the Software Development Team <http://soft-dev.org/>.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#include <stdint.h>
#include <stdlib.h>
#include <err.h>
#include <stdbool.h>
#include <stdatomic.h>

struct mir_loc {
uint64_t crate_hash;
uint32_t def_idx;
uint32_t bb_idx;
};

#define TL_TRACE_INIT_CAP 1024
#define TL_TRACE_REALLOC_CAP 1024

void yk_swt_start_tracing_impl(void);
void yk_swt_rec_loc_impl(uint64_t crate_hash, uint32_t def_idx, uint32_t bb_idx);
struct mir_loc *yk_swt_stop_tracing_impl(size_t *ret_trace_len);
void yk_swt_invalidate_trace_impl(void);

// The trace buffer.
static __thread struct mir_loc *trace_buf = NULL;
// The number of elements in the trace buffer.
static __thread size_t trace_buf_len = 0;
// The allocation capacity of the trace buffer (in elements).
static __thread size_t trace_buf_cap = 0;
// Is the current thread tracing?
// true = we are tracing, false = we are not tracing or an error occurred.
static __thread volatile atomic_bool tracing = false;

// Start tracing on the current thread.
// A new trace buffer is allocated and MIR locations will be written into it on
// subsequent calls to `yk_swt_rec_loc_impl`. If the current thread is already
// tracing, calling this will lead to undefined behaviour.
void
yk_swt_start_tracing_impl(void) {
trace_buf = calloc(TL_TRACE_INIT_CAP, sizeof(struct mir_loc));
if (trace_buf == NULL) {
err(EXIT_FAILURE, "%s: calloc: ", __func__);
}

trace_buf_cap = TL_TRACE_INIT_CAP;
atomic_store_explicit(&tracing, true, memory_order_relaxed);
}

// Record a location into the trace buffer if tracing is enabled on the current thread.
void
yk_swt_rec_loc_impl(uint64_t crate_hash, uint32_t def_idx, uint32_t bb_idx)
{
if (!atomic_load_explicit(&tracing, memory_order_relaxed)) {
return;
}

// Check if we need more space and reallocate if necessary.
if (trace_buf_len == trace_buf_cap) {
if (trace_buf_cap >= SIZE_MAX - TL_TRACE_REALLOC_CAP) {
// Trace capacity would overflow.
atomic_store_explicit(&tracing, false, memory_order_relaxed);
return;
}
size_t new_cap = trace_buf_cap + TL_TRACE_REALLOC_CAP;

if (new_cap > SIZE_MAX / sizeof(struct mir_loc)) {
// New buffer size would overflow.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should/could we free the buffer at this point? Similarly for the realloc failure below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be freed when tracing stops. Until then the user is oblivious to the error anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but do we want to waste memory in this way? Especially if realloc fails -- the user is in trouble, and we might be able to help them out a little.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm mistaken, it's not safe (at least not without a mutex or similar). The stack overflow handler could cause us to re-enter here in the event that SIGSEGV and SIGBUS are delivered in quick succession.

freeing and NULLing would need to be performed atomically.

This is why I free and NULL when we stop the tracer. The stack overflow handler can't end up in there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to waste memory in this way

I don't understand why memory would be wasted though? The same memory is used whether we free here or later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm mistaken, it's not safe (at least not without a mutex or similar).

Good point.

atomic_store_explicit(&tracing, false, memory_order_relaxed);
return;
}
size_t new_size = new_cap * sizeof(struct mir_loc);

trace_buf = realloc(trace_buf, new_size);
if (trace_buf == NULL) {
atomic_store_explicit(&tracing, false, memory_order_relaxed);
return;
}

trace_buf_cap = new_cap;
}

struct mir_loc loc = { crate_hash, def_idx, bb_idx };
trace_buf[trace_buf_len] = loc;
trace_buf_len ++;
}


// Stop tracing on the current thread.
// On success the trace buffer is returned and the number of locations it
// holds is written to `*ret_trace_len`. It is the responsibility of the caller
// to free the returned trace buffer. A NULL pointer is returned on error.
// Calling this function when tracing was not started with
// `yk_swt_start_tracing_impl()` results in undefined behaviour.
struct mir_loc *
yk_swt_stop_tracing_impl(size_t *ret_trace_len) {
if (!atomic_load_explicit(&tracing, memory_order_relaxed)) {
free(trace_buf);
trace_buf = NULL;
trace_buf_len = 0;
}

// We hand ownership of the trace to Rust now. Rust is responsible for
// freeing the trace.
struct mir_loc *ret_trace = trace_buf;
*ret_trace_len = trace_buf_len;

// Now reset all off the recorder's state.
// We reset `trace_invalid` when tracing is restarted, because signals
// handlers which set this flag may arrive in the meantime.
trace_buf = NULL;
tracing = false;
trace_buf_len = 0;
trace_buf_cap = 0;

return ret_trace;
}

// Call this to safely mark the trace invalid.
void
yk_swt_invalidate_trace_impl(void) {
// We don't free the trace buffer here, as this may be called in a signal
// handler and thus needs to be reentrant.
atomic_store_explicit(&tracing, false, memory_order_relaxed);
}
1 change: 0 additions & 1 deletion src/librustc/middle/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,6 @@ language_item_table! {
U128ShroFnLangItem, "u128_shro", u128_shro_fn, Target::Fn;

YkSwtRecLocLangItem, "yk_swt_rec_loc", yk_swt_rec_loc, Target::Fn;
YkSwtRecLocWrapLangItem, "yk_swt_rec_loc_wrap",yk_swt_rec_loc_wrap, Target::Fn;

// Align offset for stride != 1, must not panic.
AlignOffsetLangItem, "align_offset", align_offset_fn, Target::Fn;
Expand Down
1 change: 0 additions & 1 deletion src/librustc/middle/weak_lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,4 @@ weak_lang_items! {
eh_personality, EhPersonalityLangItem, rust_eh_personality;
eh_unwind_resume, EhUnwindResumeLangItem, rust_eh_unwind_resume;
oom, OomLangItem, rust_oom;
yk_swt_rec_loc, YkSwtRecLocLangItem, rust_yk_swt_rec_loc;
}
2 changes: 1 addition & 1 deletion src/librustc_mir/transform/add_yk_swt_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl MirPass for AddYkSWTCalls {
return;
}

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

let unit_ty = tcx.mk_unit();
Expand Down
5 changes: 1 addition & 4 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@
#![feature(panic_info_message)]
#![feature(non_exhaustive)]
#![feature(alloc_layout_extra)]
#![feature(yk_swt)]

#![default_lib_allocator]

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

/// Yorick software tracing.
#[unstable(feature = "yk_swt", issue = "0")]
pub mod yk_swt;

// Include a number of private modules that exist solely to provide
// the rustdoc documentation for primitive types. Using `include!`
// because rustdoc only looks for these modules at the crate level.
Expand Down
4 changes: 4 additions & 0 deletions src/libstd/sys/unix/stack_overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ mod imp {
unsafe extern fn signal_handler(signum: libc::c_int,
info: *mut libc::siginfo_t,
_data: *mut libc::c_void) {
// A signal handler can corrupt a software trace.
use core::yk_swt;
yk_swt::invalidate_trace();

use sys_common::util::report_overflow;

let guard = thread_info::stack_guard().unwrap_or(0..0);
Expand Down
19 changes: 0 additions & 19 deletions src/libstd/yk_swt.rs

This file was deleted.

3 changes: 0 additions & 3 deletions src/test/compile-fail/two-panic-runtimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,3 @@ extern crate panic_runtime_unwind2;
extern crate panic_runtime_lang_items;

fn main() {}

#[lang = "yk_swt_rec_loc"]
fn yk_swt_rec_loc(_crate_hash: u64, _def_idx: u32, _bb: u32) {}
Loading