Skip to content

Commit 62728c7

Browse files
committed
Add rustc option to output LLVM optimization remarks to YAML files
1 parent 8882507 commit 62728c7

File tree

11 files changed

+180
-13
lines changed

11 files changed

+180
-13
lines changed

compiler/rustc_codegen_llvm/src/back/lto.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::back::write::{self, save_temp_bitcode, DiagnosticHandlers};
1+
use crate::back::write::{self, save_temp_bitcode, CodegenDiagnosticsStage, DiagnosticHandlers};
22
use crate::errors::{
33
DynamicLinkingWithLTO, LlvmError, LtoBitcodeFromRlib, LtoDisallowed, LtoDylib,
44
};
@@ -302,7 +302,13 @@ fn fat_lto(
302302
// The linking steps below may produce errors and diagnostics within LLVM
303303
// which we'd like to handle and print, so set up our diagnostic handlers
304304
// (which get unregistered when they go out of scope below).
305-
let _handler = DiagnosticHandlers::new(cgcx, diag_handler, llcx);
305+
let _handler = DiagnosticHandlers::new(
306+
cgcx,
307+
diag_handler,
308+
llcx,
309+
&module,
310+
CodegenDiagnosticsStage::LTO,
311+
);
306312

307313
// For all other modules we codegened we'll need to link them into our own
308314
// bitcode. All modules were codegened in their own LLVM context, however,

compiler/rustc_codegen_llvm/src/back/write.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,16 @@ pub(crate) fn save_temp_bitcode(
268268
}
269269
}
270270

