rustc_codegen_llvm: add support for writing summary bitcode

Typical uses of ThinLTO don't have any use for this as a standalone
file, but distributed ThinLTO uses this to make the linker phase more
efficient. With clang you'd do something like `clang -flto=thin
-fthin-link-bitcode=foo.indexing.o -c foo.c` and then get both foo.o
(full of bitcode) and foo.indexing.o (just the summary or index part of
the bitcode). That's then usable by a two-stage linking process that's
more friendly to distributed build systems like bazel, which is why I'm
working on this area.

I talked some to @teresajohnson about naming in this area, as things
seem to be a little confused between various blog posts and build
systems. "bitcode index" and "bitcode summary" tend to be a little too
ambiguous, and she tends to use "thin link bitcode" and "minimized
bitcode" (which matches the descriptions in LLVM). Since the clang
option is thin-link-bitcode, I went with that to try and not add a new
spelling in the world.

Per @dtolnay, you can work around the lack of this by using `lld
--thinlto-index-only` to do the indexing on regular .o files of
bitcode, but that is a bit wasteful on actions when we already have all
the information in rustc and could just write out the matching minimized
bitcode. I didn't test that at all in our infrastructure, because by the
time I learned that I already had this patch largely written.
This commit is contained in:
Augie Fackler 2024-01-19 14:42:43 -05:00
parent e8fbd99128
commit aa91871539
9 changed files with 85 additions and 11 deletions

View File

@ -200,7 +200,7 @@ fn produce_final_output_artifacts(
// to get rid of it. // to get rid of it.
for output_type in crate_output.outputs.keys() { for output_type in crate_output.outputs.keys() {
match *output_type { match *output_type {
OutputType::Bitcode => { OutputType::Bitcode | OutputType::ThinLinkBitcode => {
// Cranelift doesn't have bitcode // Cranelift doesn't have bitcode
// user_wants_bitcode = true; // user_wants_bitcode = true;
// // Copy to .bc, but always keep the .0.bc. There is a later // // Copy to .bc, but always keep the .0.bc. There is a later

View File

@ -335,6 +335,10 @@ impl ThinBufferMethods for ThinBuffer {
fn data(&self) -> &[u8] { fn data(&self) -> &[u8] {
unimplemented!(); unimplemented!();
} }
fn thin_link_data(&self) -> &[u8] {
unimplemented!();
}
} }
pub struct GccContext { pub struct GccContext {

View File

@ -687,6 +687,14 @@ impl ThinBufferMethods for ThinBuffer {
slice::from_raw_parts(ptr, len) slice::from_raw_parts(ptr, len)
} }
} }
fn thin_link_data(&self) -> &[u8] {
unsafe {
let ptr = llvm::LLVMRustThinLTOBufferThinLinkDataPtr(self.0) as *const _;
let len = llvm::LLVMRustThinLTOBufferThinLinkDataLen(self.0);
slice::from_raw_parts(ptr, len)
}
}
} }
impl Drop for ThinBuffer { impl Drop for ThinBuffer {

View File

@ -708,6 +708,8 @@ pub(crate) unsafe fn codegen(
// asm from LLVM and use `gcc` to create the object file. // asm from LLVM and use `gcc` to create the object file.
let bc_out = cgcx.output_filenames.temp_path(OutputType::Bitcode, module_name); let bc_out = cgcx.output_filenames.temp_path(OutputType::Bitcode, module_name);
let bc_index_out =
cgcx.output_filenames.temp_path(OutputType::ThinLinkBitcode, module_name);
let obj_out = cgcx.output_filenames.temp_path(OutputType::Object, module_name); let obj_out = cgcx.output_filenames.temp_path(OutputType::Object, module_name);
if config.bitcode_needed() { if config.bitcode_needed() {
@ -716,6 +718,7 @@ pub(crate) unsafe fn codegen(
.generic_activity_with_arg("LLVM_module_codegen_make_bitcode", &*module.name); .generic_activity_with_arg("LLVM_module_codegen_make_bitcode", &*module.name);
let thin = ThinBuffer::new(llmod, config.emit_thin_lto); let thin = ThinBuffer::new(llmod, config.emit_thin_lto);
let data = thin.data(); let data = thin.data();
let index_data = thin.thin_link_data();
if let Some(bitcode_filename) = bc_out.file_name() { if let Some(bitcode_filename) = bc_out.file_name() {
cgcx.prof.artifact_size( cgcx.prof.artifact_size(
@ -725,14 +728,33 @@ pub(crate) unsafe fn codegen(
); );
} }
if let Some(thin_link_bitcode_filename) = bc_index_out.file_name() {
cgcx.prof.artifact_size(
"llvm_bitcode_summary",
thin_link_bitcode_filename.to_string_lossy(),
index_data.len() as u64,
);
let _timer = cgcx.prof.generic_activity_with_arg(
"LLVM_module_codegen_emit_bitcode_index",
&*module.name,
);
if let Err(err) = fs::write(&bc_index_out, index_data) {
dcx.emit_err(WriteBytecode { path: &bc_index_out, err });
}
}
if config.emit_bc || config.emit_obj == EmitObj::Bitcode { if config.emit_bc || config.emit_obj == EmitObj::Bitcode {
let _timer = cgcx {
.prof let _timer = cgcx.prof.generic_activity_with_arg(
.generic_activity_with_arg("LLVM_module_codegen_emit_bitcode", &*module.name); "LLVM_module_codegen_emit_bitcode",
&*module.name,
);
if let Err(err) = fs::write(&bc_out, data) { if let Err(err) = fs::write(&bc_out, data) {
dcx.emit_err(WriteBytecode { path: &bc_out, err }); dcx.emit_err(WriteBytecode { path: &bc_out, err });
} }
} }
}
if config.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full) { if config.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full) {
let _timer = cgcx let _timer = cgcx

View File

@ -2354,6 +2354,8 @@ extern "C" {
pub fn LLVMRustThinLTOBufferFree(M: &'static mut ThinLTOBuffer); pub fn LLVMRustThinLTOBufferFree(M: &'static mut ThinLTOBuffer);
pub fn LLVMRustThinLTOBufferPtr(M: &ThinLTOBuffer) -> *const c_char; pub fn LLVMRustThinLTOBufferPtr(M: &ThinLTOBuffer) -> *const c_char;
pub fn LLVMRustThinLTOBufferLen(M: &ThinLTOBuffer) -> size_t; pub fn LLVMRustThinLTOBufferLen(M: &ThinLTOBuffer) -> size_t;
pub fn LLVMRustThinLTOBufferThinLinkDataPtr(M: &ThinLTOBuffer) -> *const c_char;
pub fn LLVMRustThinLTOBufferThinLinkDataLen(M: &ThinLTOBuffer) -> size_t;
pub fn LLVMRustCreateThinLTOData( pub fn LLVMRustCreateThinLTOData(
Modules: *const ThinLTOModule, Modules: *const ThinLTOModule,
NumModules: c_uint, NumModules: c_uint,

View File

@ -107,6 +107,7 @@ pub struct ModuleConfig {
pub emit_asm: bool, pub emit_asm: bool,
pub emit_obj: EmitObj, pub emit_obj: EmitObj,
pub emit_thin_lto: bool, pub emit_thin_lto: bool,
pub emit_thin_lto_index: bool,
pub bc_cmdline: String, pub bc_cmdline: String,
// Miscellaneous flags. These are mostly copied from command-line // Miscellaneous flags. These are mostly copied from command-line
@ -231,6 +232,10 @@ impl ModuleConfig {
), ),
emit_obj, emit_obj,
emit_thin_lto: sess.opts.unstable_opts.emit_thin_lto, emit_thin_lto: sess.opts.unstable_opts.emit_thin_lto,
emit_thin_lto_index: if_regular!(
sess.opts.output_types.contains_key(&OutputType::ThinLinkBitcode),
false
),
bc_cmdline: sess.target.bitcode_llvm_cmdline.to_string(), bc_cmdline: sess.target.bitcode_llvm_cmdline.to_string(),
verify_llvm_ir: sess.verify_llvm_ir(), verify_llvm_ir: sess.verify_llvm_ir(),
@ -282,6 +287,7 @@ impl ModuleConfig {
pub fn bitcode_needed(&self) -> bool { pub fn bitcode_needed(&self) -> bool {
self.emit_bc self.emit_bc
|| self.emit_thin_lto_index
|| self.emit_obj == EmitObj::Bitcode || self.emit_obj == EmitObj::Bitcode
|| self.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full) || self.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full)
} }
@ -629,6 +635,9 @@ fn produce_final_output_artifacts(
// them for making an rlib. // them for making an rlib.
copy_if_one_unit(OutputType::Bitcode, true); copy_if_one_unit(OutputType::Bitcode, true);
} }
OutputType::ThinLinkBitcode => {
copy_if_one_unit(OutputType::ThinLinkBitcode, false);
}
OutputType::LlvmAssembly => { OutputType::LlvmAssembly => {
copy_if_one_unit(OutputType::LlvmAssembly, false); copy_if_one_unit(OutputType::LlvmAssembly, false);
} }

View File

@ -62,6 +62,7 @@ pub trait WriteBackendMethods: 'static + Sized + Clone {
pub trait ThinBufferMethods: Send + Sync { pub trait ThinBufferMethods: Send + Sync {
fn data(&self) -> &[u8]; fn data(&self) -> &[u8];
fn thin_link_data(&self) -> &[u8];
} }
pub trait ModuleBufferMethods: Send + Sync { pub trait ModuleBufferMethods: Send + Sync {

View File

@ -1488,6 +1488,7 @@ LLVMRustPrepareThinLTOImport(const LLVMRustThinLTOData *Data, LLVMModuleRef M,
// a ThinLTO summary attached. // a ThinLTO summary attached.
struct LLVMRustThinLTOBuffer { struct LLVMRustThinLTOBuffer {
std::string data; std::string data;
std::string thin_link_data;
}; };
extern "C" LLVMRustThinLTOBuffer* extern "C" LLVMRustThinLTOBuffer*
@ -1495,6 +1496,7 @@ LLVMRustThinLTOBufferCreate(LLVMModuleRef M, bool is_thin) {
auto Ret = std::make_unique<LLVMRustThinLTOBuffer>(); auto Ret = std::make_unique<LLVMRustThinLTOBuffer>();
{ {
auto OS = raw_string_ostream(Ret->data); auto OS = raw_string_ostream(Ret->data);
auto ThinLinkOS = raw_string_ostream(Ret->thin_link_data);
{ {
if (is_thin) { if (is_thin) {
PassBuilder PB; PassBuilder PB;
@ -1508,7 +1510,7 @@ LLVMRustThinLTOBufferCreate(LLVMModuleRef M, bool is_thin) {
PB.registerLoopAnalyses(LAM); PB.registerLoopAnalyses(LAM);
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
ModulePassManager MPM; ModulePassManager MPM;
MPM.addPass(ThinLTOBitcodeWriterPass(OS, nullptr)); MPM.addPass(ThinLTOBitcodeWriterPass(OS, &ThinLinkOS));
MPM.run(*unwrap(M), MAM); MPM.run(*unwrap(M), MAM);
} else { } else {
WriteBitcodeToFile(*unwrap(M), OS); WriteBitcodeToFile(*unwrap(M), OS);
@ -1533,6 +1535,16 @@ LLVMRustThinLTOBufferLen(const LLVMRustThinLTOBuffer *Buffer) {
return Buffer->data.length(); return Buffer->data.length();
} }
extern "C" const void*
LLVMRustThinLTOBufferThinLinkDataPtr(const LLVMRustThinLTOBuffer *Buffer) {
return Buffer->thin_link_data.data();
}
extern "C" size_t
LLVMRustThinLTOBufferThinLinkDataLen(const LLVMRustThinLTOBuffer *Buffer) {
return Buffer->thin_link_data.length();
}
// This is what we used to parse upstream bitcode for actual ThinLTO // This is what we used to parse upstream bitcode for actual ThinLTO
// processing. We'll call this once per module optimized through ThinLTO, and // processing. We'll call this once per module optimized through ThinLTO, and
// it'll be called concurrently on many threads. // it'll be called concurrently on many threads.

View File

@ -465,6 +465,7 @@ impl FromStr for SplitDwarfKind {
#[derive(Encodable, Decodable)] #[derive(Encodable, Decodable)]
pub enum OutputType { pub enum OutputType {
Bitcode, Bitcode,
ThinLinkBitcode,
Assembly, Assembly,
LlvmAssembly, LlvmAssembly,
Mir, Mir,
@ -492,6 +493,7 @@ impl OutputType {
match *self { match *self {
OutputType::Exe | OutputType::DepInfo | OutputType::Metadata => true, OutputType::Exe | OutputType::DepInfo | OutputType::Metadata => true,
OutputType::Bitcode OutputType::Bitcode
| OutputType::ThinLinkBitcode
| OutputType::Assembly | OutputType::Assembly
| OutputType::LlvmAssembly | OutputType::LlvmAssembly
| OutputType::Mir | OutputType::Mir
@ -502,6 +504,7 @@ impl OutputType {
pub fn shorthand(&self) -> &'static str { pub fn shorthand(&self) -> &'static str {
match *self { match *self {
OutputType::Bitcode => "llvm-bc", OutputType::Bitcode => "llvm-bc",
OutputType::ThinLinkBitcode => "thin-link-bitcode",
OutputType::Assembly => "asm", OutputType::Assembly => "asm",
OutputType::LlvmAssembly => "llvm-ir", OutputType::LlvmAssembly => "llvm-ir",
OutputType::Mir => "mir", OutputType::Mir => "mir",
@ -518,6 +521,7 @@ impl OutputType {
"llvm-ir" => OutputType::LlvmAssembly, "llvm-ir" => OutputType::LlvmAssembly,
"mir" => OutputType::Mir, "mir" => OutputType::Mir,
"llvm-bc" => OutputType::Bitcode, "llvm-bc" => OutputType::Bitcode,
"thin-link-bitcode" => OutputType::ThinLinkBitcode,
"obj" => OutputType::Object, "obj" => OutputType::Object,
"metadata" => OutputType::Metadata, "metadata" => OutputType::Metadata,
"link" => OutputType::Exe, "link" => OutputType::Exe,
@ -528,8 +532,9 @@ impl OutputType {
fn shorthands_display() -> String { fn shorthands_display() -> String {
format!( format!(
"`{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`", "`{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`",
OutputType::Bitcode.shorthand(), OutputType::Bitcode.shorthand(),
OutputType::ThinLinkBitcode.shorthand(),
OutputType::Assembly.shorthand(), OutputType::Assembly.shorthand(),
OutputType::LlvmAssembly.shorthand(), OutputType::LlvmAssembly.shorthand(),
OutputType::Mir.shorthand(), OutputType::Mir.shorthand(),
@ -543,6 +548,7 @@ impl OutputType {
pub fn extension(&self) -> &'static str { pub fn extension(&self) -> &'static str {
match *self { match *self {
OutputType::Bitcode => "bc", OutputType::Bitcode => "bc",
OutputType::ThinLinkBitcode => "indexing.o",
OutputType::Assembly => "s", OutputType::Assembly => "s",
OutputType::LlvmAssembly => "ll", OutputType::LlvmAssembly => "ll",
OutputType::Mir => "mir", OutputType::Mir => "mir",
@ -559,9 +565,11 @@ impl OutputType {
| OutputType::LlvmAssembly | OutputType::LlvmAssembly
| OutputType::Mir | OutputType::Mir
| OutputType::DepInfo => true, | OutputType::DepInfo => true,
OutputType::Bitcode | OutputType::Object | OutputType::Metadata | OutputType::Exe => { OutputType::Bitcode
false | OutputType::ThinLinkBitcode
} | OutputType::Object
| OutputType::Metadata
| OutputType::Exe => false,
} }
} }
} }
@ -644,6 +652,7 @@ impl OutputTypes {
pub fn should_codegen(&self) -> bool { pub fn should_codegen(&self) -> bool {
self.0.keys().any(|k| match *k { self.0.keys().any(|k| match *k {
OutputType::Bitcode OutputType::Bitcode
| OutputType::ThinLinkBitcode
| OutputType::Assembly | OutputType::Assembly
| OutputType::LlvmAssembly | OutputType::LlvmAssembly
| OutputType::Mir | OutputType::Mir
@ -657,6 +666,7 @@ impl OutputTypes {
pub fn should_link(&self) -> bool { pub fn should_link(&self) -> bool {
self.0.keys().any(|k| match *k { self.0.keys().any(|k| match *k {
OutputType::Bitcode OutputType::Bitcode
| OutputType::ThinLinkBitcode
| OutputType::Assembly | OutputType::Assembly
| OutputType::LlvmAssembly | OutputType::LlvmAssembly
| OutputType::Mir | OutputType::Mir
@ -1769,6 +1779,12 @@ fn parse_output_types(
display = OutputType::shorthands_display(), display = OutputType::shorthands_display(),
)) ))
}); });
if output_type == OutputType::ThinLinkBitcode && !unstable_opts.unstable_options {
early_dcx.early_fatal(format!(
"{} requested but -Zunstable-options not specified",
OutputType::ThinLinkBitcode.shorthand()
));
}
output_types.insert(output_type, path); output_types.insert(output_type, path);
} }
} }