diff --git a/Cargo.lock b/Cargo.lock index 15d016a97cf8c..ce1a0b0ff4bbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3620,6 +3620,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "rustc_coverage" +version = "0.0.0" +dependencies = [ + "log", + "rustc_ast", + "rustc_errors", + "rustc_resolve", + "rustc_span", + "smallvec 1.0.0", +] + [[package]] name = "rustc_data_structures" version = "0.0.0" @@ -3815,6 +3827,7 @@ dependencies = [ "rustc_builtin_macros", "rustc_codegen_llvm", "rustc_codegen_ssa", + "rustc_coverage", "rustc_data_structures", "rustc_errors", "rustc_expand", diff --git a/src/doc/book b/src/doc/book index c8841f2841a2d..6fb3705e52303 160000 --- a/src/doc/book +++ b/src/doc/book @@ -1 +1 @@ -Subproject commit c8841f2841a2d26124319ddadd1b6a245f9a1856 +Subproject commit 6fb3705e5230311b096d47f7e2c91f9ce24393d0 diff --git a/src/doc/nomicon b/src/doc/nomicon index 411197b0e7759..9f797e65e6bcc 160000 --- a/src/doc/nomicon +++ b/src/doc/nomicon @@ -1 +1 @@ -Subproject commit 411197b0e77590c967e37e8f6ec681abd359afe8 +Subproject commit 9f797e65e6bcc79419975b17aff8e21c9adc039f diff --git a/src/doc/reference b/src/doc/reference index 89dd146154474..e2f11fe4d6a5e 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit 89dd146154474559536d5d4049a03831c501deea +Subproject commit e2f11fe4d6a5ecb471c70323197da43c70cb96b6 diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index edd2a7e687358..cb369ae95ca36 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit edd2a7e687358712608896730c083cb76c7b401a +Subproject commit cb369ae95ca36b841960182d26f6d5d9b2e3cc18 diff --git a/src/librustc_coverage/Cargo.toml b/src/librustc_coverage/Cargo.toml new file mode 100644 index 0000000000000..7f7db2314ef78 --- /dev/null +++ b/src/librustc_coverage/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["The Rust Project Developers"] +name = "rustc_coverage" +version = "0.0.0" +edition = "2018" + +[lib] +name = "rustc_coverage" +path = "lib.rs" + +[dependencies] +log = "0.4" +rustc_errors = { path = "../librustc_errors" } +rustc_resolve = { path = "../librustc_resolve" } +rustc_ast = { path = "../librustc_ast" } +rustc_span = { path = "../librustc_span" } +smallvec = { version = "1.0", features = ["union", "may_dangle"] } diff --git a/src/librustc_coverage/coverage.rs b/src/librustc_coverage/coverage.rs new file mode 100644 index 0000000000000..25af9e891313f --- /dev/null +++ b/src/librustc_coverage/coverage.rs @@ -0,0 +1,335 @@ +//! Injects code coverage instrumentation into the AST. + +use rustc_ast::ast::*; +use rustc_ast::mut_visit::{self, MutVisitor}; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_errors::ErrorReported; +use rustc_resolve::Resolver; +use rustc_span::symbol::sym; +use rustc_span::symbol::Symbol; +use rustc_span::Span; +use smallvec::SmallVec; + +use log::trace; +use std::ops::DerefMut; +use std::sync::Mutex; + +pub type Result = std::result::Result; + +struct CoverageRegion { + region: Span, + counter_hash: u128, +} + +static mut COVERAGE_REGIONS: Option>> = None; + +impl CoverageRegion { + /// Generates a unique coverage region identifier to associate with + /// a counter. The counter increment statement is injected into the + /// code at the start of a coverage region. + // FIXME(richkadel): This function will need additional arguments and/or context + // data from which to generate the hash values. + fn generate_hash(region: &Span) -> u128 { + // THIS IS NOT THREAD SAFE, BUT WILL BE REPLACED WITH HASH FUNCTION ANYWAY. + // Seems like lazy_static is not used in the compiler at all. + static mut NEXT_COUNTER_ID: Option> = None; + let counter_hash = { + let counter_id = unsafe { + &match NEXT_COUNTER_ID.as_ref() { + Some(counter_id) => counter_id, + None => { + NEXT_COUNTER_ID = Some(Mutex::new(0)); + NEXT_COUNTER_ID.as_ref().unwrap() + } + } + }; + let mut locked_counter_id = counter_id.lock().unwrap(); + *locked_counter_id += 1; + *locked_counter_id + }; + + let coverage_regions = unsafe { + &match COVERAGE_REGIONS.as_ref() { + Some(coverage_regions) => coverage_regions, + None => { + COVERAGE_REGIONS = Some(Mutex::new(vec![])); + COVERAGE_REGIONS.as_ref().unwrap() + } + } + }; + let mut locked_coverage_regions = coverage_regions.lock().unwrap(); + locked_coverage_regions.push(CoverageRegion { region: region.clone(), counter_hash }); + + // return the counter hash value + counter_hash + } + + pub fn write_coverage_regions(/* filename param? */) { + unsafe { + if let Some(coverage_regions) = COVERAGE_REGIONS.as_ref() { + let locked_coverage_regions = coverage_regions.lock().unwrap(); + for coverage in locked_coverage_regions.iter() { + println!("{}: {:?}", coverage.counter_hash, coverage.region); + } + } + } + } +} + +struct CoverageInjector<'res, 'internal> { + resolver: &'res mut Resolver<'internal>, + span: Span, +} + +impl CoverageInjector<'_, '_> { + fn at<'res, 'internal>( + resolver: &'res mut Resolver<'internal>, + span: Span, + ) -> CoverageInjector<'res, 'internal> { + CoverageInjector { resolver, span } + } + + fn next_ast_node_id(&mut self) -> NodeId { + self.resolver.next_node_id() + } + + fn expr(&mut self, kind: ExprKind, span: Span) -> P { + P(Expr { kind, span, attrs: AttrVec::new(), id: self.next_ast_node_id() }) + } + + fn path_segment(&mut self, string: &str) -> PathSegment { + PathSegment { + ident: Ident::from_str_and_span(string, self.span.shrink_to_lo()), + id: self.next_ast_node_id(), + args: None, + } + } + + fn coverage_count_fn_path(&mut self, print_coverage_report: bool) -> P { + let fn_name = if print_coverage_report { "count_and_report" } else { "count" }; + let path = Path { + span: self.span.shrink_to_lo(), + segments: vec![ + self.path_segment("std"), + self.path_segment("coverage"), + self.path_segment(fn_name), + ], + }; + self.expr(ExprKind::Path(None, path), self.span.shrink_to_lo()) + } + + fn coverage_counter_hash_lit(&mut self, counter_hash: u128) -> P { + let token = + token::Lit::new(token::Integer, sym::integer(counter_hash), /*suffix=*/ None); + let kind = LitKind::Int(counter_hash, LitIntType::Unsuffixed); + let lit = Lit { token, kind, span: self.span.shrink_to_lo() }; + self.expr(ExprKind::Lit(lit), self.span.shrink_to_lo()) + } + + fn call(&mut self, fn_path: P, args: Vec>) -> P { + self.expr(ExprKind::Call(fn_path, args), self.span.clone()) + } +} + +struct CoverageVisitor<'res, 'internal> { + resolver: &'res mut Resolver<'internal>, + function_stack: Vec, + main_block_id: Option, +} + +impl CoverageVisitor<'_, '_> { + fn new<'res, 'internal>( + resolver: &'res mut Resolver<'internal>, + ) -> CoverageVisitor<'res, 'internal> { + CoverageVisitor { resolver, function_stack: vec![], main_block_id: None } + } + + fn next_ast_node_id(&mut self) -> NodeId { + self.resolver.next_node_id() + } + + fn is_visiting_main(&self) -> bool { + // len == 1 ensures a function is being visited and the function is not a nested function + self.function_stack.len() == 1 && *self.function_stack.last().unwrap() == sym::main + } + + fn empty_tuple(&mut self, span: Span) -> P { + P(Expr { + kind: ExprKind::Tup(vec![]), + span, + attrs: AttrVec::new(), + id: self.next_ast_node_id(), + }) + } + + fn wrap_and_count_expr( + &mut self, + coverage_span: &Span, + wrapped_expr: P, + print_coverage_report: bool, + ) -> P { + let mut injector = CoverageInjector::at(&mut self.resolver, wrapped_expr.span.clone()); + let counter_hash = CoverageRegion::generate_hash(coverage_span); + let coverage_count_fn = injector.coverage_count_fn_path(print_coverage_report); + let args = vec![injector.coverage_counter_hash_lit(counter_hash), wrapped_expr]; + injector.call(coverage_count_fn, args) + } + + fn wrap_and_count_stmt( + &mut self, + coverage_span: &Span, + wrapped_expr: P, + print_coverage_report: bool, + ) -> Stmt { + Stmt { + id: self.next_ast_node_id(), + span: wrapped_expr.span.clone(), + kind: StmtKind::Semi(self.wrap_and_count_expr( + coverage_span, + wrapped_expr, + print_coverage_report, + )), + } + } + + fn count_stmt( + &mut self, + coverage_span: &Span, + inject_site: Span, + print_coverage_report: bool, + ) -> Stmt { + let empty_tuple = self.empty_tuple(inject_site); + self.wrap_and_count_stmt(coverage_span, empty_tuple, print_coverage_report) + } + + fn instrument_block(&mut self, block: &mut Block) { + trace!("instrument_block: {:?}", block); + if let Some(mut last) = block.stmts.pop() { + let mut report = false; + if let Some(main) = self.main_block_id { + report = block.id == main + } + + match &mut last.kind { + StmtKind::Expr(result_expr) => { + let wrapped_expr = result_expr.clone(); + *result_expr = self.wrap_and_count_expr(&block.span, wrapped_expr, report); + block.stmts.push(last); + } + StmtKind::Semi(expr) => { + if let ExprKind::Ret(..) = expr.kind { + report = self.is_visiting_main(); + } + + match &mut expr.deref_mut().kind { + ExprKind::Break(_, result_expr) + | ExprKind::Ret(result_expr) + | ExprKind::Yield(result_expr) => { + match result_expr.take() { + Some(wrapped_expr) => { + *result_expr = Some(self.wrap_and_count_expr( + &block.span, + wrapped_expr, + report, + )); + } + None => { + block.stmts.push(self.count_stmt( + &block.span, + last.span.shrink_to_lo(), + report, + )); + } + } + block.stmts.push(last); + } + ExprKind::Continue(..) => { + block.stmts.push(self.count_stmt( + &block.span, + last.span.shrink_to_lo(), + report, + )); + block.stmts.push(last); + } + _ => { + let insert_after_last = last.span.shrink_to_hi(); + block.stmts.push(last); + block.stmts.push(self.count_stmt( + &block.span, + insert_after_last, + report, + )); + } + } + } + _ => (), + } + } + } +} + +impl MutVisitor for CoverageVisitor<'_, '_> { + fn visit_block(&mut self, block: &mut P) { + self.instrument_block(block.deref_mut()); + mut_visit::noop_visit_block(block, self); + } + + fn flat_map_item(&mut self, item: P) -> SmallVec<[P; 1]> { + if let ItemKind::Fn(_defaultness, _signature, _generics, block) = &item.kind { + if item.ident.name == sym::main { + if let Some(block) = block { + self.main_block_id = Some(block.id); + } + } + self.function_stack.push(item.ident.name); + let result = mut_visit::noop_flat_map_item(item, self); + self.function_stack.pop(); + result + } else { + mut_visit::noop_flat_map_item(item, self) + } + } + + // FIXME(richkadel): + // add more visit_???() functions for language constructs that are branched statements without + // blocks, such as: + // visit match arm expr + // if not block, wrap expr in block and inject counter + // + // There are language constructs that have statement spans that don't require + // braces if only one statement, in which case, they PROBABLY don't hit "Block", and + // therefore, I need to insert the counters in other parts of the AST as well, while + // also virtually inserting the curly braces: + // * closures: |...| stmt -> |...| { coverage::counter(n); stmt } + // * match arms: match variant { pat => stmt, ...} -> match variant { pat => { coverage::counter(n); stmt } ... } + // Lazy boolean operators: logical expressions that may not be executed if prior expressions obviate the need + // "the right-hand operand is only evaluated when the left-hand operand does not already + // determine the result of the expression. That is, || only evaluates its right-hand + // operand when the left-hand operand evaluates to false, and && only when it evaluates to + // true." + // * if true || stmt -> if true || coverage::wrap_and_count(n, { stmt } ) + // make sure operator precedence is handled with boolean expressions + // (?? IS THAT if false && coverage::count(condition2 && coverage::count(condition3))) + // I THINK it doesn't matter if the operator is && or || + // Unless there are parentheses, each operator requires a coverage wrap_and_count around + // ALL remaining && or || conditions on the right side, and then recursively nested. + // Rust calls parentheticals "Grouped expressions" + // `?` operator which can invoke the equivalent of an early return (of `Err(...)`). + // * {expr_possibly_in_block}? -> coverage::counter(n, {expr_possibly_in_block})? + // * Expression initializers for const/static values (where legal to invoke a function?) or + // assign a coverage region to the entire file, and increment a counter if/when it's known + // that the consts and statics were initialized for the file/mod. + // Any others? + // * NOT let var: type = expr (since there's no branching to worry about) + // * NOT for or while loop expressions because they aren't optionally executed + + // FIXME(richkadel): if multi-threaded, are we assured that exiting main means all threads have completed? +} + +pub fn instrument<'res>(mut krate: Crate, resolver: &'res mut Resolver<'_>) -> Result { + trace!("Calling coverage::instrument() for {:?}", &krate); + mut_visit::noop_visit_crate(&mut krate, &mut CoverageVisitor::new(resolver)); + CoverageRegion::write_coverage_regions(); + Ok(krate) +} diff --git a/src/librustc_coverage/lib.rs b/src/librustc_coverage/lib.rs new file mode 100644 index 0000000000000..0474207a238fa --- /dev/null +++ b/src/librustc_coverage/lib.rs @@ -0,0 +1,5 @@ +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")] +#![feature(nll)] +#![recursion_limit = "256"] + +pub mod coverage; diff --git a/src/librustc_interface/Cargo.toml b/src/librustc_interface/Cargo.toml index c9d81e51641a8..e0bb6b92294a9 100644 --- a/src/librustc_interface/Cargo.toml +++ b/src/librustc_interface/Cargo.toml @@ -38,6 +38,7 @@ rustc_mir_build = { path = "../librustc_mir_build" } rustc_passes = { path = "../librustc_passes" } rustc_typeck = { path = "../librustc_typeck" } rustc_lint = { path = "../librustc_lint" } +rustc_coverage = { path = "../librustc_coverage" } rustc_errors = { path = "../librustc_errors" } rustc_plugin_impl = { path = "../librustc_plugin_impl" } rustc_privacy = { path = "../librustc_privacy" } diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs index e3fc4fa52fb61..11f926821459c 100644 --- a/src/librustc_interface/passes.rs +++ b/src/librustc_interface/passes.rs @@ -7,6 +7,7 @@ use rustc_ast::mut_visit::MutVisitor; use rustc_ast::{self, ast, visit}; use rustc_codegen_ssa::back::link::emit_metadata; use rustc_codegen_ssa::traits::CodegenBackend; +use rustc_coverage::coverage; use rustc_data_structures::sync::{par_iter, Lrc, Once, ParallelIterator, WorkerLocal}; use rustc_data_structures::{box_region_allow_access, declare_box_region_type, parallel}; use rustc_errors::PResult; @@ -408,6 +409,15 @@ fn configure_and_expand_inner<'a>( println!("{}", json::as_json(&krate)); } + let instrument_coverage = match sess.opts.debugging_opts.instrument_coverage { + Some(opt) => opt, + None => false, + }; + + if instrument_coverage { + krate = coverage::instrument(krate, &mut resolver)?; + } + resolver.resolve_crate(&krate); // Needs to go *after* expansion to be able to check the results of macro expansion. diff --git a/src/librustc_session/options.rs b/src/librustc_session/options.rs index 432f1e17ab312..fb2fcac1b2279 100644 --- a/src/librustc_session/options.rs +++ b/src/librustc_session/options.rs @@ -959,6 +959,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "link the `.rlink` file generated by `-Z no-link`"), new_llvm_pass_manager: Option = (None, parse_opt_bool, [TRACKED], "use new LLVM pass manager"), + instrument_coverage: Option = (None, parse_opt_bool, [TRACKED], + "inject code coverage counters and export a counter-to-coverage-span map"), link_native_libraries: Option = (None, parse_opt_bool, [UNTRACKED], "Link native libraries in the linker invocation."), } diff --git a/src/libstd/coverage.rs b/src/libstd/coverage.rs new file mode 100644 index 0000000000000..77aded607436c --- /dev/null +++ b/src/libstd/coverage.rs @@ -0,0 +1,111 @@ +//! Code coverage counters and report +//! +//! The code coverage library is typically not included in Rust source files. +//! +//! Instead, the `rustc` compiler optionally injects coverage calls into the internal representation +//! of the source if requested by command line option to the compiler. The end result is an +//! executable that includes the coverage counters, and writes a coverage report upon exit. +//! +//! The injected calls behave as if code like the following examples was actually part of the +//! original source. +//! +//! Example: +//! +//! ``` +//! fn main() { +//! let value = if true { +//! std::coverage::count(1, { +//! // any expression +//! 1000 +//! }) +//! } else { +//! std::coverage::count(2, 500) +//! }; +//! std::coverage::count_and_report(3, ()) +//! } +//! ``` + +#![stable(feature = "coverage", since = "1.44.0")] + +use crate::collections::HashMap; +use crate::sync::LockResult; +use crate::sync::Mutex; +use crate::sync::MutexGuard; +use crate::sync::Once; + +static mut COUNTERS: Option>> = None; + +static INIT: Once = Once::new(); + +// FIXME(richkadel): Thinking about ways of optimizing executing coverage-instrumented code, I may +// be able to replace the hashmap lookup with a simple vector index, for most invocations, by +// assigning an index to each hash, at execution time, and storing that index in an injected static +// at the site of each counter. If the static is initialized to an unused index (say, u64::MAX), I +// can pass it into the std::coverage::count() function as mutable, and update it to the next index +// available. The vector would contain the hashed region IDs and corresponding counts. +// I just need to be sure the change can be done atomically, and if the same counter is called from +// multiple threads before the index is assigned, that the behavior is reasonable. (I suppose it +// *could* even tolerate the possibility that two indexes were assigned. Printing the report could +// sum the counts for any identical coverage region IDs, if we need to allow for that.) +#[stable(feature = "coverage", since = "1.44.0")] +#[inline(always)] +fn increment_counter(counter: u128) -> LockResult>> { + let mut lock = unsafe { + INIT.call_once(|| { + COUNTERS = Some(Mutex::new(HashMap::with_capacity(1024))); + }); + COUNTERS.as_mut().unwrap().lock() + }; + let counters = lock.as_mut().unwrap(); + match counters.get_mut(&counter) { + Some(count) => *count += 1, + None => { + counters.insert(counter, 1); + } + } + lock +} + +/// The statement may be the last statement of a block, the value of a `return` or `break`, +/// a match pattern arm statement that might not be in a block (but if it is, don't wrap both the +/// block and the last statement of the block), or a closure statement without braces. +/// +/// ```no_run +/// # fn some_statement_with_or_without_semicolon() {} +/// std::coverage::count(234234, {some_statement_with_or_without_semicolon()}) +/// ``` +// Adding inline("always") for now, so I don't forget that some llvm experts thought +// it might not work to inject the llvm intrinsic inside this function if it is not +// inlined. Roughly speaking, LLVM lowering may change how the intrinsic and +// neighboring code is translated, in a way that behaves counterintuitively. +// (Note that "always" is still only a "recommendation", so this still might not +// address that problem.) Further investigation is needed, and an alternative +// solution would be to just expand code into the projected inline version at +// code injection time, rather than call this function at all. +#[stable(feature = "coverage", since = "1.44.0")] +#[inline(always)] +pub fn count(counter: u128, result: T) -> T { + // FIXME(richkadel): replace increment_counter with a call to the LLVM intrinsic: + // ``` + // declare void @llvm.instrprof.increment(i8* , i64 , + // i32 , i32 ) + // ``` + // See: http://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic + let _ = increment_counter(counter); + result +} + +/// Increment the specified counter and then write the coverage report. This function normally wraps +/// the final expression in a `main()` function. There can be more than one statement, for example +/// if the `main()` has one or more `return` statements. In this case, all returns and the last +/// statement of `main()` (unless not reachable) should use this function. +#[stable(feature = "coverage", since = "1.44.0")] +#[inline(always)] +pub fn count_and_report(counter: u128, result: T) -> T { + println!("Print the coverage counters:"); + let mut counters = increment_counter(counter).unwrap(); + for (counter, count) in counters.drain() { + println!("Counter '{}' has count: {}", counter, count); + } + result +} diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index ff8f6731724d0..b0f9ea5dfcfdc 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -451,6 +451,7 @@ pub mod thread; pub mod ascii; pub mod backtrace; pub mod collections; +pub mod coverage; pub mod env; pub mod error; pub mod ffi; diff --git a/src/llvm-project b/src/llvm-project index 992e608cfc5d1..9f65ad057357b 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit 992e608cfc5d1c126a23c640222fd396a3bdeb9f +Subproject commit 9f65ad057357b307180955831968f79e74090a90 diff --git a/src/tools/cargo b/src/tools/cargo index 8a0d4d9c9abc7..7019b3ed3d539 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit 8a0d4d9c9abc74fd670353094387d62028b40ae9 +Subproject commit 7019b3ed3d539db7429d10a343b69be8c426b576 diff --git a/src/tools/clippy b/src/tools/clippy index e170c849420b9..34763a5f3632b 160000 --- a/src/tools/clippy +++ b/src/tools/clippy @@ -1 +1 @@ -Subproject commit e170c849420b9da1799b447828c6e6484f16696c +Subproject commit 34763a5f3632b1d9081ba0245a729174996f0b38 diff --git a/src/tools/miri b/src/tools/miri index d1e06b4298129..aaa16a5f4b8ca 160000 --- a/src/tools/miri +++ b/src/tools/miri @@ -1 +1 @@ -Subproject commit d1e06b429812916e5fc3129bff992d37639d9da4 +Subproject commit aaa16a5f4b8caabf8e044e6dd1c48330dfb7900d