271+
/// In what context is a dignostic handler being attached to a codegen unit?
272+
pub enum CodegenDiagnosticsStage {
273+
/// Prelink optimization stage.
274+
Opt,
275+
/// LTO/ThinLTO postlink optimization stage.
276+
LTO,
277+
/// Code generation.
278+
Codegen,
279+
}
280+
271281
pub struct DiagnosticHandlers<'a> {
272282
data: *mut (&'a CodegenContext<LlvmCodegenBackend>, &'a Handler),
273283
llcx: &'a llvm::Context,
@@ -279,6 +289,8 @@ impl<'a> DiagnosticHandlers<'a> {
279289
cgcx: &'a CodegenContext<LlvmCodegenBackend>,
280290
handler: &'a Handler,
281291
llcx: &'a llvm::Context,
292+
module: &ModuleCodegen<ModuleLlvm>,
293+
stage: CodegenDiagnosticsStage,
282294
) -> Self {
283295
let remark_passes_all: bool;
284296
let remark_passes: Vec<CString>;
@@ -295,6 +307,20 @@ impl<'a> DiagnosticHandlers<'a> {
295307
};
296308
let remark_passes: Vec<*const c_char> =
297309
remark_passes.iter().map(|name: &CString| name.as_ptr()).collect();
310+
let remark_file = cgcx
311+
.remark_dir
312+
.as_ref()
313+
// Use the .opt.yaml file suffix, which is supported by LLVM's opt-viewer.
314+
.map(|dir| {
315+
let stage_suffix = match stage {
316+
CodegenDiagnosticsStage::Codegen => "codegen",
317+
CodegenDiagnosticsStage::Opt => "opt",
318+
CodegenDiagnosticsStage::LTO => "lto",
319+
};
320+
dir.join(format!("{}.{stage_suffix}.opt.yaml", module.name))
321+
})
322+
.and_then(|dir| dir.to_str().and_then(|p| CString::new(p).ok()));
323+
298324
let data = Box::into_raw(Box::new((cgcx, handler)));
299325
unsafe {
300326
let old_handler = llvm::LLVMRustContextGetDiagnosticHandler(llcx);
@@ -305,6 +331,9 @@ impl<'a> DiagnosticHandlers<'a> {
305331
remark_passes_all,
306332
remark_passes.as_ptr(),
307333
remark_passes.len(),
334+
// The `as_ref()` is important here, otherwise the `CString` will be dropped
335+
// too soon!
336+
remark_file.as_ref().map(|dir| dir.as_ptr()).unwrap_or(std::ptr::null()),
308337
);
309338
DiagnosticHandlers { data, llcx, old_handler }
310339
}
@@ -523,7 +552,8 @@ pub(crate) unsafe fn optimize(
523552

524553
let llmod = module.module_llvm.llmod();
525554
let llcx = &*module.module_llvm.llcx;
526-
let _handlers = DiagnosticHandlers::new(cgcx, diag_handler, llcx);
555+
let _handlers =
556+
DiagnosticHandlers::new(cgcx, diag_handler, llcx, module, CodegenDiagnosticsStage::Opt);
527557

528558
let module_name = module.name.clone();
529559
let module_name = Some(&module_name[..]);
@@ -582,7 +612,13 @@ pub(crate) unsafe fn codegen(
582612
let tm = &*module.module_llvm.tm;
583613
let module_name = module.name.clone();
584614
let module_name = Some(&module_name[..]);
585-
let handlers = DiagnosticHandlers::new(cgcx, diag_handler, llcx);
615+
let _handlers = DiagnosticHandlers::new(
616+
cgcx,
617+
diag_handler,
618+
llcx,
619+
&module,
620+
CodegenDiagnosticsStage::Codegen,
621+
);
586622

587623
if cgcx.msvc_imps_needed {
588624
create_msvc_imps(cgcx, llcx, llmod);
@@ -775,7 +811,6 @@ pub(crate) unsafe fn codegen(
775811
}
776812

777813
record_llvm_cgu_instructions_stats(&cgcx.prof, llmod);
778-
drop(handlers);
779814
}
780815

781816
// `.dwo` files are only emitted if:

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,7 @@ extern "C" {
25122512
remark_all_passes: bool,
25132513
remark_passes: *const *const c_char,
25142514
remark_passes_len: usize,
2515+
remark_file: *const c_char,
25152516
);
25162517

25172518
#[allow(improper_ctypes)]

compiler/rustc_codegen_ssa/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ codegen_ssa_create_temp_dir = couldn't create a temp dir: {$error}
2121
2222
codegen_ssa_erroneous_constant = erroneous constant encountered
2323
24+
codegen_ssa_error_creating_remark_dir = failed to create remark directory: {$error}
25+
2426
codegen_ssa_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)`
2527
2628
codegen_ssa_extern_funcs_not_found = some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified

compiler/rustc_codegen_ssa/src/back/write.rs

+16
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use rustc_span::symbol::sym;
3535
use rustc_span::{BytePos, FileName, InnerSpan, Pos, Span};
3636
use rustc_target::spec::{MergeFunctions, SanitizerSet};
3737

38+
use crate::errors::ErrorCreatingRemarkDir;
3839
use std::any::Any;
3940
use std::borrow::Cow;
4041
use std::fs;
@@ -345,6 +346,9 @@ pub struct CodegenContext<B: WriteBackendMethods> {
345346
pub diag_emitter: SharedEmitter,
346347
/// LLVM optimizations for which we want to print remarks.
347348
pub remark: Passes,
349+
/// Directory into which should the LLVM optimization remarks be written.
350+
/// If `None`, they will be written to stderr.
351+
pub remark_dir: Option<PathBuf>,
348352
/// Worker thread number
349353
pub worker: usize,
350354
/// The incremental compilation session directory, or None if we are not
@@ -1041,6 +1045,17 @@ fn start_executing_work<B: ExtraBackendMethods>(
10411045
tcx.backend_optimization_level(())
10421046
};
10431047
let backend_features = tcx.global_backend_features(());
1048+
1049+
let remark_dir = if let Some(ref dir) = sess.opts.unstable_opts.remark_dir {
1050+
let result = fs::create_dir_all(dir).and_then(|_| dir.canonicalize());
1051+
match result {
1052+
Ok(dir) => Some(dir),
1053+
Err(error) => sess.emit_fatal(ErrorCreatingRemarkDir { error }),
1054+
}
1055+
} else {
1056+
None
1057+
};
1058+
10441059
let cgcx = CodegenContext::<B> {
10451060
crate_types: sess.crate_types().to_vec(),
10461061
each_linked_rlib_for_lto,
@@ -1052,6 +1067,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
10521067
prof: sess.prof.clone(),
10531068
exported_symbols,
10541069
remark: sess.opts.cg.remark.clone(),
1070+
remark_dir,
10551071
worker: 0,
10561072
incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()),
10571073
cgu_reuse_tracker: sess.cgu_reuse_tracker.clone(),

compiler/rustc_codegen_ssa/src/errors.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1023,3 +1023,9 @@ pub struct TargetFeatureSafeTrait {
10231023
#[label(codegen_ssa_label_def)]
10241024
pub def: Span,
10251025
}
1026+
1027+
#[derive(Diagnostic)]
1028+
#[diag(codegen_ssa_error_creating_remark_dir)]
1029+
pub struct ErrorCreatingRemarkDir {
1030+
pub error: std::io::Error,
1031+
}

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

+83-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
#include "llvm/IR/Instructions.h"
88
#include "llvm/IR/Intrinsics.h"
99
#include "llvm/IR/IntrinsicsARM.h"
10+
#include "llvm/IR/LLVMRemarkStreamer.h"
1011
#include "llvm/IR/Mangler.h"
12+
#include "llvm/Remarks/RemarkStreamer.h"
13+
#include "llvm/Remarks/RemarkSerializer.h"
14+
#include "llvm/Remarks/RemarkFormat.h"
15+
#include "llvm/Support/ToolOutputFile.h"
1116
#if LLVM_VERSION_GE(16, 0)
1217
#include "llvm/Support/ModRef.h"
1318
#endif
@@ -1855,23 +1860,44 @@ using LLVMDiagnosticHandlerTy = DiagnosticHandler::DiagnosticHandlerTy;
18551860
// When RemarkAllPasses is true, remarks are enabled for all passes. Otherwise
18561861
// the RemarkPasses array specifies individual passes for which remarks will be
18571862
// enabled.
1863+
//
1864+
// If RemarkFilePath is not NULL, optimization remarks will be streamed directly into this file,
1865+
// bypassing the diagnostics handler.
18581866
extern "C" void LLVMRustContextConfigureDiagnosticHandler(
18591867
LLVMContextRef C, LLVMDiagnosticHandlerTy DiagnosticHandlerCallback,
18601868
void *DiagnosticHandlerContext, bool RemarkAllPasses,
1861-
const char * const * RemarkPasses, size_t RemarkPassesLen) {
1869+
const char * const * RemarkPasses, size_t RemarkPassesLen,
1870+
const char * RemarkFilePath
1871+
) {
18621872

18631873
class RustDiagnosticHandler final : public DiagnosticHandler {
18641874
public:
1865-
RustDiagnosticHandler(LLVMDiagnosticHandlerTy DiagnosticHandlerCallback,
1866-
void *DiagnosticHandlerContext,
1867-
bool RemarkAllPasses,
1868-
std::vector<std::string> RemarkPasses)
1875+
RustDiagnosticHandler(
1876+
LLVMDiagnosticHandlerTy DiagnosticHandlerCallback,
1877+
void *DiagnosticHandlerContext,
1878+
bool RemarkAllPasses,
1879+
std::vector<std::string> RemarkPasses,
1880+
std::unique_ptr<ToolOutputFile> RemarksFile,
1881+
std::unique_ptr<llvm::remarks::RemarkStreamer> RemarkStreamer,
1882+
std::unique_ptr<LLVMRemarkStreamer> LlvmRemarkStreamer
1883+
)
18691884
: DiagnosticHandlerCallback(DiagnosticHandlerCallback),
18701885
DiagnosticHandlerContext(DiagnosticHandlerContext),
18711886
RemarkAllPasses(RemarkAllPasses),
1872-
RemarkPasses(RemarkPasses) {}
1887+
RemarkPasses(std::move(RemarkPasses)),
1888+
RemarksFile(std::move(RemarksFile)),
1889+
RemarkStreamer(std::move(RemarkStreamer)),
1890+
LlvmRemarkStreamer(std::move(LlvmRemarkStreamer)) {}
18731891

18741892
virtual bool handleDiagnostics(const DiagnosticInfo &DI) override {
1893+
if (this->LlvmRemarkStreamer) {
1894+
if (auto *OptDiagBase = dyn_cast<DiagnosticInfoOptimizationBase>(&DI)) {
1895+
if (OptDiagBase->isEnabled()) {
1896+
this->LlvmRemarkStreamer->emit(*OptDiagBase);
1897+
return true;
1898+
}
1899+
}
1900+
}
18751901
if (DiagnosticHandlerCallback) {
18761902
DiagnosticHandlerCallback(DI, DiagnosticHandlerContext);
18771903
return true;
@@ -1912,14 +1938,64 @@ extern "C" void LLVMRustContextConfigureDiagnosticHandler(
19121938

19131939
bool RemarkAllPasses = false;
19141940
std::vector<std::string> RemarkPasses;
1941+
1942+
// Since LlvmRemarkStreamer contains a pointer to RemarkStreamer, the ordering of the three
1943+
// members below is important.
1944+
std::unique_ptr<ToolOutputFile> RemarksFile;
1945+
std::unique_ptr<llvm::remarks::RemarkStreamer> RemarkStreamer;
1946+
std::unique_ptr<LLVMRemarkStreamer> LlvmRemarkStreamer;
19151947
};
19161948

19171949
std::vector<std::string> Passes;
19181950
for (size_t I = 0; I != RemarkPassesLen; ++I)
1951+
{
19191952
Passes.push_back(RemarkPasses[I]);
1953+
}
1954+
1955+
// We need to hold onto both the streamers and the opened file
1956+
std::unique_ptr<ToolOutputFile> RemarkFile;
1957+
std::unique_ptr<llvm::remarks::RemarkStreamer> RemarkStreamer;
1958+
std::unique_ptr<LLVMRemarkStreamer> LlvmRemarkStreamer;
1959+
1960+
if (RemarkFilePath != nullptr) {
1961+
std::error_code EC;
1962+
RemarkFile = std::make_unique<ToolOutputFile>(
1963+
RemarkFilePath,
1964+
EC,
1965+
llvm::sys::fs::OF_TextWithCRLF
1966+
);
1967+
if (EC) {
1968+
std::string Error = std::string("Cannot create remark file: ") +
1969+
toString(errorCodeToError(EC));
1970+
report_fatal_error(Twine(Error));
1971+
}
1972+
1973+
// Do not delete the file after we gather remarks
1974+
RemarkFile->keep();
1975+
1976+
auto RemarkSerializer = remarks::createRemarkSerializer(
1977+
llvm::remarks::Format::YAML,
1978+
remarks::SerializerMode::Separate,
1979+
RemarkFile->os()
1980+
);
1981+
if (Error E = RemarkSerializer.takeError())
1982+
{
1983+
std::string Error = std::string("Cannot create remark serializer: ") + toString(std::move(E));
1984+
report_fatal_error(Twine(Error));
1985+
}
1986+
RemarkStreamer = std::make_unique<llvm::remarks::RemarkStreamer>(std::move(*RemarkSerializer));
1987+
LlvmRemarkStreamer = std::make_unique<LLVMRemarkStreamer>(*RemarkStreamer);
1988+
}
19201989

19211990
unwrap(C)->setDiagnosticHandler(std::make_unique<RustDiagnosticHandler>(
1922-
DiagnosticHandlerCallback, DiagnosticHandlerContext, RemarkAllPasses, Passes));
1991+
DiagnosticHandlerCallback,
1992+
DiagnosticHandlerContext,
1993+
RemarkAllPasses,
1994+
Passes,
1995+
std::move(RemarkFile),
1996+
std::move(RemarkStreamer),
1997+
std::move(LlvmRemarkStreamer)
1998+
));
19231999
}
19242000

19252001
extern "C" void LLVMRustGetMangledName(LLVMValueRef V, RustStringRef Str) {

compiler/rustc_session/src/config.rs

+4
Original file line numberDiff line numberDiff line change
@@ -2583,6 +2583,10 @@ pub fn build_session_options(
25832583
handler.early_warn("-C remark requires \"-C debuginfo=n\" to show source locations");
25842584
}
25852585

2586+
if cg.remark.is_empty() && unstable_opts.remark_dir.is_some() {
2587+
handler.early_warn("using -Z remark-dir without enabling remarks using e.g. -C remark=all");
2588+
}
2589+
25862590
let externs = parse_externs(handler, matches, &unstable_opts);
25872591

25882592
let crate_name = matches.opt_str("crate-name");

compiler/rustc_session/src/options.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ options! {
13141314
"control generation of position-independent code (PIC) \
13151315
(`rustc --print relocation-models` for details)"),
13161316
remark: Passes = (Passes::Some(Vec::new()), parse_passes, [UNTRACKED],
1317-
"print remarks for these optimization passes (space separated, or \"all\")"),
1317+
"output remarks for these optimization passes (space separated, or \"all\")"),
13181318
rpath: bool = (false, parse_bool, [UNTRACKED],
13191319
"set rpath values in libs/exes (default: no)"),
13201320
save_temps: bool = (false, parse_bool, [UNTRACKED],
@@ -1659,6 +1659,9 @@ options! {
16591659
"choose which RELRO level to use"),
16601660
remap_cwd_prefix: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
16611661
"remap paths under the current working directory to this path prefix"),
1662+
remark_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
1663+
"directory into which to write optimization remarks (if not specified, they will be \
1664+
written to standard error output)"),
16621665
report_delayed_bugs: bool = (false, parse_bool, [TRACKED],
16631666
"immediately print bugs registered with `delay_span_bug` (default: no)"),
16641667
sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
include ../tools.mk
2+
3+
PROFILE_DIR=$(TMPDIR)/profiles
4+
5+
all: check_inline check_filter
6+
7+
check_inline:
8+
$(RUSTC) -O foo.rs --crate-type=lib -Cremark=all -Zremark-dir=$(PROFILE_DIR)
9+
cat $(PROFILE_DIR)/*.opt.yaml | $(CGREP) -e "inline"
10+
check_filter:
11+
$(RUSTC) -O foo.rs --crate-type=lib -Cremark=foo -Zremark-dir=$(PROFILE_DIR)
12+
cat $(PROFILE_DIR)/*.opt.yaml | $(CGREP) -e -v "inline"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#[inline(never)]
2+
pub fn bar() {}
3+
4+
pub fn foo() {
5+
bar();
6+
}

0 commit comments

Comments
 (0)