Skip to content

Commit 131a036

Browse files
committed
Auto merge of #113040 - Kobzol:llvm-remark-streamer, r=tmiasko
Add `-Zremark-dir` unstable flag to write LLVM optimization remarks to YAML This PR adds an option for `rustc` to emit LLVM optimization remarks to a set of YAML files, which can then be digested by existing tools, like https://github.com/OfekShilon/optview2. When `-Cremark-dir` is passed, and remarks are enabled (`-Cremark=all`), the remarks will be now written to the specified directory, **instead** of being printed to standard error output. The files are named based on the CGU from which they are being generated. Currently, the remarks are written using the LLVM streaming machinery, directly in the diagnostics handler. It seemed easier than going back to Rust and then form there back to C++ to use the streamer from the diagnostics handler. But there are many ways to implement this, of course, so I'm open to suggestions :) I included some comments with questions into the code. Also, I'm not sure how to test this. r? `@tmiasko`
2 parents 4c3f8c7 + 62728c7 commit 131a036

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
@@ -2513,6 +2513,7 @@ extern "C" {
25132513
remark_all_passes: bool,
25142514
remark_passes: *const *const c_char,
25152515
remark_passes_len: usize,
2516+
remark_file: *const c_char,
25162517
);
25172518

25182519
#[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
@@ -1062,6 +1066,17 @@ fn start_executing_work<B: ExtraBackendMethods>(
10621066
tcx.backend_optimization_level(())
10631067
};
10641068
let backend_features = tcx.global_backend_features(());
1069+
1070+
let remark_dir = if let Some(ref dir) = sess.opts.unstable_opts.remark_dir {
1071+
let result = fs::create_dir_all(dir).and_then(|_| dir.canonicalize());
1072+
match result {
1073+
Ok(dir) => Some(dir),
1074+
Err(error) => sess.emit_fatal(ErrorCreatingRemarkDir { error }),
1075+
}
1076+
} else {
1077+
None
1078+
};
1079+
10651080
let cgcx = CodegenContext::<B> {
10661081
crate_types: sess.crate_types().to_vec(),
10671082
each_linked_rlib_for_lto,
@@ -1073,6 +1088,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
10731088
prof: sess.prof.clone(),
10741089
exported_symbols,
10751090
remark: sess.opts.cg.remark.clone(),
1091+
remark_dir,
10761092
worker: 0,
10771093
incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()),
10781094
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
@@ -2707,6 +2707,10 @@ pub fn build_session_options(
27072707
handler.early_warn("-C remark requires \"-C debuginfo=n\" to show source locations");
27082708
}
27092709

2710+
if cg.remark.is_empty() && unstable_opts.remark_dir.is_some() {
2711+
handler.early_warn("using -Z remark-dir without enabling remarks using e.g. -C remark=all");
2712+
}
2713+
27102714
let externs = parse_externs(handler, matches, &unstable_opts);
27112715

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

compiler/rustc_session/src/options.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1344,7 +1344,7 @@ options! {
13441344
"control generation of position-independent code (PIC) \
13451345
(`rustc --print relocation-models` for details)"),
13461346
remark: Passes = (Passes::Some(Vec::new()), parse_passes, [UNTRACKED],
1347-
"print remarks for these optimization passes (space separated, or \"all\")"),
1347+
"output remarks for these optimization passes (space separated, or \"all\")"),
13481348
rpath: bool = (false, parse_bool, [UNTRACKED],
13491349
"set rpath values in libs/exes (default: no)"),
13501350
save_temps: bool = (false, parse_bool, [UNTRACKED],
@@ -1689,6 +1689,9 @@ options! {
16891689
"choose which RELRO level to use"),
16901690
remap_cwd_prefix: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
16911691
"remap paths under the current working directory to this path prefix"),
1692+
remark_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
1693+
"directory into which to write optimization remarks (if not specified, they will be \
1694+
written to standard error output)"),
16921695
report_delayed_bugs: bool = (false, parse_bool, [TRACKED],
16931696
"immediately print bugs registered with `delay_span_bug` (default: no)"),
16941697
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)