Skip to content

Commit 63d710d

Browse files
authored
Rollup merge of rust-lang#57268 - peterhj:peterhj-optmergefunc, r=nagisa
Add a target option "merge-functions", and a corresponding -Z flag (works around rust-lang#57356) This commit adds a target option "merge-functions", which takes values in ("disabled", "trampolines", or "aliases" (default is "aliases")), to allow targets to opt out of the MergeFunctions LLVM pass. Additionally, the latest commit also adds an optional -Z flag, "merge-functions", which takes the same values and has precedence over the target option when both are specified. This works around rust-lang#57356. cc @eddyb @japaric @oli-obk @nox @nagisa Also thanks to @denzp and @gnzlbg for discussing this on rust-cuda! ### Motivation Basically, the problem is that the MergeFunctions pass, which rustc currently enables by default at -O2 and -O3 [1], and `extern "ptx-kernel"` functions (specific to the NVPTX target) are currently not compatible with each other. If the MergeFunctions pass is allowed to run, rustc can generate invalid PTX assembly (i.e. a PTX file that is not accepted by the native PTX assembler `ptxas`). Therefore we would like a way to opt out of the MergeFunctions pass, which is what our target option does. ### Related work The current behavior of rustc is to enable MergeFunctions at -O2 and -O3 [1], and also to enable the use of function aliases within MergeFunctions [2] [3]. MergeFunctions seems to have some benefits, such as reducing code size and fixing a crash [4], which is why it is enabled. However, MergeFunctions both with and without function aliases is incompatible with the NVPTX target; a more detailed example for both cases is given below. clang's "solution" is to have a "-fmerge-functions" flag that opts in to the MergeFunctions pass, but it is not enabled by default. ### Examples/more details Consider an example Rust lib using `extern "ptx-kernel"` functions: https://github.com/peterhj/nvptx-mergefunc-bug/blob/master/nocore.rs. If we try to compile this with nightly rustc, we get the following compiler error: LLVM ERROR: Module has aliases, which NVPTX does not support. This error happens because: (1) functions `foo` and `bar` have the same body, so are candidates to be merged by MergeFunctions; and (2) rustc configures MergeFunctions to generate function aliases using the "mergefunc-use-aliases" LLVM option [2] [3], but the NVPTX backend does not support those aliases. Okay, so we can try omitting "mergefunc-use-aliases", and then rustc will happily emit PTX assembly: https://github.com/peterhj/nvptx-mergefunc-bug/blob/master/nocore-mergefunc-nousealiases-bad.ptx. However, this PTX is invalid! When we try to assemble it with `ptxas` (I'm on the CUDA 9.2 toolchain), we get an assembler error: ptxas nocore-mergefunc-nousealiases-bad.ptx, line 38; error : Illegal call target, device function expected ptxas fatal : Ptx assembly aborted due to errors What's happening is that MergeFunctions rewrites the `bar` function to call `foo`. However, directly calling an `extern "ptx-kernel"` function from another `extern "ptx-kernel"` is wrong. If we disable the MergeFunctions pass from running at all, rustc generates correct PTX assembly: https://github.com/peterhj/nvptx-mergefunc-bug/blob/master/nocore-nomergefunc-ok.ptx [1] https://github.com/rust-lang/rust/blob/a36b960df626cbb8bea74f01243318b73f0bd201/src/librustc_codegen_ssa/back/write.rs#L155 [2] https://github.com/rust-lang/rust/blob/a36b960df626cbb8bea74f01243318b73f0bd201/src/librustc_codegen_llvm/llvm_util.rs#L64 [3] rust-lang#56358 [4] rust-lang#49479
2 parents ce882c1 + b91d211 commit 63d710d

File tree

4 files changed

+117
-8
lines changed

4 files changed

+117
-8
lines changed

src/librustc/session/config.rs

+24-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::str::FromStr;
66
use session::{early_error, early_warn, Session};
77
use session::search_paths::SearchPath;
88

9-
use rustc_target::spec::{LinkerFlavor, PanicStrategy, RelroLevel};
9+
use rustc_target::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, RelroLevel};
1010
use rustc_target::spec::{Target, TargetTriple};
1111
use lint;
1212
use middle::cstore;
@@ -808,13 +808,16 @@ macro_rules! options {
808808
pub const parse_cross_lang_lto: Option<&str> =
809809
Some("either a boolean (`yes`, `no`, `on`, `off`, etc), \
810810
or the path to the linker plugin");
811+
pub const parse_merge_functions: Option<&str> =
812+
Some("one of: `disabled`, `trampolines`, or `aliases`");
811813
}
812814

813815
#[allow(dead_code)]
814816
mod $mod_set {
815817
use super::{$struct_name, Passes, Sanitizer, LtoCli, CrossLangLto};
816-
use rustc_target::spec::{LinkerFlavor, PanicStrategy, RelroLevel};
818+
use rustc_target::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, RelroLevel};
817819
use std::path::PathBuf;
820+
use std::str::FromStr;
818821

819822
$(
820823
pub fn $opt(cg: &mut $struct_name, v: Option<&str>) -> bool {
@@ -1046,6 +1049,14 @@ macro_rules! options {
10461049
};
10471050
true
10481051
}
1052+
1053+
fn parse_merge_functions(slot: &mut Option<MergeFunctions>, v: Option<&str>) -> bool {
1054+
match v.and_then(|s| MergeFunctions::from_str(s).ok()) {
1055+
Some(mergefunc) => *slot = Some(mergefunc),
1056+
_ => return false,
1057+
}
1058+
true
1059+
}
10491060
}
10501061
) }
10511062

