Skip to content

Commit 0b20dbd

Browse files
authored
Merge 337b1e7 into e3892a4
2 parents e3892a4 + 337b1e7 commit 0b20dbd

File tree

16 files changed

+229
-1
lines changed

16 files changed

+229
-1
lines changed

compiler/rustc_codegen_llvm/src/attributes.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,15 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
393393
to_add.push(llvm::CreateAttrString(cx.llcx, "use-sample-profile"));
394394
}
395395

396+
// patchable-function is only implemented on x86 on LLVM
397+
if cx.sess().opts.unstable_opts.hotpatch && cx.sess().target.is_x86() {
398+
to_add.push(llvm::CreateAttrStringValue(
399+
cx.llcx,
400+
"patchable-function",
401+
"prologue-short-redirect",
402+
));
403+
}
404+
396405
// FIXME: none of these functions interact with source level attributes.
397406
to_add.extend(frame_pointer_type_attr(cx));
398407
to_add.extend(function_return_attr(cx));

compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ impl OwnedTargetMachine {
3434
emit_stack_size_section: bool,
3535
relax_elf_relocations: bool,
3636
use_init_array: bool,
37+
is_hotpatchable: bool,
3738
split_dwarf_file: &CStr,
3839
output_obj_file: &CStr,
3940
debug_info_compression: &CStr,
@@ -66,6 +67,7 @@ impl OwnedTargetMachine {
6667
emit_stack_size_section,
6768
relax_elf_relocations,
6869
use_init_array,
70+
is_hotpatchable,
6971
split_dwarf_file.as_ptr(),
7072
output_obj_file.as_ptr(),
7173
debug_info_compression.as_ptr(),

compiler/rustc_codegen_llvm/src/back/write.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ pub(crate) fn target_machine_factory(
244244
let use_init_array =
245245
!sess.opts.unstable_opts.use_ctors_section.unwrap_or(sess.target.use_ctors_section);
246246

247+
// this makes LLVM add a hotpatch flag in the codeview S_COMPILE3 record,
248+
// which is required by linkers for the functionpadmin option
249+
// aarch64 is always hotpatchable
250+
let is_hotpatchable = sess.opts.unstable_opts.hotpatch || sess.target.arch.contains("aarch64");
251+
247252
let path_mapping = sess.source_map().path_mapping().clone();
248253

249254
let use_emulated_tls = matches!(sess.tls_model(), TlsModel::Emulated);
@@ -316,6 +321,7 @@ pub(crate) fn target_machine_factory(
316321
emit_stack_size_section,
317322
relax_elf_relocations,
318323
use_init_array,
324+
is_hotpatchable,
319325
&split_dwarf_file,
320326
&output_obj_file,
321327
&debuginfo_compression,

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2417,6 +2417,7 @@ unsafe extern "C" {
24172417
EmitStackSizeSection: bool,
24182418
RelaxELFRelocations: bool,
24192419
UseInitArray: bool,
2420+
IsHotpatchable: bool,
24202421
SplitDwarfFile: *const c_char,
24212422
OutputObjFile: *const c_char,
24222423
DebugInfoCompression: *const c_char,

compiler/rustc_interface/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ fn test_unstable_options_tracking_hash() {
801801
tracked!(force_unstable_if_unmarked, true);
802802
tracked!(function_return, FunctionReturn::ThunkExtern);
803803
tracked!(function_sections, Some(false));
804+
tracked!(hotpatch, true);
804805
tracked!(human_readable_cgu_names, true);
805806
tracked!(incremental_ignore_spans, true);
806807
tracked!(inline_mir, Some(true));

compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(
394394
bool FunctionSections, bool DataSections, bool UniqueSectionNames,
395395
bool TrapUnreachable, bool Singlethread, bool VerboseAsm,
396396
bool EmitStackSizeSection, bool RelaxELFRelocations, bool UseInitArray,
397-
const char *SplitDwarfFile, const char *OutputObjFile,
397+
bool IsHotpatchable, const char *SplitDwarfFile, const char *OutputObjFile,
398398
const char *DebugInfoCompression, bool UseEmulatedTls,
399399
const char *ArgsCstrBuff, size_t ArgsCstrBuffLen) {
400400

@@ -422,6 +422,7 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(
422422
// Always preserve comments that were written by the user
423423
Options.MCOptions.PreserveAsmComments = true;
424424
Options.MCOptions.ABIName = ABIStr;
425+
Options.Hotpatch = IsHotpatchable;
425426
if (SplitDwarfFile) {
426427
Options.MCOptions.SplitDwarfFile = SplitDwarfFile;
427428
}

compiler/rustc_session/src/options.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2234,6 +2234,10 @@ options! {
22342234
environment variable `RUSTC_GRAPHVIZ_FONT` (default: `Courier, monospace`)"),
22352235
has_thread_local: Option<bool> = (None, parse_opt_bool, [TRACKED],
22362236
"explicitly enable the `cfg(target_thread_local)` directive"),
2237+
hotpatch: bool = (false, parse_bool, [TRACKED],
2238+
"ensures hotpatching is always possible by ensuring that the first instruction of \
2239+
each function is at least two bytes, and no jump within the function goes to the first instruction. \
2240+
Should be combined with link-arg passing -functionpadmin to the linker. Currently only supported for x86 (default: false)"),
22372241
human_readable_cgu_names: bool = (false, parse_bool, [TRACKED],
22382242
"generate human-readable, predictable names for codegen units (default: no)"),
22392243
identify_regions: bool = (false, parse_bool, [UNTRACKED],

compiler/rustc_target/src/spec/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2219,6 +2219,9 @@ impl Target {
22192219

22202220
Ok(dl)
22212221
}
2222+
pub fn is_x86(&self) -> bool {
2223+
["x86", "x86_64"].contains(&&self.arch[..])
2224+
}
22222225
}
22232226

22242227
pub trait HasTargetSpec {

src/tools/run-make-support/src/external_deps/llvm.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,13 @@ impl LlvmFilecheck {
282282
self
283283
}
284284

285+
/// Specify the prefix (without :) for patterns to match. By default, these patterns are prefixed with "CHECK:".
286+
pub fn check_prefix(&mut self, prefix: &str) -> &mut Self {
287+
self.cmd.arg("--check-prefix");
288+
self.cmd.arg(prefix);
289+
self
290+
}
291+
285292
/// `--input-file` option.
286293
pub fn input_file<P: AsRef<Path>>(&mut self, input_file: P) -> &mut Self {
287294
self.cmd.arg("--input-file");

tests/codegen/hotpatch.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// check if functions get the attribute, so that LLVM ensures they are hotpatchable
2+
// the attribute is only implemented for x86, aarch64 does not require it
3+
4+
//@ revisions: x32 x64
5+
//@[x32] only-x86
6+
//@[x64] only-x86_64
7+
//@ compile-flags: -Z hotpatch
8+
9+
#![crate_type = "lib"]
10+
11+
#[no_mangle]
12+
pub fn foo() {}
13+
14+
// CHECK-LABEL: @foo() unnamed_addr #0
15+
// CHECK: attributes #0 = { {{.*}} "patchable-function"="prologue-short-redirect" {{.*}}}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// to be able to hotpatch a function there are two requirements:
2+
// 1. the first instruction of a functin must be at least two bytes long
3+
// 2. there must not be a jump to the first instruction
4+
5+
// the functions in this file already fulfill the conditions so hotpatch should not affect them
6+
7+
// --------------------------------------------------------------------------------------------
8+
9+
#[no_mangle]
10+
#[inline(never)]
11+
pub fn return_42() -> i32 {
12+
42
13+
}
14+
15+
// --------------------------------------------------------------------------------------------
16+
// This tailcall does not jump to the first instruction so hotpatch should leave it unaffected
17+
18+
#[no_mangle]
19+
pub fn tailcall(a: i32) -> i32 {
20+
if a > 10000 {
21+
return a;
22+
}
23+
24+
if a % 2 == 0 { tailcall(a / 2) } else { tailcall(a * 3 + 1) }
25+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Check if hotpatch leaves the functions that are already hotpatchable untouched
2+
3+
use run_make_support::{assertion_helpers, llvm, rustc};
4+
5+
fn main() {
6+
// hotpatch is only implemented for X86 and aarch64
7+
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
8+
{
9+
fn base_rustc() -> rustc::Rustc {
10+
let mut rustc = rustc();
11+
rustc.input("lib.rs").crate_type("lib").opt_level("3");
12+
rustc
13+
}
14+
15+
fn dump_lib(libname: &str) -> String {
16+
llvm::llvm_objdump()
17+
.arg("--disassemble-symbols=return_42,tailcall")
18+
.input(libname)
19+
.run()
20+
.stdout_utf8()
21+
}
22+
23+
base_rustc().crate_name("regular").run();
24+
let regular_dump = dump_lib("libregular.rlib");
25+
26+
base_rustc().crate_name("hotpatch").arg("-Zhotpatch").run();
27+
let hotpatch_dump = dump_lib("libhotpatch.rlib");
28+
29+
{
30+
let mut lines_regular = regular_dump.lines();
31+
let mut lines_hotpatch = hotpatch_dump.lines();
32+
33+
loop {
34+
match (lines_regular.next(), lines_hotpatch.next()) {
35+
(None, None) => break,
36+
(Some(r), Some(h)) => {
37+
if r.contains("libregular.rlib") {
38+
assertion_helpers::assert_contains(h, "libhotpatch.rlib")
39+
} else {
40+
assertion_helpers::assert_equals(&r, &h)
41+
}
42+
}
43+
_ => panic!("expected files to have equal number of lines"),
44+
}
45+
}
46+
}
47+
}
48+
}

tests/run-make/hotpatch/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// to be able to hotpatch a function there are two requirements:
2+
// 1) the first instruction of a functin must be at least two bytes long
3+
// 2) there must not be a jump to the first instruction
4+
5+
// The LLVM attribute we use '"patchable-function", "prologue-short-redirect"' only ensures 1)
6+
// However in practice 2) rarely matters. Its rare that it occurs and the problems it caused can be
7+
// avoided by the hotpatch tool.
8+
// In this test we check if 1) is ensured by inserted nops as needed
9+
10+
// ----------------------------------------------------------------------------------------------
11+
12+
// empty_fn just returns. Note that 'ret' is a single byte instruction, but hotpatch requires
13+
// a two or more byte instructions to be at the start of the functions.
14+
// Preferably we would also tests a different single byte instruction,
15+
// but I was not able to find an example with another one byte intstruction.
16+
17+
// check that if the first instruction is just a single byte, so our test is valid
18+
// CHECK-LABEL: <empty_fn>:
19+
// CHECK-NOT: 0: {{[0-9a-f][0-9a-f]}} {{[0-9a-f][0-9a-f]}} {{.*}}
20+
21+
// check that the first instruction is at least 2 bytes long
22+
// HOTPATCH-LABEL: <empty_fn>:
23+
// HOTPATCH-NEXT: 0: {{[0-9a-f][0-9a-f]}} {{[0-9a-f][0-9a-f]}} {{.*}}
24+
25+
#[no_mangle]
26+
#[inline(never)]
27+
pub fn empty_fn() {}

tests/run-make/hotpatch/rmake.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Check if hotpatch makes the functions hotpachable that were not
2+
// More details in lib.rs
3+
4+
use run_make_support::{llvm, rustc};
5+
6+
fn main() {
7+
// hotpatch is only implemented for x86 and aarch64, but for aarch64 functions
8+
// are always hotpatchable so we don't need to check it
9+
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
10+
{
11+
fn base_rustc() -> rustc::Rustc {
12+
let mut rustc = rustc();
13+
rustc.input("lib.rs").crate_type("lib").opt_level("3");
14+
rustc
15+
}
16+
17+
fn dump_lib(libname: &str) -> String {
18+
llvm::llvm_objdump()
19+
.arg("--disassemble-symbols=empty_fn")
20+
.input(libname)
21+
.run()
22+
.stdout_utf8()
23+
}
24+
25+
{
26+
base_rustc().crate_name("regular").run();
27+
let regular_dump = dump_lib("libregular.rlib");
28+
llvm::llvm_filecheck().patterns("lib.rs").stdin_buf(regular_dump).run();
29+
}
30+
31+
{
32+
base_rustc().crate_name("hotpatch").arg("-Zhotpatch").run();
33+
let hotpatch_dump = dump_lib("libhotpatch.rlib");
34+
35+
llvm::llvm_filecheck()
36+
.patterns("lib.rs")
37+
.check_prefix("HOTPATCH")
38+
.stdin_buf(hotpatch_dump)
39+
.run();
40+
}
41+
}
42+
}

tests/run-make/hotpatch_pdb/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// CHECK: S_OBJNAME{{.*}}hotpatch_pdb{{.*}}.o
2+
// CHECK: S_COMPILE3
3+
// CHECK-NOT: S_
4+
// CHECK: flags = {{.*}}hot patchable
5+
6+
pub fn main() {}

tests/run-make/hotpatch_pdb/rmake.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Check if hotpatch flag is present in the Codeview.
2+
// While this is not strictly neccessary for functionpadmin to work, the currently linker
3+
// would ignore functionpadmin if this is not pressent
4+
5+
use run_make_support::{llvm, rustc};
6+
7+
fn main() {
8+
// PDBs are windows only and hotpatch is only implemented for x86 and aarch64
9+
#[cfg(all(
10+
target_os = "windows",
11+
any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")
12+
))]
13+
{
14+
let output = rustc()
15+
.input("main.rs")
16+
.arg("-g")
17+
.arg("-Zhotpatch")
18+
.crate_name("hotpatch_pdb")
19+
.crate_type("bin")
20+
.run();
21+
22+
let pdbutil_output = llvm::llvm_pdbutil()
23+
.arg("dump")
24+
.arg("-symbols")
25+
.input("hotpatch_pdb.pdb")
26+
.run()
27+
.stdout_utf8();
28+
29+
llvm::llvm_filecheck().patterns("main.rs").stdin_buf(&pdbutil_output).run();
30+
}
31+
}

0 commit comments

Comments
 (0)