From ff697c613c2fa52819a045fb82b7cb3daa45c1f6 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 18 Aug 2021 05:11:26 -0700 Subject: [PATCH 01/15] On macOS, make strip="symbols" not pass any options to strip This makes the output with `strip="symbols"` match the result of just calling `strip` on the output binary, minimizing the size of the binary. --- compiler/rustc_codegen_ssa/src/back/link.rs | 22 ++++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index f3eb1e04d07dc..aed057ffcf69a 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -977,14 +977,20 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>( } if sess.target.is_like_osx { - if let Some(option) = osx_strip_opt(sess.opts.debugging_opts.strip) { - strip_symbols_in_osx(sess, &out_filename, option); + match sess.opts.debugging_opts.strip { + Strip::Debuginfo => strip_symbols_in_osx(sess, &out_filename, Some("-S")), + Strip::Symbols => strip_symbols_in_osx(sess, &out_filename, None), + Strip::None => {} } } } -fn strip_symbols_in_osx<'a>(sess: &'a Session, out_filename: &Path, option: &str) { - let prog = Command::new("strip").arg(option).arg(out_filename).output(); +fn strip_symbols_in_osx<'a>(sess: &'a Session, out_filename: &Path, option: Option<&str>) { + let mut cmd = Command::new("strip"); + if let Some(option) = option { + cmd.arg(option); + } + let prog = cmd.arg(out_filename).output(); match prog { Ok(prog) => { if !prog.status.success() { @@ -1002,14 +1008,6 @@ fn strip_symbols_in_osx<'a>(sess: &'a Session, out_filename: &Path, option: &str } } -fn osx_strip_opt<'a>(strip: Strip) -> Option<&'a str> { - match strip { - Strip::Debuginfo => Some("-S"), - Strip::Symbols => Some("-x"), - Strip::None => None, - } -} - fn escape_string(s: &[u8]) -> String { str::from_utf8(s).map(|s| s.to_owned()).unwrap_or_else(|_| { let mut x = "Non-UTF-8 output: ".to_string(); From 142f6c0b078ceef1dc817c418f628d350551f6e4 Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Fri, 10 Sep 2021 17:34:09 -0700 Subject: [PATCH 02/15] Implement #[link_ordinal] attribute in the context of #[link(kind = "raw-dylib")]. --- .../rustc_codegen_llvm/src/back/archive.rs | 10 +++--- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 11 ++++-- .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 11 +++--- compiler/rustc_metadata/src/native_libs.rs | 8 ++++- .../src/middle/codegen_fn_attrs.rs | 2 +- compiler/rustc_typeck/src/collect.rs | 35 ++++++++++++++++--- .../run-make/raw-dylib-link-ordinal/Makefile | 18 ++++++++++ .../run-make/raw-dylib-link-ordinal/driver.rs | 5 +++ .../raw-dylib-link-ordinal/exporter.c | 5 +++ .../raw-dylib-link-ordinal/exporter.def | 3 ++ .../run-make/raw-dylib-link-ordinal/lib.rs | 13 +++++++ .../raw-dylib-link-ordinal/output.txt | 1 + .../link-ordinal-missing-argument.rs | 11 ++++++ .../link-ordinal-missing-argument.stderr | 19 ++++++++++ .../link-ordinal-multiple.rs | 13 +++++++ .../link-ordinal-multiple.stderr | 17 +++++++++ .../link-ordinal-too-large.rs | 4 +-- .../link-ordinal-too-large.stderr | 8 ++--- .../link-ordinal-too-many-arguments.rs | 11 ++++++ .../link-ordinal-too-many-arguments.stderr | 19 ++++++++++ 20 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 src/test/run-make/raw-dylib-link-ordinal/Makefile create mode 100644 src/test/run-make/raw-dylib-link-ordinal/driver.rs create mode 100644 src/test/run-make/raw-dylib-link-ordinal/exporter.c create mode 100644 src/test/run-make/raw-dylib-link-ordinal/exporter.def create mode 100644 src/test/run-make/raw-dylib-link-ordinal/lib.rs create mode 100644 src/test/run-make/raw-dylib-link-ordinal/output.txt create mode 100644 src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.rs create mode 100644 src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.stderr create mode 100644 src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.rs create mode 100644 src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.stderr create mode 100644 src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.rs create mode 100644 src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.stderr diff --git a/compiler/rustc_codegen_llvm/src/back/archive.rs b/compiler/rustc_codegen_llvm/src/back/archive.rs index 4e86946219fb1..58a76b30578d2 100644 --- a/compiler/rustc_codegen_llvm/src/back/archive.rs +++ b/compiler/rustc_codegen_llvm/src/back/archive.rs @@ -163,13 +163,13 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> { // All import names are Rust identifiers and therefore cannot contain \0 characters. // FIXME: when support for #[link_name] implemented, ensure that import.name values don't // have any \0 characters - let import_name_vector: Vec = dll_imports + let import_name_and_ordinal_vector: Vec<(CString, Option)> = dll_imports .iter() .map(|import: &DllImport| { if self.config.sess.target.arch == "x86" { - LlvmArchiveBuilder::i686_decorated_name(import) + (LlvmArchiveBuilder::i686_decorated_name(import), import.ordinal) } else { - CString::new(import.name.to_string()).unwrap() + (CString::new(import.name.to_string()).unwrap(), import.ordinal) } }) .collect(); @@ -184,9 +184,9 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> { dll_imports.iter().map(|import| import.name.to_string()).collect::>().join(", "), ); - let ffi_exports: Vec = import_name_vector + let ffi_exports: Vec = import_name_and_ordinal_vector .iter() - .map(|name_z| LLVMRustCOFFShortExport::from_name(name_z.as_ptr())) + .map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal)) .collect(); let result = unsafe { crate::llvm::LLVMRustWriteImportLibrary( diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 3f2ed02d90df3..f69716e4e1e9b 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -34,11 +34,18 @@ pub enum LLVMRustResult { #[repr(C)] pub struct LLVMRustCOFFShortExport { pub name: *const c_char, + pub ordinal_present: bool, + // value of `ordinal` only important when `ordinal_present` is true + pub ordinal: u16, } impl LLVMRustCOFFShortExport { - pub fn from_name(name: *const c_char) -> LLVMRustCOFFShortExport { - LLVMRustCOFFShortExport { name } + pub fn new(name: *const c_char, ordinal: Option) -> LLVMRustCOFFShortExport { + LLVMRustCOFFShortExport { + name, + ordinal_present: ordinal.is_some(), + ordinal: ordinal.unwrap_or(0), + } } } diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 9850f395a0f65..b0ebae5214af3 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1749,10 +1749,11 @@ LLVMRustBuildMaxNum(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS) { } // This struct contains all necessary info about a symbol exported from a DLL. -// At the moment, it's just the symbol's name, but we use a separate struct to -// make it easier to add other information like ordinal later. struct LLVMRustCOFFShortExport { const char* name; + bool ordinal_present; + // The value of `ordinal` is only meaningful if `ordinal_present` is true. + uint16_t ordinal; }; // Machine must be a COFF machine type, as defined in PE specs. @@ -1768,13 +1769,15 @@ extern "C" LLVMRustResult LLVMRustWriteImportLibrary( ConvertedExports.reserve(NumExports); for (size_t i = 0; i < NumExports; ++i) { + bool ordinal_present = Exports[i].ordinal_present; + uint16_t ordinal = ordinal_present ? Exports[i].ordinal : 0; ConvertedExports.push_back(llvm::object::COFFShortExport{ Exports[i].name, // Name std::string{}, // ExtName std::string{}, // SymbolName std::string{}, // AliasTarget - 0, // Ordinal - false, // Noname + ordinal, // Ordinal + ordinal_present, // Noname false, // Data false, // Private false // Constant diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs index 5f0d8c46f20dc..45050d0618a59 100644 --- a/compiler/rustc_metadata/src/native_libs.rs +++ b/compiler/rustc_metadata/src/native_libs.rs @@ -433,6 +433,12 @@ impl Collector<'tcx> { } } }; - DllImport { name: item.ident.name, ordinal: None, calling_convention, span: item.span } + + DllImport { + name: item.ident.name, + ordinal: self.tcx.codegen_fn_attrs(item.id.def_id).link_ordinal, + calling_convention, + span: item.span, + } } } diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index b2705c7693914..b054d21adaa13 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -22,7 +22,7 @@ pub struct CodegenFnAttrs { /// imported function has in the dynamic library. Note that this must not /// be set when `link_name` is set. This is for foreign items with the /// "raw-dylib" kind. - pub link_ordinal: Option, + pub link_ordinal: Option, /// The `#[target_feature(enable = "...")]` attribute and the enabled /// features (only enabled features are supported right now). pub target_features: Vec, diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index 1bc7bc3e063d4..02857c7886fd0 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -2858,6 +2858,14 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { } else if attr.has_name(sym::link_name) { codegen_fn_attrs.link_name = attr.value_str(); } else if attr.has_name(sym::link_ordinal) { + if link_ordinal_span.is_some() { + tcx.sess + .struct_span_err( + attr.span, + "multiple `link_ordinal` attributes on a single definition", + ) + .emit(); + } link_ordinal_span = Some(attr.span); if let ordinal @ Some(_) = check_link_ordinal(tcx, attr) { codegen_fn_attrs.link_ordinal = ordinal; @@ -3153,22 +3161,41 @@ fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool { false } -fn check_link_ordinal(tcx: TyCtxt<'_>, attr: &ast::Attribute) -> Option { +fn check_link_ordinal(tcx: TyCtxt<'_>, attr: &ast::Attribute) -> Option { use rustc_ast::{Lit, LitIntType, LitKind}; let meta_item_list = attr.meta_item_list(); let meta_item_list: Option<&[ast::NestedMetaItem]> = meta_item_list.as_ref().map(Vec::as_ref); let sole_meta_list = match meta_item_list { Some([item]) => item.literal(), + Some(_) => { + tcx.sess + .struct_span_err(attr.span, "incorrect number of arguments to `#[link_ordinal]`") + .note("the attribute requires exactly one argument") + .emit(); + return None; + } _ => None, }; if let Some(Lit { kind: LitKind::Int(ordinal, LitIntType::Unsuffixed), .. }) = sole_meta_list { - if *ordinal <= usize::MAX as u128 { - Some(*ordinal as usize) + // According to the table at https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-header, + // the ordinal must fit into 16 bits. Similarly, the Ordinal field in COFFShortExport (defined + // in llvm/include/llvm/Object/COFFImportFile.h), which we use to communicate import information + // to LLVM for `#[link(kind = "raw-dylib"_])`, is also defined to be uint16_t. + // + // FIXME: should we allow an ordinal of 0? The MSVC toolchain has inconsistent support for this: + // both LINK.EXE and LIB.EXE signal errors and abort when given a .DEF file that specifies + // a zero ordinal. However, llvm-dlltool is perfectly happy to generate an import library + // for such a .DEF file, and MSVC's LINK.EXE is also perfectly happy to consume an import + // library produced by LLVM with an ordinal of 0, and it generates an .EXE. (I don't know yet + // if the resulting EXE runs, as I haven't yet built the necessary DLL -- see earlier comment + // about LINK.EXE failing.) + if *ordinal <= u16::MAX as u128 { + Some(*ordinal as u16) } else { let msg = format!("ordinal value in `link_ordinal` is too large: `{}`", &ordinal); tcx.sess .struct_span_err(attr.span, &msg) - .note("the value may not exceed `usize::MAX`") + .note("the value may not exceed `u16::MAX`") .emit(); None } diff --git a/src/test/run-make/raw-dylib-link-ordinal/Makefile b/src/test/run-make/raw-dylib-link-ordinal/Makefile new file mode 100644 index 0000000000000..04b257d063204 --- /dev/null +++ b/src/test/run-make/raw-dylib-link-ordinal/Makefile @@ -0,0 +1,18 @@ +# Test the behavior of #[link(.., kind = "raw-dylib")] and #[link_ordinal] on windows-msvc + +# only-windows-msvc + +-include ../../run-make-fulldeps/tools.mk + +all: + $(call COMPILE_OBJ,"$(TMPDIR)"/exporter.obj,exporter.c) + $(CC) "$(TMPDIR)"/exporter.obj exporter.def -link -dll -out:"$(TMPDIR)"/exporter.dll + $(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs + $(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)" + "$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt + +ifdef RUSTC_BLESS_TEST + cp "$(TMPDIR)"/output.txt output.txt +else + $(DIFF) output.txt "$(TMPDIR)"/output.txt +endif diff --git a/src/test/run-make/raw-dylib-link-ordinal/driver.rs b/src/test/run-make/raw-dylib-link-ordinal/driver.rs new file mode 100644 index 0000000000000..4059ede11fc96 --- /dev/null +++ b/src/test/run-make/raw-dylib-link-ordinal/driver.rs @@ -0,0 +1,5 @@ +extern crate raw_dylib_test; + +fn main() { + raw_dylib_test::library_function(); +} diff --git a/src/test/run-make/raw-dylib-link-ordinal/exporter.c b/src/test/run-make/raw-dylib-link-ordinal/exporter.c new file mode 100644 index 0000000000000..a9dd6da6616f9 --- /dev/null +++ b/src/test/run-make/raw-dylib-link-ordinal/exporter.c @@ -0,0 +1,5 @@ +#include + +void exported_function() { + printf("exported_function\n"); +} diff --git a/src/test/run-make/raw-dylib-link-ordinal/exporter.def b/src/test/run-make/raw-dylib-link-ordinal/exporter.def new file mode 100644 index 0000000000000..1a4b4c941b65d --- /dev/null +++ b/src/test/run-make/raw-dylib-link-ordinal/exporter.def @@ -0,0 +1,3 @@ +LIBRARY exporter +EXPORTS + exported_function @13 NONAME diff --git a/src/test/run-make/raw-dylib-link-ordinal/lib.rs b/src/test/run-make/raw-dylib-link-ordinal/lib.rs new file mode 100644 index 0000000000000..20609caa5be21 --- /dev/null +++ b/src/test/run-make/raw-dylib-link-ordinal/lib.rs @@ -0,0 +1,13 @@ +#![feature(raw_dylib)] + +#[link(name = "exporter", kind = "raw-dylib")] +extern { + #[link_ordinal(13)] + fn imported_function(); +} + +pub fn library_function() { + unsafe { + imported_function(); + } +} diff --git a/src/test/run-make/raw-dylib-link-ordinal/output.txt b/src/test/run-make/raw-dylib-link-ordinal/output.txt new file mode 100644 index 0000000000000..2d0ed60f21667 --- /dev/null +++ b/src/test/run-make/raw-dylib-link-ordinal/output.txt @@ -0,0 +1 @@ +exported_function diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.rs b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.rs new file mode 100644 index 0000000000000..c391ccd1c8227 --- /dev/null +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.rs @@ -0,0 +1,11 @@ +#![feature(raw_dylib)] +//~^ WARN the feature `raw_dylib` is incomplete + +#[link(name = "foo")] +extern "C" { + #[link_ordinal()] + //~^ ERROR incorrect number of arguments to `#[link_ordinal]` + fn foo(); +} + +fn main() {} diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.stderr b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.stderr new file mode 100644 index 0000000000000..8e9edfb9d20ac --- /dev/null +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-missing-argument.stderr @@ -0,0 +1,19 @@ +warning: the feature `raw_dylib` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/link-ordinal-missing-argument.rs:1:12 + | +LL | #![feature(raw_dylib)] + | ^^^^^^^^^ + | + = note: `#[warn(incomplete_features)]` on by default + = note: see issue #58713 for more information + +error: incorrect number of arguments to `#[link_ordinal]` + --> $DIR/link-ordinal-missing-argument.rs:6:5 + | +LL | #[link_ordinal()] + | ^^^^^^^^^^^^^^^^^ + | + = note: the attribute requires exactly one argument + +error: aborting due to previous error; 1 warning emitted + diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.rs b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.rs new file mode 100644 index 0000000000000..9874121267717 --- /dev/null +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.rs @@ -0,0 +1,13 @@ +// only-windows-msvc +#![feature(raw_dylib)] +//~^ WARN the feature `raw_dylib` is incomplete + +#[link(name = "foo", kind = "raw-dylib")] +extern "C" { + #[link_ordinal(1)] + #[link_ordinal(2)] + //~^ ERROR multiple `link_ordinal` attributes on a single definition + fn foo(); +} + +fn main() {} diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.stderr b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.stderr new file mode 100644 index 0000000000000..a79fb2de94402 --- /dev/null +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-multiple.stderr @@ -0,0 +1,17 @@ +warning: the feature `raw_dylib` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/link-ordinal-multiple.rs:2:12 + | +LL | #![feature(raw_dylib)] + | ^^^^^^^^^ + | + = note: `#[warn(incomplete_features)]` on by default + = note: see issue #58713 for more information + +error: multiple `link_ordinal` attributes on a single definition + --> $DIR/link-ordinal-multiple.rs:8:5 + | +LL | #[link_ordinal(2)] + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error; 1 warning emitted + diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.rs b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.rs index 10db497297027..b6089d27e7ab7 100644 --- a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.rs +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.rs @@ -3,8 +3,8 @@ #[link(name = "foo")] extern "C" { - #[link_ordinal(18446744073709551616)] - //~^ ERROR ordinal value in `link_ordinal` is too large: `18446744073709551616` + #[link_ordinal(72436)] + //~^ ERROR ordinal value in `link_ordinal` is too large: `72436` fn foo(); } diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.stderr b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.stderr index 35f9b53fdf720..bbe985fa10ada 100644 --- a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.stderr +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-large.stderr @@ -7,13 +7,13 @@ LL | #![feature(raw_dylib)] = note: `#[warn(incomplete_features)]` on by default = note: see issue #58713 for more information -error: ordinal value in `link_ordinal` is too large: `18446744073709551616` +error: ordinal value in `link_ordinal` is too large: `72436` --> $DIR/link-ordinal-too-large.rs:6:5 | -LL | #[link_ordinal(18446744073709551616)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[link_ordinal(72436)] + | ^^^^^^^^^^^^^^^^^^^^^^ | - = note: the value may not exceed `usize::MAX` + = note: the value may not exceed `u16::MAX` error: aborting due to previous error; 1 warning emitted diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.rs b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.rs new file mode 100644 index 0000000000000..93286c616c5ac --- /dev/null +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.rs @@ -0,0 +1,11 @@ +#![feature(raw_dylib)] +//~^ WARN the feature `raw_dylib` is incomplete + +#[link(name = "foo")] +extern "C" { + #[link_ordinal(3, 4)] + //~^ ERROR incorrect number of arguments to `#[link_ordinal]` + fn foo(); +} + +fn main() {} diff --git a/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.stderr b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.stderr new file mode 100644 index 0000000000000..484c85a0f422a --- /dev/null +++ b/src/test/ui/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.stderr @@ -0,0 +1,19 @@ +warning: the feature `raw_dylib` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/link-ordinal-too-many-arguments.rs:1:12 + | +LL | #![feature(raw_dylib)] + | ^^^^^^^^^ + | + = note: `#[warn(incomplete_features)]` on by default + = note: see issue #58713 for more information + +error: incorrect number of arguments to `#[link_ordinal]` + --> $DIR/link-ordinal-too-many-arguments.rs:6:5 + | +LL | #[link_ordinal(3, 4)] + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the attribute requires exactly one argument + +error: aborting due to previous error; 1 warning emitted + From fa23d4fe93020bfde8f45c7cad077117d999c3ed Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Sat, 18 Sep 2021 18:10:01 -0400 Subject: [PATCH 03/15] Implement #85440 --- library/test/src/cli.rs | 74 +++++++++++++++++++++++++++ library/test/src/console.rs | 4 +- library/test/src/event.rs | 2 +- library/test/src/formatters/json.rs | 11 ++-- library/test/src/formatters/junit.rs | 6 ++- library/test/src/formatters/mod.rs | 2 +- library/test/src/formatters/pretty.rs | 9 +++- library/test/src/formatters/terse.rs | 9 +++- library/test/src/helpers/mod.rs | 1 + library/test/src/helpers/shuffle.rs | 67 ++++++++++++++++++++++++ library/test/src/lib.rs | 11 +++- library/test/src/tests.rs | 2 + src/tools/compiletest/src/main.rs | 2 + 13 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 library/test/src/helpers/shuffle.rs diff --git a/library/test/src/cli.rs b/library/test/src/cli.rs index 84874a2d2254a..e26b910101101 100644 --- a/library/test/src/cli.rs +++ b/library/test/src/cli.rs @@ -21,6 +21,8 @@ pub struct TestOpts { pub nocapture: bool, pub color: ColorConfig, pub format: OutputFormat, + pub shuffle: bool, + pub shuffle_seed: Option, pub test_threads: Option, pub skip: Vec, pub time_options: Option, @@ -138,6 +140,13 @@ fn optgroups() -> getopts::Options { `CRITICAL_TIME` here means the limit that should not be exceeded by test. ", + ) + .optflag("", "shuffle", "Run tests in random order") + .optopt( + "", + "shuffle-seed", + "Run tests in random order; seed the random number generator with SEED", + "SEED", ); opts } @@ -155,6 +164,12 @@ By default, all tests are run in parallel. This can be altered with the --test-threads flag or the RUST_TEST_THREADS environment variable when running tests (set it to 1). +By default, the tests are run in alphabetical order. Use --shuffle or set +RUST_TEST_SHUFFLE to run the tests in random order. Pass the generated +"shuffle seed" to --shuffle-seed (or set RUST_TEST_SHUFFLE_SEED) to run the +tests in the same order again. Note that --shuffle and --shuffle-seed do not +affect whether the tests are run in parallel. + All tests have their standard output and standard error captured by default. This can be overridden with the --nocapture flag or setting RUST_TEST_NOCAPTURE environment variable to a value other than "0". Logging is not captured by default. @@ -218,6 +233,21 @@ macro_rules! unstable_optflag { }}; } +// Gets the option value and checks if unstable features are enabled. +macro_rules! unstable_optopt { + ($matches:ident, $allow_unstable:ident, $option_name:literal) => {{ + let opt = $matches.opt_str($option_name); + if !$allow_unstable && opt.is_some() { + return Err(format!( + "The \"{}\" option is only accepted on the nightly compiler with -Z unstable-options", + $option_name + )); + } + + opt + }}; +} + // Implementation of `parse_opts` that doesn't care about help message // and returns a `Result`. fn parse_opts_impl(matches: getopts::Matches) -> OptRes { @@ -227,6 +257,8 @@ fn parse_opts_impl(matches: getopts::Matches) -> OptRes { let force_run_in_process = unstable_optflag!(matches, allow_unstable, "force-run-in-process"); let exclude_should_panic = unstable_optflag!(matches, allow_unstable, "exclude-should-panic"); let time_options = get_time_options(&matches, allow_unstable)?; + let shuffle = get_shuffle(&matches, allow_unstable)?; + let shuffle_seed = get_shuffle_seed(&matches, allow_unstable)?; let include_ignored = matches.opt_present("include-ignored"); let quiet = matches.opt_present("quiet"); @@ -260,6 +292,8 @@ fn parse_opts_impl(matches: getopts::Matches) -> OptRes { nocapture, color, format, + shuffle, + shuffle_seed, test_threads, skip, time_options, @@ -303,6 +337,46 @@ fn get_time_options( Ok(options) } +fn get_shuffle(matches: &getopts::Matches, allow_unstable: bool) -> OptPartRes { + let mut shuffle = unstable_optflag!(matches, allow_unstable, "shuffle"); + if !shuffle { + shuffle = match env::var("RUST_TEST_SHUFFLE") { + Ok(val) => &val != "0", + Err(_) => false, + }; + } + + Ok(shuffle) +} + +fn get_shuffle_seed(matches: &getopts::Matches, allow_unstable: bool) -> OptPartRes> { + let mut shuffle_seed = match unstable_optopt!(matches, allow_unstable, "shuffle-seed") { + Some(n_str) => match n_str.parse::() { + Ok(n) => Some(n), + Err(e) => { + return Err(format!( + "argument for --shuffle-seed must be a number \ + (error: {})", + e + )); + } + }, + None => None, + }; + + if shuffle_seed.is_none() { + shuffle_seed = match env::var("RUST_TEST_SHUFFLE_SEED") { + Ok(val) => match val.parse::() { + Ok(n) => Some(n), + Err(_) => panic!("RUST_TEST_SHUFFLE_SEED is `{}`, should be a number.", val), + }, + Err(_) => None, + }; + } + + Ok(shuffle_seed) +} + fn get_test_threads(matches: &getopts::Matches) -> OptPartRes> { let test_threads = match matches.opt_str("test-threads") { Some(n_str) => match n_str.parse::() { diff --git a/library/test/src/console.rs b/library/test/src/console.rs index 54e30a1fcd070..11c5ab48ed3e8 100644 --- a/library/test/src/console.rs +++ b/library/test/src/console.rs @@ -225,9 +225,9 @@ fn on_test_event( out: &mut dyn OutputFormatter, ) -> io::Result<()> { match (*event).clone() { - TestEvent::TeFiltered(ref filtered_tests) => { + TestEvent::TeFiltered(ref filtered_tests, shuffle_seed) => { st.total = filtered_tests.len(); - out.write_run_start(filtered_tests.len())?; + out.write_run_start(filtered_tests.len(), shuffle_seed)?; } TestEvent::TeFilteredOut(filtered_out) => { st.filtered_out = filtered_out; diff --git a/library/test/src/event.rs b/library/test/src/event.rs index 206f3e10e847d..6ff1a615eb4d0 100644 --- a/library/test/src/event.rs +++ b/library/test/src/event.rs @@ -28,7 +28,7 @@ impl CompletedTest { #[derive(Debug, Clone)] pub enum TestEvent { - TeFiltered(Vec), + TeFiltered(Vec, Option), TeWait(TestDesc), TeResult(CompletedTest), TeTimeout(TestDesc), diff --git a/library/test/src/formatters/json.rs b/library/test/src/formatters/json.rs index 57b6d1a02021f..424d3ef7b4106 100644 --- a/library/test/src/formatters/json.rs +++ b/library/test/src/formatters/json.rs @@ -60,10 +60,15 @@ impl JsonFormatter { } impl OutputFormatter for JsonFormatter { - fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { + fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option) -> io::Result<()> { + let shuffle_seed_json = if let Some(shuffle_seed) = shuffle_seed { + format!(r#", "shuffle_seed": {}"#, shuffle_seed) + } else { + String::new() + }; self.writeln_message(&*format!( - r#"{{ "type": "suite", "event": "started", "test_count": {} }}"#, - test_count + r#"{{ "type": "suite", "event": "started", "test_count": {}{} }}"#, + test_count, shuffle_seed_json )) } diff --git a/library/test/src/formatters/junit.rs b/library/test/src/formatters/junit.rs index aa24480751419..f3ae5494906cc 100644 --- a/library/test/src/formatters/junit.rs +++ b/library/test/src/formatters/junit.rs @@ -27,7 +27,11 @@ impl JunitFormatter { } impl OutputFormatter for JunitFormatter { - fn write_run_start(&mut self, _test_count: usize) -> io::Result<()> { + fn write_run_start( + &mut self, + _test_count: usize, + _shuffle_seed: Option, + ) -> io::Result<()> { // We write xml header on run start self.write_message(&"") } diff --git a/library/test/src/formatters/mod.rs b/library/test/src/formatters/mod.rs index 2e03581b3af3a..cb80859759fad 100644 --- a/library/test/src/formatters/mod.rs +++ b/library/test/src/formatters/mod.rs @@ -18,7 +18,7 @@ pub(crate) use self::pretty::PrettyFormatter; pub(crate) use self::terse::TerseFormatter; pub(crate) trait OutputFormatter { - fn write_run_start(&mut self, test_count: usize) -> io::Result<()>; + fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option) -> io::Result<()>; fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()>; fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()>; fn write_result( diff --git a/library/test/src/formatters/pretty.rs b/library/test/src/formatters/pretty.rs index 9cad71e30bddb..4a03b4b914760 100644 --- a/library/test/src/formatters/pretty.rs +++ b/library/test/src/formatters/pretty.rs @@ -181,9 +181,14 @@ impl PrettyFormatter { } impl OutputFormatter for PrettyFormatter { - fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { + fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option) -> io::Result<()> { let noun = if test_count != 1 { "tests" } else { "test" }; - self.write_plain(&format!("\nrunning {} {}\n", test_count, noun)) + let shuffle_seed_msg = if let Some(shuffle_seed) = shuffle_seed { + format!(" (shuffle seed: {})", shuffle_seed) + } else { + String::new() + }; + self.write_plain(&format!("\nrunning {} {}{}\n", test_count, noun, shuffle_seed_msg)) } fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { diff --git a/library/test/src/formatters/terse.rs b/library/test/src/formatters/terse.rs index 0c8215c5daca1..1f2c410cd96f3 100644 --- a/library/test/src/formatters/terse.rs +++ b/library/test/src/formatters/terse.rs @@ -170,10 +170,15 @@ impl TerseFormatter { } impl OutputFormatter for TerseFormatter { - fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { + fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option) -> io::Result<()> { self.total_test_count = test_count; let noun = if test_count != 1 { "tests" } else { "test" }; - self.write_plain(&format!("\nrunning {} {}\n", test_count, noun)) + let shuffle_seed_msg = if let Some(shuffle_seed) = shuffle_seed { + format!(" (shuffle seed: {})", shuffle_seed) + } else { + String::new() + }; + self.write_plain(&format!("\nrunning {} {}{}\n", test_count, noun, shuffle_seed_msg)) } fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { diff --git a/library/test/src/helpers/mod.rs b/library/test/src/helpers/mod.rs index b7f00c4c86cdf..049cadf86a6d0 100644 --- a/library/test/src/helpers/mod.rs +++ b/library/test/src/helpers/mod.rs @@ -5,3 +5,4 @@ pub mod concurrency; pub mod exit_code; pub mod isatty; pub mod metrics; +pub mod shuffle; diff --git a/library/test/src/helpers/shuffle.rs b/library/test/src/helpers/shuffle.rs new file mode 100644 index 0000000000000..ca503106c556c --- /dev/null +++ b/library/test/src/helpers/shuffle.rs @@ -0,0 +1,67 @@ +use crate::cli::TestOpts; +use crate::types::{TestDescAndFn, TestId, TestName}; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub fn get_shuffle_seed(opts: &TestOpts) -> Option { + opts.shuffle_seed.or_else(|| { + if opts.shuffle { + Some( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Failed to get system time") + .as_nanos() as u64, + ) + } else { + None + } + }) +} + +pub fn shuffle_tests(shuffle_seed: u64, tests: &mut [(TestId, TestDescAndFn)]) { + let test_names: Vec<&TestName> = tests.iter().map(|test| &test.1.desc.name).collect(); + let test_names_hash = calculate_hash(&test_names); + let mut rng = Rng::new(shuffle_seed, test_names_hash); + shuffle(&mut rng, tests); +} + +// `shuffle` is from `rust-analyzer/src/cli/analysis_stats.rs`. +fn shuffle(rng: &mut Rng, slice: &mut [T]) { + for i in 0..slice.len() { + randomize_first(rng, &mut slice[i..]); + } + + fn randomize_first(rng: &mut Rng, slice: &mut [T]) { + assert!(!slice.is_empty()); + let idx = rng.rand_range(0..slice.len() as u64) as usize; + slice.swap(0, idx); + } +} + +struct Rng { + state: u64, + extra: u64, +} + +impl Rng { + fn new(seed: u64, extra: u64) -> Self { + Self { state: seed, extra } + } + + fn rand_range(&mut self, range: core::ops::Range) -> u64 { + self.rand_u64() % (range.end - range.start) + range.start + } + + fn rand_u64(&mut self) -> u64 { + self.state = calculate_hash(&(self.state, self.extra)); + self.state + } +} + +// `calculate_hash` is from `core/src/hash/mod.rs`. +fn calculate_hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() +} diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index 251f099f28af4..67548819f6543 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -91,6 +91,7 @@ mod tests; use event::{CompletedTest, TestEvent}; use helpers::concurrency::get_concurrency; use helpers::exit_code::get_exit_code; +use helpers::shuffle::{get_shuffle_seed, shuffle_tests}; use options::{Concurrent, RunStrategy}; use test_result::*; use time::TestExecTime; @@ -247,7 +248,9 @@ where let filtered_descs = filtered_tests.iter().map(|t| t.desc.clone()).collect(); - let event = TestEvent::TeFiltered(filtered_descs); + let shuffle_seed = get_shuffle_seed(opts); + + let event = TestEvent::TeFiltered(filtered_descs, shuffle_seed); notify_about_test_event(event)?; let (filtered_tests, filtered_benchs): (Vec<_>, _) = filtered_tests @@ -259,7 +262,11 @@ where let concurrency = opts.test_threads.unwrap_or_else(get_concurrency); let mut remaining = filtered_tests; - remaining.reverse(); + if let Some(shuffle_seed) = shuffle_seed { + shuffle_tests(shuffle_seed, &mut remaining); + } else { + remaining.reverse(); + } let mut pending = 0; let (tx, rx) = channel::(); diff --git a/library/test/src/tests.rs b/library/test/src/tests.rs index 794f727700476..7b68b8fbaf82f 100644 --- a/library/test/src/tests.rs +++ b/library/test/src/tests.rs @@ -45,6 +45,8 @@ impl TestOpts { nocapture: false, color: AutoColor, format: OutputFormat::Pretty, + shuffle: false, + shuffle_seed: None, test_threads: None, skip: vec![], time_options: None, diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 9e655557fd271..d72ed61d13401 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -505,6 +505,8 @@ pub fn test_opts(config: &Config) -> test::TestOpts { Err(_) => false, }, color: config.color, + shuffle: false, + shuffle_seed: None, test_threads: None, skip: vec![], list: false, From a6738c7231dadb0a84d338ae6f41cc34651518eb Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Sun, 19 Sep 2021 19:04:07 -0400 Subject: [PATCH 04/15] Add tests --- library/test/src/tests.rs | 117 ++++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 25 deletions(-) diff --git a/library/test/src/tests.rs b/library/test/src/tests.rs index 7b68b8fbaf82f..718613895dee4 100644 --- a/library/test/src/tests.rs +++ b/library/test/src/tests.rs @@ -567,11 +567,7 @@ pub fn exact_filter_match() { assert_eq!(exact.len(), 2); } -#[test] -pub fn sort_tests() { - let mut opts = TestOpts::new(); - opts.run_tests = true; - +fn sample_tests() -> Vec { let names = vec![ "sha1::test".to_string(), "isize::test_to_str".to_string(), @@ -585,26 +581,32 @@ pub fn sort_tests() { "test::run_include_ignored_option".to_string(), "test::sort_tests".to_string(), ]; - let tests = { - fn testfn() {} - let mut tests = Vec::new(); - for name in &names { - let test = TestDescAndFn { - desc: TestDesc { - name: DynTestName((*name).clone()), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - compile_fail: false, - no_run: false, - test_type: TestType::Unknown, - }, - testfn: DynTestFn(Box::new(testfn)), - }; - tests.push(test); - } - tests - }; + fn testfn() {} + let mut tests = Vec::new(); + for name in &names { + let test = TestDescAndFn { + desc: TestDesc { + name: DynTestName((*name).clone()), + ignore: false, + should_panic: ShouldPanic::No, + allow_fail: false, + compile_fail: false, + no_run: false, + test_type: TestType::Unknown, + }, + testfn: DynTestFn(Box::new(testfn)), + }; + tests.push(test); + } + tests +} + +#[test] +pub fn sort_tests() { + let mut opts = TestOpts::new(); + opts.run_tests = true; + + let tests = sample_tests(); let filtered = filter_tests(&opts, tests); let expected = vec![ @@ -626,6 +628,71 @@ pub fn sort_tests() { } } +#[test] +pub fn shuffle_tests() { + let mut opts = TestOpts::new(); + opts.shuffle = true; + + let shuffle_seed = get_shuffle_seed(&opts).unwrap(); + + let left = + sample_tests().into_iter().enumerate().map(|(i, e)| (TestId(i), e)).collect::>(); + let mut right = + sample_tests().into_iter().enumerate().map(|(i, e)| (TestId(i), e)).collect::>(); + + assert!(left.iter().zip(&right).all(|(a, b)| a.1.desc.name == b.1.desc.name)); + + helpers::shuffle::shuffle_tests(shuffle_seed, right.as_mut_slice()); + + assert!(left.iter().zip(right).any(|(a, b)| a.1.desc.name != b.1.desc.name)); +} + +#[test] +pub fn shuffle_tests_with_seed() { + let mut opts = TestOpts::new(); + opts.shuffle = true; + + let shuffle_seed = get_shuffle_seed(&opts).unwrap(); + + let mut left = + sample_tests().into_iter().enumerate().map(|(i, e)| (TestId(i), e)).collect::>(); + let mut right = + sample_tests().into_iter().enumerate().map(|(i, e)| (TestId(i), e)).collect::>(); + + helpers::shuffle::shuffle_tests(shuffle_seed, left.as_mut_slice()); + helpers::shuffle::shuffle_tests(shuffle_seed, right.as_mut_slice()); + + assert!(left.iter().zip(right).all(|(a, b)| a.1.desc.name == b.1.desc.name)); +} + +#[test] +pub fn order_depends_on_more_than_seed() { + let mut opts = TestOpts::new(); + opts.shuffle = true; + + let shuffle_seed = get_shuffle_seed(&opts).unwrap(); + + let mut left_tests = sample_tests(); + let mut right_tests = sample_tests(); + + left_tests.pop(); + right_tests.remove(0); + + let mut left = + left_tests.into_iter().enumerate().map(|(i, e)| (TestId(i), e)).collect::>(); + let mut right = + right_tests.into_iter().enumerate().map(|(i, e)| (TestId(i), e)).collect::>(); + + assert_eq!(left.len(), right.len()); + + assert!(left.iter().zip(&right).all(|(a, b)| a.0 == b.0)); + + helpers::shuffle::shuffle_tests(shuffle_seed, left.as_mut_slice()); + helpers::shuffle::shuffle_tests(shuffle_seed, right.as_mut_slice()); + + assert!(left.iter().zip(right).any(|(a, b)| a.0 != b.0)); +} + #[test] pub fn test_metricmap_compare() { let mut m1 = MetricMap::new(); From 32b6ac5b44e4f0ffa5e56b9c81407633dc4edb64 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Thu, 30 Sep 2021 12:57:34 -0400 Subject: [PATCH 05/15] Check `allow_unstable` before checking environment variables --- library/test/src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/test/src/cli.rs b/library/test/src/cli.rs index e26b910101101..cb40b4e965b2a 100644 --- a/library/test/src/cli.rs +++ b/library/test/src/cli.rs @@ -339,7 +339,7 @@ fn get_time_options( fn get_shuffle(matches: &getopts::Matches, allow_unstable: bool) -> OptPartRes { let mut shuffle = unstable_optflag!(matches, allow_unstable, "shuffle"); - if !shuffle { + if !shuffle && allow_unstable { shuffle = match env::var("RUST_TEST_SHUFFLE") { Ok(val) => &val != "0", Err(_) => false, @@ -364,7 +364,7 @@ fn get_shuffle_seed(matches: &getopts::Matches, allow_unstable: bool) -> OptPart None => None, }; - if shuffle_seed.is_none() { + if shuffle_seed.is_none() && allow_unstable { shuffle_seed = match env::var("RUST_TEST_SHUFFLE_SEED") { Ok(val) => match val.parse::() { Ok(n) => Some(n), From e16e15f3aec7437181080b8a5025b42a26436565 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Mon, 4 Oct 2021 18:53:22 -0400 Subject: [PATCH 06/15] Add documentation --- src/doc/rustc/src/tests/index.md | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc/src/tests/index.md b/src/doc/rustc/src/tests/index.md index ec23d4fe0dbd3..2681105b468d5 100644 --- a/src/doc/rustc/src/tests/index.md +++ b/src/doc/rustc/src/tests/index.md @@ -181,6 +181,38 @@ unstable-options` flag. See [tracking issue #64888](https://github.com/rust-lang/rust/issues/64888) and the [unstable docs](../../unstable-book/compiler-flags/report-time.html) for more information. +#### `--shuffle` + +Runs the tests in random order, as opposed to the default alphabetical order. + +This may also be specified by setting the `RUST_TEST_SHUFFLE` environment +variable to anything but `0`. + +The random number generator seed that is output can be passed to +[`--shuffle-seed`](#--shuffle-seed-seed) to run the tests in the same order +again. + +Note that `--shuffle` does not affect whether the tests are run in parallel. To +run the tests in random order sequentially, use `--shuffle --test-threads 1`. + +⚠️ 🚧 This option is [unstable](#unstable-options), and requires the `-Z +unstable-options` flag. + +#### `--shuffle-seed` _SEED_ + +Like [`--shuffle`](#--shuffle), but seeds the random number generator with +_SEED_. Thus, calling the test harness with `--shuffle-seed` _SEED_ twice runs +the tests in the same order both times. + +_SEED_ is any 64-bit unsigned integer, for example, one produced by +[`--shuffle`](#--shuffle). + +This can also be specified with the `RUST_TEST_SHUFFLE_SEED` environment +variable. + +⚠️ 🚧 This option is [unstable](#unstable-options), and requires the `-Z +unstable-options` flag. + ### Output options The following options affect the output behavior. @@ -197,7 +229,7 @@ to the console. Usually the output is captured, and only displayed if the test fails. This may also be specified by setting the `RUST_TEST_NOCAPTURE` environment -variable set to anything but `0`. +variable to anything but `0`. #### `--show-output` From ecf474152350664f1227421eeb278b2e8185cd07 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Tue, 5 Oct 2021 20:46:28 -0400 Subject: [PATCH 07/15] Add tracking issue --- src/doc/rustc/src/tests/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc/src/tests/index.md b/src/doc/rustc/src/tests/index.md index 2681105b468d5..66e3a5261e4bd 100644 --- a/src/doc/rustc/src/tests/index.md +++ b/src/doc/rustc/src/tests/index.md @@ -196,7 +196,8 @@ Note that `--shuffle` does not affect whether the tests are run in parallel. To run the tests in random order sequentially, use `--shuffle --test-threads 1`. ⚠️ 🚧 This option is [unstable](#unstable-options), and requires the `-Z -unstable-options` flag. +unstable-options` flag. See [tracking issue +#89583](https://github.com/rust-lang/rust/issues/89583) for more information. #### `--shuffle-seed` _SEED_ @@ -211,7 +212,8 @@ This can also be specified with the `RUST_TEST_SHUFFLE_SEED` environment variable. ⚠️ 🚧 This option is [unstable](#unstable-options), and requires the `-Z -unstable-options` flag. +unstable-options` flag. See [tracking issue +#89583](https://github.com/rust-lang/rust/issues/89583) for more information. ### Output options From c3dfda0e3d9f006b9f279d263b88db9a13861037 Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Thu, 9 Sep 2021 11:46:22 +0200 Subject: [PATCH 08/15] Rebase Result::map_or_else doc wording on top of #89400. --- library/core/src/result.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/core/src/result.rs b/library/core/src/result.rs index c9c2d2e645ace..dda827900d959 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -795,9 +795,8 @@ impl Result { } } - /// Maps a `Result` to `U` by applying a provided default fallback - /// function to a contained [`Err`] value, or a provided function to a - /// contained [`Ok`] value. + /// Maps a `Result` to `U` by applying fallback function `default` to + /// a contained [`Err`] value, or function `f` to a contained [`Ok`] value. /// /// This function can be used to unpack a successful result /// while handling an error. From ce21756ed3acdd6bb4c222725214c24e1bc70915 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sat, 2 Oct 2021 17:35:27 +0200 Subject: [PATCH 09/15] Access Session while decoding expn_id. --- compiler/rustc_metadata/src/rmeta/decoder.rs | 4 ++-- .../rustc_metadata/src/rmeta/decoder/cstore_impl.rs | 10 ++++++++-- compiler/rustc_query_impl/src/on_disk_cache.rs | 7 ++++++- compiler/rustc_session/src/cstore.rs | 9 ++++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 08fc11f21d94e..ffb7fdacd2275 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1624,7 +1624,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { self.def_path_hash_map.def_path_hash_to_def_index(&hash) } - fn expn_hash_to_expn_id(&self, index_guess: u32, hash: ExpnHash) -> ExpnId { + fn expn_hash_to_expn_id(&self, sess: &Session, index_guess: u32, hash: ExpnHash) -> ExpnId { debug_assert_eq!(ExpnId::from_hash(hash), None); let index_guess = ExpnIndex::from_u32(index_guess); let old_hash = self.root.expn_hashes.get(self, index_guess).map(|lazy| lazy.decode(self)); @@ -1655,7 +1655,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { map[&hash] }; - let data = self.root.expn_data.get(self, index).unwrap().decode(self); + let data = self.root.expn_data.get(self, index).unwrap().decode((self, sess)); rustc_span::hygiene::register_expn_id(self.cnum, index, data, hash) } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 292ef03d856d9..4e7f85d2c3727 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -506,7 +506,13 @@ impl CrateStore for CStore { DefId { krate: cnum, index: def_index } } - fn expn_hash_to_expn_id(&self, cnum: CrateNum, index_guess: u32, hash: ExpnHash) -> ExpnId { - self.get_crate_data(cnum).expn_hash_to_expn_id(index_guess, hash) + fn expn_hash_to_expn_id( + &self, + sess: &Session, + cnum: CrateNum, + index_guess: u32, + hash: ExpnHash, + ) -> ExpnId { + self.get_crate_data(cnum).expn_hash_to_expn_id(sess, index_guess, hash) } } diff --git a/compiler/rustc_query_impl/src/on_disk_cache.rs b/compiler/rustc_query_impl/src/on_disk_cache.rs index d8cff0bd1880f..f93623e445c22 100644 --- a/compiler/rustc_query_impl/src/on_disk_cache.rs +++ b/compiler/rustc_query_impl/src/on_disk_cache.rs @@ -667,7 +667,12 @@ impl<'a, 'tcx> Decodable> for ExpnId { rustc_span::hygiene::register_local_expn_id(data, hash) } else { let index_guess = decoder.foreign_expn_data[&hash]; - decoder.tcx.cstore_untracked().expn_hash_to_expn_id(krate, index_guess, hash) + decoder.tcx.cstore_untracked().expn_hash_to_expn_id( + decoder.tcx.sess, + krate, + index_guess, + hash, + ) }; #[cfg(debug_assertions)] diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs index 9d6bd20103989..59e7abc2ea3bd 100644 --- a/compiler/rustc_session/src/cstore.rs +++ b/compiler/rustc_session/src/cstore.rs @@ -4,6 +4,7 @@ use crate::search_paths::PathKind; use crate::utils::NativeLibKind; +use crate::Session; use rustc_ast as ast; use rustc_data_structures::sync::{self, MetadataRef}; use rustc_hir::def_id::{CrateNum, DefId, StableCrateId, LOCAL_CRATE}; @@ -193,7 +194,13 @@ pub trait CrateStore: std::fmt::Debug { /// Fetch a DefId from a DefPathHash for a foreign crate. fn def_path_hash_to_def_id(&self, cnum: CrateNum, hash: DefPathHash) -> DefId; - fn expn_hash_to_expn_id(&self, cnum: CrateNum, index_guess: u32, hash: ExpnHash) -> ExpnId; + fn expn_hash_to_expn_id( + &self, + sess: &Session, + cnum: CrateNum, + index_guess: u32, + hash: ExpnHash, + ) -> ExpnId; } pub type CrateStoreDyn = dyn CrateStore + sync::Sync; From daf8903e8ed1f4704077e02479f841b84ebc82d3 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sat, 2 Oct 2021 21:14:52 +0200 Subject: [PATCH 10/15] Do not re-hash foreign spans. --- .../rustc_query_impl/src/on_disk_cache.rs | 27 +++++++++++-------- src/test/incremental/mir-opt.rs | 11 ++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 src/test/incremental/mir-opt.rs diff --git a/compiler/rustc_query_impl/src/on_disk_cache.rs b/compiler/rustc_query_impl/src/on_disk_cache.rs index f93623e445c22..48eb488792d89 100644 --- a/compiler/rustc_query_impl/src/on_disk_cache.rs +++ b/compiler/rustc_query_impl/src/on_disk_cache.rs @@ -664,7 +664,21 @@ impl<'a, 'tcx> Decodable> for ExpnId { let data: ExpnData = decoder .with_position(pos.to_usize(), |decoder| decode_tagged(decoder, TAG_EXPN_DATA))?; - rustc_span::hygiene::register_local_expn_id(data, hash) + let expn_id = rustc_span::hygiene::register_local_expn_id(data, hash); + + #[cfg(debug_assertions)] + { + use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; + let mut hcx = decoder.tcx.create_stable_hashing_context(); + let mut hasher = StableHasher::new(); + hcx.while_hashing_spans(true, |hcx| { + expn_id.expn_data().hash_stable(hcx, &mut hasher) + }); + let local_hash: u64 = hasher.finish(); + debug_assert_eq!(hash.local_hash(), local_hash); + } + + expn_id } else { let index_guess = decoder.foreign_expn_data[&hash]; decoder.tcx.cstore_untracked().expn_hash_to_expn_id( @@ -675,16 +689,7 @@ impl<'a, 'tcx> Decodable> for ExpnId { ) }; - #[cfg(debug_assertions)] - { - use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; - let mut hcx = decoder.tcx.create_stable_hashing_context(); - let mut hasher = StableHasher::new(); - hcx.while_hashing_spans(true, |hcx| expn_id.expn_data().hash_stable(hcx, &mut hasher)); - let local_hash: u64 = hasher.finish(); - debug_assert_eq!(hash.local_hash(), local_hash); - } - + debug_assert_eq!(expn_id.krate, krate); Ok(expn_id) } } diff --git a/src/test/incremental/mir-opt.rs b/src/test/incremental/mir-opt.rs new file mode 100644 index 0000000000000..5bd863439df5d --- /dev/null +++ b/src/test/incremental/mir-opt.rs @@ -0,0 +1,11 @@ +// MIR optimizations can create expansions after the TyCtxt has been created. +// This test verifies that those expansions can be decoded correctly. + +// revisions:rpass1 rpass2 +// compile-flags: -Z query-dep-graph -Z mir-opt-level=3 + +fn main() { + if std::env::var("a").is_ok() { + println!("b"); + } +} From 4028b093e468afad4896a1658924e3e3f3b8f57c Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sat, 2 Oct 2021 22:55:04 +0200 Subject: [PATCH 11/15] Do not ICE if some foreign expansions were not encoded. The metadata encoder does not necessarily encode all expansions, only those which are referenced in other metadata fields. --- compiler/rustc_metadata/src/rmeta/decoder.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index ffb7fdacd2275..9b97d1a476210 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1646,8 +1646,6 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { let i = ExpnIndex::from_u32(i); if let Some(hash) = self.root.expn_hashes.get(self, i) { map.insert(hash.decode(self), i); - } else { - panic!("Missing expn_hash entry for {:?}", i); } } map From a17193dbb931ea0c8b66d82f640385bce8b4929a Mon Sep 17 00:00:00 2001 From: Michael Benfield Date: Fri, 7 May 2021 07:41:37 +0000 Subject: [PATCH 12/15] Enable AutoFDO. This largely involves implementing the options debug-info-for-profiling and profile-sample-use and forwarding them on to LLVM. AutoFDO can be used on x86-64 Linux like this: rustc -O -Cdebug-info-for-profiling main.rs -o main perf record -b ./main create_llvm_prof --binary=main --out=code.prof rustc -O -Cprofile-sample-use=code.prof main.rs -o main2 Now `main2` will have feedback directed optimization applied to it. The create_llvm_prof tool can be obtained from this github repository: https://github.com/google/autofdo Fixes #64892. --- compiler/rustc_codegen_llvm/src/attributes.rs | 4 +++ compiler/rustc_codegen_llvm/src/back/write.rs | 15 ++++++++ compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 3 ++ compiler/rustc_codegen_ssa/src/back/linker.rs | 3 ++ compiler/rustc_codegen_ssa/src/back/write.rs | 4 +++ compiler/rustc_interface/src/tests.rs | 2 ++ .../rustc_llvm/llvm-wrapper/PassWrapper.cpp | 30 +++++++++++----- compiler/rustc_session/src/config.rs | 9 +++++ compiler/rustc_session/src/options.rs | 4 +++ compiler/rustc_session/src/session.rs | 10 ++++++ .../debug_info_for_profiling.md | 35 +++++++++++++++++++ .../src/compiler-flags/profile_sample_use.md | 10 ++++++ 12 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 src/doc/unstable-book/src/compiler-flags/debug_info_for_profiling.md create mode 100644 src/doc/unstable-book/src/compiler-flags/profile_sample_use.md diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 51c70f0868f26..659cf9ea070b1 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -263,6 +263,10 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty:: attributes::emit_uwtable(llfn, true); } + if cx.sess().opts.debugging_opts.profile_sample_use.is_some() { + llvm::AddFunctionAttrString(llfn, Function, cstr!("use-sample-profile")); + } + // FIXME: none of these three functions interact with source level attributes. set_frame_pointer_type(cx, llfn); set_instrument_function(cx, llfn); diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index ab48c56a62663..ca78254f0c84a 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -370,6 +370,13 @@ fn get_pgo_use_path(config: &ModuleConfig) -> Option { .map(|path_buf| CString::new(path_buf.to_string_lossy().as_bytes()).unwrap()) } +fn get_pgo_sample_use_path(config: &ModuleConfig) -> Option { + config + .pgo_sample_use + .as_ref() + .map(|path_buf| CString::new(path_buf.to_string_lossy().as_bytes()).unwrap()) +} + pub(crate) fn should_use_new_llvm_pass_manager(config: &ModuleConfig) -> bool { // The new pass manager is enabled by default for LLVM >= 13. // This matches Clang, which also enables it since Clang 13. @@ -389,6 +396,7 @@ pub(crate) unsafe fn optimize_with_new_llvm_pass_manager( let using_thin_buffers = opt_stage == llvm::OptStage::PreLinkThinLTO || config.bitcode_needed(); let pgo_gen_path = get_pgo_gen_path(config); let pgo_use_path = get_pgo_use_path(config); + let pgo_sample_use_path = get_pgo_sample_use_path(config); let is_lto = opt_stage == llvm::OptStage::ThinLTO || opt_stage == llvm::OptStage::FatLTO; // Sanitizer instrumentation is only inserted during the pre-link optimization stage. let sanitizer_options = if !is_lto { @@ -439,6 +447,8 @@ pub(crate) unsafe fn optimize_with_new_llvm_pass_manager( pgo_use_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), config.instrument_coverage, config.instrument_gcov, + pgo_sample_use_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), + config.debug_info_for_profiling, llvm_selfprofiler, selfprofile_before_pass_callback, selfprofile_after_pass_callback, @@ -544,6 +554,9 @@ pub(crate) unsafe fn optimize( if config.instrument_coverage { llvm::LLVMRustAddPass(mpm, find_pass("instrprof").unwrap()); } + if config.debug_info_for_profiling { + llvm::LLVMRustAddPass(mpm, find_pass("add-discriminators").unwrap()); + } add_sanitizer_passes(config, &mut extra_passes); @@ -1001,6 +1014,7 @@ pub unsafe fn with_llvm_pmb( let inline_threshold = config.inline_threshold; let pgo_gen_path = get_pgo_gen_path(config); let pgo_use_path = get_pgo_use_path(config); + let pgo_sample_use_path = get_pgo_sample_use_path(config); llvm::LLVMRustConfigurePassManagerBuilder( builder, @@ -1011,6 +1025,7 @@ pub unsafe fn with_llvm_pmb( prepare_for_thin_lto, pgo_gen_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()), pgo_use_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()), + pgo_sample_use_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()), ); llvm::LLVMPassManagerBuilderSetSizeLevel(builder, opt_size as u32); diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 436d906827b5b..5d437b2a068df 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2176,6 +2176,7 @@ extern "C" { PrepareForThinLTO: bool, PGOGenPath: *const c_char, PGOUsePath: *const c_char, + PGOSampleUsePath: *const c_char, ); pub fn LLVMRustAddLibraryInfo( PM: &PassManager<'a>, @@ -2210,6 +2211,8 @@ extern "C" { PGOUsePath: *const c_char, InstrumentCoverage: bool, InstrumentGCOV: bool, + PGOSampleUsePath: *const c_char, + DebugInfoForProfiling: bool, llvm_selfprofiler: *mut c_void, begin_callback: SelfProfileBeforePassCallback, end_callback: SelfProfileAfterPassCallback, diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index e3b0eea0d89c7..429dc45d6a4c4 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -286,6 +286,9 @@ impl<'a> GccLinker<'a> { config::OptLevel::Aggressive => "O3", }; + if let Some(path) = &self.sess.opts.debugging_opts.profile_sample_use { + self.linker_arg(&format!("-plugin-opt=sample-profile={}", path.display())); + }; self.linker_arg(&format!("-plugin-opt={}", opt_level)); self.linker_arg(&format!("-plugin-opt=mcpu={}", self.target_cpu)); } diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 31722d07414d0..da34612ce76ac 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -83,6 +83,8 @@ pub struct ModuleConfig { pub pgo_gen: SwitchWithOptPath, pub pgo_use: Option, + pub pgo_sample_use: Option, + pub debug_info_for_profiling: bool, pub instrument_coverage: bool, pub instrument_gcov: bool, @@ -176,6 +178,8 @@ impl ModuleConfig { SwitchWithOptPath::Disabled ), pgo_use: if_regular!(sess.opts.cg.profile_use.clone(), None), + pgo_sample_use: if_regular!(sess.opts.debugging_opts.profile_sample_use.clone(), None), + debug_info_for_profiling: sess.opts.debugging_opts.debug_info_for_profiling, instrument_coverage: if_regular!(sess.instrument_coverage(), false), instrument_gcov: if_regular!( // compiler_builtins overrides the codegen-units settings, diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index cfe13b1fd4e1f..844e5ab56a420 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -715,6 +715,7 @@ fn test_debugging_options_tracking_hash() { tracked!(chalk, true); tracked!(codegen_backend, Some("abc".to_string())); tracked!(crate_attr, vec!["abc".to_string()]); + tracked!(debug_info_for_profiling, true); tracked!(debug_macros, true); tracked!(dep_info_omit_d_target, true); tracked!(dual_proc_macros, true); @@ -752,6 +753,7 @@ fn test_debugging_options_tracking_hash() { tracked!(profile, true); tracked!(profile_emit, Some(PathBuf::from("abc"))); tracked!(profiler_runtime, "abc".to_string()); + tracked!(profile_sample_use, Some(PathBuf::from("abc"))); tracked!(relax_elf_relocations, Some(true)); tracked!(relro_level, Some(RelroLevel::Full)); tracked!(remap_cwd_prefix, Some(PathBuf::from("abc"))); diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index ddb5f7dcebfad..87f423fb2d56e 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -25,6 +25,7 @@ #include "llvm/Transforms/IPO/PassManagerBuilder.h" #include "llvm/Transforms/IPO/AlwaysInliner.h" #include "llvm/Transforms/IPO/FunctionImport.h" +#include "llvm/Transforms/Utils/AddDiscriminators.h" #include "llvm/Transforms/Utils/FunctionImportUtils.h" #include "llvm/LTO/LTO.h" #include "llvm-c/Transforms/PassManagerBuilder.h" @@ -39,6 +40,7 @@ #include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h" #include "llvm/Transforms/Utils/CanonicalizeAliases.h" #include "llvm/Transforms/Utils/NameAnonGlobals.h" +#include "llvm/Transforms/Utils.h" using namespace llvm; @@ -523,7 +525,7 @@ extern "C" void LLVMRustDisposeTargetMachine(LLVMTargetMachineRef TM) { extern "C" void LLVMRustConfigurePassManagerBuilder( LLVMPassManagerBuilderRef PMBR, LLVMRustCodeGenOptLevel OptLevel, bool MergeFunctions, bool SLPVectorize, bool LoopVectorize, bool PrepareForThinLTO, - const char* PGOGenPath, const char* PGOUsePath) { + const char* PGOGenPath, const char* PGOUsePath, const char* PGOSampleUsePath) { unwrap(PMBR)->MergeFunctions = MergeFunctions; unwrap(PMBR)->SLPVectorize = SLPVectorize; unwrap(PMBR)->OptLevel = fromRust(OptLevel); @@ -531,13 +533,14 @@ extern "C" void LLVMRustConfigurePassManagerBuilder( unwrap(PMBR)->PrepareForThinLTO = PrepareForThinLTO; if (PGOGenPath) { - assert(!PGOUsePath); + assert(!PGOUsePath && !PGOSampleUsePath); unwrap(PMBR)->EnablePGOInstrGen = true; unwrap(PMBR)->PGOInstrGen = PGOGenPath; - } - if (PGOUsePath) { - assert(!PGOGenPath); + } else if (PGOUsePath) { + assert(!PGOSampleUsePath); unwrap(PMBR)->PGOInstrUse = PGOUsePath; + } else if (PGOSampleUsePath) { + unwrap(PMBR)->PGOSampleUse = PGOSampleUsePath; } } @@ -759,6 +762,7 @@ LLVMRustOptimizeWithNewPassManager( LLVMRustSanitizerOptions *SanitizerOptions, const char *PGOGenPath, const char *PGOUsePath, bool InstrumentCoverage, bool InstrumentGCOV, + const char *PGOSampleUsePath, bool DebugInfoForProfiling, void* LlvmSelfProfiler, LLVMRustSelfProfileBeforePassCallback BeforePassCallback, LLVMRustSelfProfileAfterPassCallback AfterPassCallback, @@ -797,11 +801,19 @@ LLVMRustOptimizeWithNewPassManager( Optional PGOOpt; if (PGOGenPath) { - assert(!PGOUsePath); - PGOOpt = PGOOptions(PGOGenPath, "", "", PGOOptions::IRInstr); + assert(!PGOUsePath && !PGOSampleUsePath); + PGOOpt = PGOOptions(PGOGenPath, "", "", PGOOptions::IRInstr, + PGOOptions::NoCSAction, DebugInfoForProfiling); } else if (PGOUsePath) { - assert(!PGOGenPath); - PGOOpt = PGOOptions(PGOUsePath, "", "", PGOOptions::IRUse); + assert(!PGOSampleUsePath); + PGOOpt = PGOOptions(PGOUsePath, "", "", PGOOptions::IRUse, + PGOOptions::NoCSAction, DebugInfoForProfiling); + } else if (PGOSampleUsePath) { + PGOOpt = PGOOptions(PGOSampleUsePath, "", "", PGOOptions::SampleUse, + PGOOptions::NoCSAction, DebugInfoForProfiling); + } else if (DebugInfoForProfiling) { + PGOOpt = PGOOptions("", "", "", PGOOptions::NoAction, + PGOOptions::NoCSAction, DebugInfoForProfiling); } #if LLVM_VERSION_GE(12, 0) && !LLVM_VERSION_GE(13,0) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 32aa035e1cdec..ac4bce7350b90 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2009,6 +2009,15 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { ); } + if debugging_opts.profile_sample_use.is_some() + && (cg.profile_generate.enabled() || cg.profile_use.is_some()) + { + early_error( + error_format, + "option `-Z profile-sample-use` cannot be used with `-C profile-generate` or `-C profile-use`", + ); + } + if debugging_opts.instrument_coverage.is_some() && debugging_opts.instrument_coverage != Some(InstrumentCoverage::Off) { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 8ecb7a031ad81..b3d36b396c51c 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1040,6 +1040,8 @@ options! { "combine CGUs into a single one"), crate_attr: Vec = (Vec::new(), parse_string_push, [TRACKED], "inject the given attribute in the crate"), + debug_info_for_profiling: bool = (false, parse_bool, [TRACKED], + "emit discriminators and other data necessary for AutoFDO"), debug_macros: bool = (false, parse_bool, [TRACKED], "emit line numbers debug info inside macros (default: no)"), deduplicate_diagnostics: bool = (true, parse_bool, [UNTRACKED], @@ -1242,6 +1244,8 @@ options! { (default based on relative source path)"), profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED], "name of the profiler runtime crate to automatically inject (default: `profiler_builtins`)"), + profile_sample_use: Option = (None, parse_opt_pathbuf, [TRACKED], + "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"), query_dep_graph: bool = (false, parse_bool, [UNTRACKED], "enable queries of the dependency graph for regression testing (default: no)"), query_stats: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index bf04154a3dafe..b6ba6cc1dd659 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1353,6 +1353,16 @@ fn validate_commandline_args_with_session_available(sess: &Session) { } } + // Do the same for sample profile data. + if let Some(ref path) = sess.opts.debugging_opts.profile_sample_use { + if !path.exists() { + sess.err(&format!( + "File `{}` passed to `-C profile-sample-use` does not exist.", + path.display() + )); + } + } + // Unwind tables cannot be disabled if the target requires them. if let Some(include_uwtables) = sess.opts.cg.force_unwind_tables { if sess.target.requires_uwtable && !include_uwtables { diff --git a/src/doc/unstable-book/src/compiler-flags/debug_info_for_profiling.md b/src/doc/unstable-book/src/compiler-flags/debug_info_for_profiling.md new file mode 100644 index 0000000000000..44bd3baeeedfc --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/debug_info_for_profiling.md @@ -0,0 +1,35 @@ +# `debug-info-for-profiling + +--- + +## Introduction + +Automatic Feedback Directed Optimization (AFDO) is a method for using sampling +based profiles to guide optimizations. This is contrasted with other methods of +FDO or profile-guided optimization (PGO) which use instrumented profiling. + +Unlike PGO (controlled by the `rustc` flags `-Cprofile-generate` and +`-Cprofile-use`), a binary being profiled does not perform significantly worse, +and thus it's possible to profile binaries used in real workflows and not +necessary to construct artificial workflows. + +## Use + +In order to use AFDO, the target platform must be Linux running on an `x86_64` +architecture with the performance profiler `perf` available. In addition, the +external tool `create_llvm_prof` from [this repository] must be used. + +Given a Rust file `main.rs`, we can produce an optimized binary as follows: + +```shell +rustc -O -Zdebug-info-for-profiling main.rs -o main +perf record -b ./main +create_llvm_prof --binary=main --out=code.prof +rustc -O -Zprofile-sample-use=code.prof main.rs -o main2 +``` + +The `perf` command produces a profile `perf.data`, which is then used by the +`create_llvm_prof` command to create `code.prof`. This final profile is then +used by `rustc` to guide optimizations in producing the binary `main2`. + +[this repository]: https://github.com/google/autofdo diff --git a/src/doc/unstable-book/src/compiler-flags/profile_sample_use.md b/src/doc/unstable-book/src/compiler-flags/profile_sample_use.md new file mode 100644 index 0000000000000..ce894ce6ac7f1 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/profile_sample_use.md @@ -0,0 +1,10 @@ +# `profile-sample-use + +--- + +`-Zprofile-sample-use=code.prof` directs `rustc` to use the profile +`code.prof` as a source for Automatic Feedback Directed Optimization (AFDO). +See the documentation of [`-Zdebug-info-for-profiling`] for more information +on using AFDO. + +[`-Zdebug-info-for-profiling`]: debug_info_for_profiling.html From afe5335b978bc490f27d45a42bac770383450268 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 7 Oct 2021 12:44:46 +0200 Subject: [PATCH 13/15] Use correct edition for panic in [debug_]assert!() etc. --- compiler/rustc_builtin_macros/src/assert.rs | 6 +++--- compiler/rustc_builtin_macros/src/panic.rs | 19 ++++++++++++++++++- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/macros/mod.rs | 1 + 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs index 93ba54da3424e..1e2646e4d348f 100644 --- a/compiler/rustc_builtin_macros/src/assert.rs +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -1,10 +1,10 @@ -use rustc_errors::{Applicability, DiagnosticBuilder}; - +use crate::panic::use_panic_2021; use rustc_ast::ptr::P; use rustc_ast::token; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::{self as ast, *}; use rustc_ast_pretty::pprust; +use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_expand::base::*; use rustc_parse::parser::Parser; use rustc_span::symbol::{sym, Ident, Symbol}; @@ -28,7 +28,7 @@ pub fn expand_assert<'cx>( let sp = cx.with_call_site_ctxt(span); let panic_call = if let Some(tokens) = custom_message { - let path = if span.rust_2021() { + let path = if use_panic_2021(span) { // On edition 2021, we always call `$crate::panic::panic_2021!()`. Path { span: sp, diff --git a/compiler/rustc_builtin_macros/src/panic.rs b/compiler/rustc_builtin_macros/src/panic.rs index 6f5962d435c33..54ab596bf3eb8 100644 --- a/compiler/rustc_builtin_macros/src/panic.rs +++ b/compiler/rustc_builtin_macros/src/panic.rs @@ -2,6 +2,7 @@ use rustc_ast::ptr::P; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::*; use rustc_expand::base::*; +use rustc_span::edition::Edition; use rustc_span::symbol::sym; use rustc_span::Span; @@ -19,7 +20,7 @@ pub fn expand_panic<'cx>( sp: Span, tts: TokenStream, ) -> Box { - let panic = if sp.rust_2021() { sym::panic_2021 } else { sym::panic_2015 }; + let panic = if use_panic_2021(sp) { sym::panic_2021 } else { sym::panic_2015 }; let sp = cx.with_call_site_ctxt(sp); @@ -46,3 +47,19 @@ pub fn expand_panic<'cx>( ), ) } + +pub fn use_panic_2021(mut span: Span) -> bool { + // To determine the editon, we check the first span up the expansion + // stack that does not have #[allow_internal_unstable(edition_panic)]. + // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.) + loop { + let expn = span.ctxt().outer_expn_data(); + if let Some(features) = expn.allow_internal_unstable { + if features.iter().any(|&f| f == sym::edition_panic) { + span = expn.call_site; + continue; + } + } + break expn.edition >= Edition::Edition2021; + } +} diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 77baf7d73810e..995c974b865ba 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -568,6 +568,7 @@ symbols! { dyn_metadata, dyn_trait, edition_macro_pats, + edition_panic, eh_catch_typeinfo, eh_personality, emit_enum, diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 035a748f78248..5b3e988caa506 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -210,6 +210,7 @@ pub macro assert_matches { #[macro_export] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_diagnostic_item = "debug_assert_macro"] +#[allow_internal_unstable(edition_panic)] macro_rules! debug_assert { ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert!($($arg)*); }) } From fcd9fa9099569beba9c85c594ecbb9b07a1a7501 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 7 Oct 2021 12:45:01 +0200 Subject: [PATCH 14/15] Add tests for panic and [debug_]assert in Rust 2021. --- src/test/ui/rust-2021/panic.rs | 24 +++++++ src/test/ui/rust-2021/panic.stderr | 101 +++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/test/ui/rust-2021/panic.rs create mode 100644 src/test/ui/rust-2021/panic.stderr diff --git a/src/test/ui/rust-2021/panic.rs b/src/test/ui/rust-2021/panic.rs new file mode 100644 index 0000000000000..394fc3c8f8254 --- /dev/null +++ b/src/test/ui/rust-2021/panic.rs @@ -0,0 +1,24 @@ +// edition:2021 + +fn main() { + debug_assert!(false, 123); + //~^ ERROR must be a string literal + assert!(false, 123); + //~^ ERROR must be a string literal + panic!(false, 123); + //~^ ERROR must be a string literal + + std::debug_assert!(false, 123); + //~^ ERROR must be a string literal + std::assert!(false, 123); + //~^ ERROR must be a string literal + std::panic!(false, 123); + //~^ ERROR must be a string literal + + core::debug_assert!(false, 123); + //~^ ERROR must be a string literal + core::assert!(false, 123); + //~^ ERROR must be a string literal + core::panic!(false, 123); + //~^ ERROR must be a string literal +} diff --git a/src/test/ui/rust-2021/panic.stderr b/src/test/ui/rust-2021/panic.stderr new file mode 100644 index 0000000000000..40b62d279a509 --- /dev/null +++ b/src/test/ui/rust-2021/panic.stderr @@ -0,0 +1,101 @@ +error: format argument must be a string literal + --> $DIR/panic.rs:4:26 + | +LL | debug_assert!(false, 123); + | ^^^ + | +help: you might be missing a string literal to format with + | +LL | debug_assert!(false, "{}", 123); + | +++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:6:20 + | +LL | assert!(false, 123); + | ^^^ + | +help: you might be missing a string literal to format with + | +LL | assert!(false, "{}", 123); + | +++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:8:12 + | +LL | panic!(false, 123); + | ^^^^^ + | +help: you might be missing a string literal to format with + | +LL | panic!("{} {}", false, 123); + | ++++++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:11:31 + | +LL | std::debug_assert!(false, 123); + | ^^^ + | +help: you might be missing a string literal to format with + | +LL | std::debug_assert!(false, "{}", 123); + | +++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:13:25 + | +LL | std::assert!(false, 123); + | ^^^ + | +help: you might be missing a string literal to format with + | +LL | std::assert!(false, "{}", 123); + | +++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:15:17 + | +LL | std::panic!(false, 123); + | ^^^^^ + | +help: you might be missing a string literal to format with + | +LL | std::panic!("{} {}", false, 123); + | ++++++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:18:32 + | +LL | core::debug_assert!(false, 123); + | ^^^ + | +help: you might be missing a string literal to format with + | +LL | core::debug_assert!(false, "{}", 123); + | +++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:20:26 + | +LL | core::assert!(false, 123); + | ^^^ + | +help: you might be missing a string literal to format with + | +LL | core::assert!(false, "{}", 123); + | +++++ + +error: format argument must be a string literal + --> $DIR/panic.rs:22:18 + | +LL | core::panic!(false, 123); + | ^^^^^ + | +help: you might be missing a string literal to format with + | +LL | core::panic!("{} {}", false, 123); + | ++++++++ + +error: aborting due to 9 previous errors + From 6162fc0c809477529875b294a8ce37ff8737356c Mon Sep 17 00:00:00 2001 From: Hans Kratz Date: Sat, 25 Sep 2021 15:25:08 +0200 Subject: [PATCH 15/15] Add wrapper for -Z gcc-ld=lld to invoke rust-lld with the correct flavor The wrapper is installed as `ld` and `ld64` in the `lib\rustlib\\bin\gcc-ld` directory and its sole purpose is to invoke `rust-lld` in the parent directory with the correct flavor. --- Cargo.lock | 4 + Cargo.toml | 1 + src/bootstrap/compile.rs | 16 ++-- src/bootstrap/dist.rs | 13 ++-- src/bootstrap/tool.rs | 32 ++++++++ src/tools/lld-wrapper/Cargo.toml | 11 +++ src/tools/lld-wrapper/src/main.rs | 125 ++++++++++++++++++++++++++++++ 7 files changed, 189 insertions(+), 13 deletions(-) create mode 100644 src/tools/lld-wrapper/Cargo.toml create mode 100644 src/tools/lld-wrapper/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 197b2c8f3f06a..870b7d3dbabbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1965,6 +1965,10 @@ dependencies = [ "walkdir", ] +[[package]] +name = "lld-wrapper" +version = "0.1.0" + [[package]] name = "lock_api" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index ce7073886c20e..42dd5d7ef432e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "src/tools/jsondocck", "src/tools/html-checker", "src/tools/bump-stage0", + "src/tools/lld-wrapper", ] exclude = [ diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs index ae234fb1dc729..4b189672226ea 100644 --- a/src/bootstrap/compile.rs +++ b/src/bootstrap/compile.rs @@ -1136,14 +1136,14 @@ impl Step for Assemble { // for `-Z gcc-ld=lld` let gcc_ld_dir = libdir_bin.join("gcc-ld"); t!(fs::create_dir(&gcc_ld_dir)); - builder.copy( - &lld_install.join("bin").join(&src_exe), - &gcc_ld_dir.join(exe("ld", target_compiler.host)), - ); - builder.copy( - &lld_install.join("bin").join(&src_exe), - &gcc_ld_dir.join(exe("ld64", target_compiler.host)), - ); + for flavor in ["ld", "ld64"] { + let lld_wrapper_exe = builder.ensure(crate::tool::LldWrapper { + compiler: build_compiler, + target: target_compiler.host, + flavor_feature: flavor, + }); + builder.copy(&lld_wrapper_exe, &gcc_ld_dir.join(exe(flavor, target_compiler.host))); + } } // Similarly, copy `llvm-dwp` into libdir for Split DWARF. Only copy it when the LLVM diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 7c1bb1a91481b..d4875cfe1b066 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -409,11 +409,14 @@ impl Step for Rustc { let rust_lld = exe("rust-lld", compiler.host); builder.copy(&src_dir.join(&rust_lld), &dst_dir.join(&rust_lld)); // for `-Z gcc-ld=lld` - let gcc_lld_dir = dst_dir.join("gcc-ld"); - t!(fs::create_dir(&gcc_lld_dir)); - builder.copy(&src_dir.join(&rust_lld), &gcc_lld_dir.join(exe("ld", compiler.host))); - builder - .copy(&src_dir.join(&rust_lld), &gcc_lld_dir.join(exe("ld64", compiler.host))); + let gcc_lld_src_dir = src_dir.join("gcc-ld"); + let gcc_lld_dst_dir = dst_dir.join("gcc-ld"); + t!(fs::create_dir(&gcc_lld_dst_dir)); + for flavor in ["ld", "ld64"] { + let exe_name = exe(flavor, compiler.host); + builder + .copy(&gcc_lld_src_dir.join(&exe_name), &gcc_lld_dst_dir.join(&exe_name)); + } } // Copy over llvm-dwp if it's there diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index c035894638538..af6f4bb0e5fcb 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -664,6 +664,38 @@ impl Step for Cargo { } } +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct LldWrapper { + pub compiler: Compiler, + pub target: TargetSelection, + pub flavor_feature: &'static str, +} + +impl Step for LldWrapper { + type Output = PathBuf; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.never() + } + + fn run(self, builder: &Builder<'_>) -> PathBuf { + let src_exe = builder + .ensure(ToolBuild { + compiler: self.compiler, + target: self.target, + tool: "lld-wrapper", + mode: Mode::ToolStd, + path: "src/tools/lld-wrapper", + is_optional_tool: false, + source_type: SourceType::InTree, + extra_features: vec![self.flavor_feature.to_owned()], + }) + .expect("expected to build -- essential tool"); + + src_exe + } +} + macro_rules! tool_extended { (($sel:ident, $builder:ident), $($name:ident, diff --git a/src/tools/lld-wrapper/Cargo.toml b/src/tools/lld-wrapper/Cargo.toml new file mode 100644 index 0000000000000..66a586fd6c35e --- /dev/null +++ b/src/tools/lld-wrapper/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lld-wrapper" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] + +[features] +ld = [] +ld64 = [] \ No newline at end of file diff --git a/src/tools/lld-wrapper/src/main.rs b/src/tools/lld-wrapper/src/main.rs new file mode 100644 index 0000000000000..1601bf1b34e9c --- /dev/null +++ b/src/tools/lld-wrapper/src/main.rs @@ -0,0 +1,125 @@ +//! Script to invoke the bundled rust-lld with the correct flavor. The flavor is selected by +//! feature. +//! +//! lld supports multiple command line interfaces. If `-flavor ` are passed as the first +//! two arguments the `` command line interface is used to process the remaining arguments. +//! If no `-flavor` argument is present the flavor is determined by the executable name. +//! +//! In Rust with `-Z gcc-ld=lld` we have gcc or clang invoke rust-lld. Since there is no way to +//! make gcc/clang pass `-flavor ` as the first two arguments in the linker invocation +//! and since Windows does not support symbolic links for files this wrapper is used in place of a +//! symblic link. It execs `../rust-lld -flavor ld` if the feature `ld` is enabled and +//! `../rust-lld -flavor ld64` if `ld64` is enabled. On Windows it spawns a `..\rust-lld.exe` +//! child process. + +#[cfg(not(any(feature = "ld", feature = "ld64")))] +compile_error!("One of the features ld and ld64 must be enabled."); + +#[cfg(all(feature = "ld", feature = "ld64"))] +compile_error!("Only one of the feature ld or ld64 can be enabled."); + +#[cfg(feature = "ld")] +const FLAVOR: &str = "ld"; + +#[cfg(feature = "ld64")] +const FLAVOR: &str = "ld64"; + +use std::env; +use std::fmt::Display; +use std::path::{Path, PathBuf}; +use std::process; + +trait ResultExt { + fn unwrap_or_exit_with(self, context: &str) -> T; +} + +impl ResultExt for Result +where + E: Display, +{ + fn unwrap_or_exit_with(self, context: &str) -> T { + match self { + Ok(t) => t, + Err(e) => { + eprintln!("lld-wrapper: {}: {}", context, e); + process::exit(1); + } + } + } +} + +trait OptionExt { + fn unwrap_or_exit_with(self, context: &str) -> T; +} + +impl OptionExt for Option { + fn unwrap_or_exit_with(self, context: &str) -> T { + match self { + Some(t) => t, + None => { + eprintln!("lld-wrapper: {}", context); + process::exit(1); + } + } + } +} + +/// Returns the path to rust-lld in the parent directory. +/// +/// Exits if the parent directory cannot be determined. +fn get_rust_lld_path(current_exe_path: &Path) -> PathBuf { + let mut rust_lld_exe_name = "rust-lld".to_owned(); + rust_lld_exe_name.push_str(env::consts::EXE_SUFFIX); + let mut rust_lld_path = current_exe_path + .parent() + .unwrap_or_exit_with("directory containing current executable could not be determined") + .parent() + .unwrap_or_exit_with("parent directory could not be determined") + .to_owned(); + rust_lld_path.push(rust_lld_exe_name); + rust_lld_path +} + +/// Returns the command for invoking rust-lld with the correct flavor. +/// +/// Exits on error. +fn get_rust_lld_command(current_exe_path: &Path) -> process::Command { + let rust_lld_path = get_rust_lld_path(current_exe_path); + let mut command = process::Command::new(rust_lld_path); + command.arg("-flavor"); + command.arg(FLAVOR); + command.args(env::args_os().skip(1)); + command +} + +#[cfg(unix)] +fn exec_lld(mut command: process::Command) { + use std::os::unix::prelude::CommandExt; + Result::<(), _>::Err(command.exec()).unwrap_or_exit_with("could not exec rust-lld"); + unreachable!("lld-wrapper: after exec without error"); +} + +#[cfg(not(unix))] +fn exec_lld(mut command: process::Command) { + // Windows has no exec(), spawn a child process and wait for it + let exit_status = command.status().unwrap_or_exit_with("error running rust-lld child process"); + if !exit_status.success() { + match exit_status.code() { + Some(code) => { + // return the original lld exit code + process::exit(code) + } + None => { + eprintln!("lld-wrapper: rust-lld child process exited with error: {}", exit_status,); + process::exit(1); + } + } + } +} + +fn main() { + let current_exe_path = + env::current_exe().unwrap_or_exit_with("could not get the path of the current executable"); + + exec_lld(get_rust_lld_command(current_exe_path.as_ref())); +}