@@ -1376,6 +1387,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
13761387
"whether to use the PLT when calling into shared libraries;
13771388
only has effect for PIC code on systems with ELF binaries
13781389
(default: PLT is disabled if full relro is enabled)"),
1390+
merge_functions: Option<MergeFunctions> = (None, parse_merge_functions, [TRACKED],
1391+
"control the operation of the MergeFunctions LLVM pass, taking
1392+
the same values as the target option of the same name"),
13791393
}
13801394

13811395
pub fn default_lib_output() -> CrateType {
@@ -2394,7 +2408,7 @@ mod dep_tracking {
23942408
use super::{CrateType, DebugInfo, ErrorOutputType, OptLevel, OutputTypes,
23952409
Passes, Sanitizer, LtoCli, CrossLangLto};
23962410
use syntax::feature_gate::UnstableFeatures;
2397-
use rustc_target::spec::{PanicStrategy, RelroLevel, TargetTriple};
2411+
use rustc_target::spec::{MergeFunctions, PanicStrategy, RelroLevel, TargetTriple};
23982412
use syntax::edition::Edition;
23992413

24002414
pub trait DepTrackingHash {
@@ -2437,12 +2451,14 @@ mod dep_tracking {
24372451
impl_dep_tracking_hash_via_hash!(Option<usize>);
24382452
impl_dep_tracking_hash_via_hash!(Option<String>);
24392453
impl_dep_tracking_hash_via_hash!(Option<(String, u64)>);
2454+
impl_dep_tracking_hash_via_hash!(Option<MergeFunctions>);
24402455
impl_dep_tracking_hash_via_hash!(Option<PanicStrategy>);
24412456
impl_dep_tracking_hash_via_hash!(Option<RelroLevel>);
24422457
impl_dep_tracking_hash_via_hash!(Option<lint::Level>);
24432458
impl_dep_tracking_hash_via_hash!(Option<PathBuf>);
24442459
impl_dep_tracking_hash_via_hash!(Option<cstore::NativeLibraryKind>);
24452460
impl_dep_tracking_hash_via_hash!(CrateType);
2461+
impl_dep_tracking_hash_via_hash!(MergeFunctions);
24462462
impl_dep_tracking_hash_via_hash!(PanicStrategy);
24472463
impl_dep_tracking_hash_via_hash!(RelroLevel);
24482464
impl_dep_tracking_hash_via_hash!(Passes);
@@ -2528,7 +2544,7 @@ mod tests {
25282544
use std::iter::FromIterator;
25292545
use std::path::PathBuf;
25302546
use super::{Externs, OutputType, OutputTypes};
2531-
use rustc_target::spec::{PanicStrategy, RelroLevel};
2547+
use rustc_target::spec::{MergeFunctions, PanicStrategy, RelroLevel};
25322548
use syntax::symbol::Symbol;
25332549
use syntax::edition::{Edition, DEFAULT_EDITION};
25342550
use syntax;
@@ -3183,6 +3199,10 @@ mod tests {
31833199
opts = reference.clone();
31843200
opts.debugging_opts.cross_lang_lto = CrossLangLto::LinkerPluginAuto;
31853201
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
3202+
3203+
opts = reference.clone();
3204+
opts.debugging_opts.merge_functions = Some(MergeFunctions::Disabled);
3205+
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
31863206
}
31873207

31883208
#[test]

src/librustc_codegen_llvm/llvm_util.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use back::write::create_target_machine;
33
use llvm;
44
use rustc::session::Session;
55
use rustc::session::config::PrintRequest;
6+
use rustc_target::spec::MergeFunctions;
67
use libc::c_int;
78
use std::ffi::CString;
89
use syntax::feature_gate::UnstableFeatures;
@@ -61,7 +62,14 @@ unsafe fn configure_llvm(sess: &Session) {
6162
add("-disable-preinline");
6263
}
6364
if llvm::LLVMRustIsRustLLVM() {
64-
add("-mergefunc-use-aliases");
65+
match sess.opts.debugging_opts.merge_functions
66+
.unwrap_or(sess.target.target.options.merge_functions) {
67+
MergeFunctions::Disabled |
68+
MergeFunctions::Trampolines => {}
69+
MergeFunctions::Aliases => {
70+
add("-mergefunc-use-aliases");
71+
}
72+
}
6573
}
6674

6775
// HACK(eddyb) LLVM inserts `llvm.assume` calls to preserve align attributes

src/librustc_codegen_ssa/back/write.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use rustc_fs_util::link_or_copy;
2424
use rustc_data_structures::svh::Svh;
2525
use rustc_errors::{Handler, Level, DiagnosticBuilder, FatalError, DiagnosticId};
2626
use rustc_errors::emitter::{Emitter};
27+
use rustc_target::spec::MergeFunctions;
2728
use syntax::attr;
2829
use syntax::ext::hygiene::Mark;
2930
use syntax_pos::MultiSpan;
@@ -152,8 +153,24 @@ impl ModuleConfig {
152153
sess.opts.optimize == config::OptLevel::Aggressive &&
153154
!sess.target.target.options.is_like_emscripten;
154155

155-
self.merge_functions = sess.opts.optimize == config::OptLevel::Default ||
156-
sess.opts.optimize == config::OptLevel::Aggressive;
156+
// Some targets (namely, NVPTX) interact badly with the MergeFunctions
157+
// pass. This is because MergeFunctions can generate new function calls
158+
// which may interfere with the target calling convention; e.g. for the
159+
// NVPTX target, PTX kernels should not call other PTX kernels.
160+
// MergeFunctions can also be configured to generate aliases instead,
161+
// but aliases are not supported by some backends (again, NVPTX).
162+
// Therefore, allow targets to opt out of the MergeFunctions pass,
163+
// but otherwise keep the pass enabled (at O2 and O3) since it can be
164+
// useful for reducing code size.
165+
self.merge_functions = match sess.opts.debugging_opts.merge_functions
166+
.unwrap_or(sess.target.target.options.merge_functions) {
167+
MergeFunctions::Disabled => false,
168+
MergeFunctions::Trampolines |
169+
MergeFunctions::Aliases => {
170+
sess.opts.optimize == config::OptLevel::Default ||
171+
sess.opts.optimize == config::OptLevel::Aggressive
172+
}
173+
};
157174
}
158175

159176
pub fn bitcode_needed(&self) -> bool {

src/librustc_target/spec/mod.rs

+65-1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,46 @@ impl ToJson for RelroLevel {
217217
}
218218
}
219219

220+
#[derive(Clone, Copy, Debug, PartialEq, Hash, RustcEncodable, RustcDecodable)]
221+
pub enum MergeFunctions {
222+
Disabled,
223+
Trampolines,
224+
Aliases
225+
}
226+
227+
impl MergeFunctions {
228+
pub fn desc(&self) -> &str {
229+
match *self {
230+
MergeFunctions::Disabled => "disabled",
231+
MergeFunctions::Trampolines => "trampolines",
232+
MergeFunctions::Aliases => "aliases",
233+
}
234+
}
235+
}
236+
237+
impl FromStr for MergeFunctions {
238+
type Err = ();
239+
240+
fn from_str(s: &str) -> Result<MergeFunctions, ()> {
241+
match s {
242+
"disabled" => Ok(MergeFunctions::Disabled),
243+
"trampolines" => Ok(MergeFunctions::Trampolines),
244+
"aliases" => Ok(MergeFunctions::Aliases),
245+
_ => Err(()),
246+
}
247+
}
248+
}
249+
250+
impl ToJson for MergeFunctions {
251+
fn to_json(&self) -> Json {
252+
match *self {
253+
MergeFunctions::Disabled => "disabled".to_json(),
254+
MergeFunctions::Trampolines => "trampolines".to_json(),
255+
MergeFunctions::Aliases => "aliases".to_json(),
256+
}
257+
}
258+
}
259+
220260
pub type LinkArgs = BTreeMap<LinkerFlavor, Vec<String>>;
221261
pub type TargetResult = Result<Target, String>;
222262

@@ -690,7 +730,15 @@ pub struct TargetOptions {
690730

691731
/// If set, have the linker export exactly these symbols, instead of using
692732
/// the usual logic to figure this out from the crate itself.
693-
pub override_export_symbols: Option<Vec<String>>
733+
pub override_export_symbols: Option<Vec<String>>,
734+
735+
/// Determines how or whether the MergeFunctions LLVM pass should run for
736+
/// this target. Either "disabled", "trampolines", or "aliases".
737+
/// The MergeFunctions pass is generally useful, but some targets may need
738+
/// to opt out. The default is "aliases".
739+
///
740+
/// Workaround for: https://github.com/rust-lang/rust/issues/57356
741+
pub merge_functions: MergeFunctions
694742
}
695743

696744
impl Default for TargetOptions {
@@ -773,6 +821,7 @@ impl Default for TargetOptions {
773821
requires_uwtable: false,
774822
simd_types_indirect: true,
775823
override_export_symbols: None,
824+
merge_functions: MergeFunctions::Aliases,
776825
}
777826
}
778827
}
@@ -875,6 +924,19 @@ impl Target {
875924
.map(|o| o.as_u64()
876925
.map(|s| base.options.$key_name = Some(s)));
877926
} );
927+
($key_name:ident, MergeFunctions) => ( {
928+
let name = (stringify!($key_name)).replace("_", "-");
929+
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| {
930+
match s.parse::<MergeFunctions>() {
931+
Ok(mergefunc) => base.options.$key_name = mergefunc,
932+
_ => return Some(Err(format!("'{}' is not a valid value for \
933+
merge-functions. Use 'disabled', \
934+
'trampolines', or 'aliases'.",
935+
s))),
936+
}
937+
Some(Ok(()))
938+
})).unwrap_or(Ok(()))
939+
} );
878940
($key_name:ident, PanicStrategy) => ( {
879941
let name = (stringify!($key_name)).replace("_", "-");
880942
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| {
@@ -1064,6 +1126,7 @@ impl Target {
10641126
key!(requires_uwtable, bool);
10651127
key!(simd_types_indirect, bool);
10661128
key!(override_export_symbols, opt_list);
1129+
key!(merge_functions, MergeFunctions)?;
10671130

10681131
if let Some(array) = obj.find("abi-blacklist").and_then(Json::as_array) {
10691132
for name in array.iter().filter_map(|abi| abi.as_string()) {
@@ -1275,6 +1338,7 @@ impl ToJson for Target {
12751338
target_option_val!(requires_uwtable);
12761339
target_option_val!(simd_types_indirect);
12771340
target_option_val!(override_export_symbols);
1341+
target_option_val!(merge_functions);
12781342

12791343
if default.abi_blacklist != self.options.abi_blacklist {
12801344
d.insert("abi-blacklist".to_string(), self.options.abi_blacklist.iter()

0 commit comments

Comments
 (0)