Change:
LLVM's MergeFunctions introduces aliases which can trigger a bug in macOS's linker when compiling with ThinLTO: For this reason Firefox has to be built with a patched version of when compiling with xLTO. Since the bug in question is pretty nasty (the above example shows a program just returning a different value with no indication that anything is wrong), we should probably try to not trigger it. Right now, however, it seems that one cannot even turn the MergeFunctions pass off in stable Rust. (I wonder if the pass should even be disabled by default on macOS?) cc & (this seems related to )
Is it a mergefunc bug or a ThinLTO bug? Can it be triggered without ThinLTO? If not, I would rather not blindly disable it on macOS, at least not when ThinLTO isn't enabled.
It's not a bug in mergefunc. It might not even be a bug in ThinLTO. It's just that mergefunc is the only thing in that generates symbol aliases in LLVM IR, and these are what can lead to "miscompilations" when using the macOS linker.
Has the ld64 bug been reported somewhere?
We could use to prevent the pass from emitting aliases on macOS, AFAIK.
This is already supported via either or in the target spec.
Do you mean that the test case also fails with enabled, or that we already have a knob to disable them?
Answering my own question, we already have a knob for that.
Which was because .
As and point to there is the unstable option which I've only tested on nightly. I guess there could be a stabilized option, but maybe what you really want to do is to patch the x8664 macos target definition (I believe this is the correct one: ).
Yes, I've been told that it has been reported to Apple. On nightly, one can already control the mergefunc pass. On stable, however, one is stuck with the default behavior. That's what this issue is mostly about.
just want to double-check my understanding: I would have thought one could use the options and to override the set of passes, even on the stable channel. Am I incorrect about this? (If I am correct, I nonetheless will admit that this is an ugly (and very fragile) method to opt out of the MergeFunctions pass)
I think that is correct, yes. It would be rather hard, however, to re-create the set of passes meaningfully, I suspect. Even the compiler delegates this to methods within LLVM, iirc.
I think using might work on stable.
So did that help,
Unfortunately not. Specifying this gives me:

Discussion:
use syntax_pos::symbol::Symbol; use rustc::session::Session; use rustc::session::config::PrintRequest; <ins> use rustc_data_structures::fx::FxHashSet; </ins> use rustc_target::spec::{MergeFunctions, PanicStrategy}; use libc::c_int; use std::ffi::CString;

llvm::LLVMRustInstallFatalErrorHandler(); <ins> fn llvm_arg_to_arg_name(full_arg: &str) -> &str { full_arg.trim().split(|c: char| { c == '=' || c.is_whitespace() }).next().unwrap_or("") } let user_specified_args: FxHashSet<_> = sess .opts .cg .llvm_args .iter() .map(|s| llvm_arg_to_arg_name(s)) .filter(|s| s.len() > 0) .collect(); </ins> { <del> let mut add = |arg: &str| { let s = CString::new(arg).unwrap(); llvm_args.push(s.as_ptr()); llvm_c_strs.push(s); </del> <ins> // This adds the given argument to LLVM. Unless `force` is true // user specified arguments are *not* overridden. let mut add = |arg: &str, force: bool| { if force || !user_specified_args.contains(llvm_arg_to_arg_name(arg)) { let s = CString::new(arg).unwrap(); llvm_args.push(s.as_ptr()); llvm_c_strs.push(s); } </ins> }; <del> add("rustc"); // fake program name if sess.time_llvm_passes() { add("-time-passes"); } if sess.print_llvm_passes() { add("-debug-pass=Structure"); } if sess.opts.debugging_opts.disable_instrumentation_preinliner { add("-disable-preinline"); } </del> <ins> add("rustc", true); // fake program name if sess.time_llvm_passes() { add("-time-passes", false); } if sess.print_llvm_passes() { add("-debug-pass=Structure", false); } </ins> if sess.opts.debugging_opts.generate_arange_section { <del> add("-generate-arange-section"); </del> <ins> add("-generate-arange-section", false); </ins> } if get_major_version() >= 8 { match sess.opts.debugging_opts.merge_functions

MergeFunctions::Disabled | MergeFunctions::Trampolines => {} MergeFunctions::Aliases => { <del> add("-mergefunc-use-aliases"); </del> <ins> add("-mergefunc-use-aliases", false); </ins> } } } if sess.target.target.target_os == "emscripten" && sess.panic_strategy() == PanicStrategy::Unwind { <del> add("-enable-emscripten-cxx-exceptions"); </del> <ins> add("-enable-emscripten-cxx-exceptions", false); </ins> } // HACK(eddyb) LLVM inserts `llvm.assume` calls to preserve align attributes // during inlining. Unfortunately these may block other optimizations. <del> add("-preserve-alignment-assumptions-during-inlining=false"); </del> <ins> add("-preserve-alignment-assumptions-during-inlining=false", false); </ins> for arg in &sess.opts.cg.llvm_args { <del> add(&(*arg)); </del> <ins> add(&(*arg), true); </ins> } }

"extra arguments to prepend to the linker invocation (space separated)"), profile: bool = (false, parse_bool, [TRACKED], "insert profiling code"), <del> disable_instrumentation_preinliner: bool = (false, parse_bool, [TRACKED], "Disable the instrumentation pre-inliner, useful for profiling / PGO."), </del> relro_level: Option<RelroLevel> = (None, parse_relro_level, [TRACKED], "choose which RELRO level to use"), nll_facts: bool = (false, parse_bool, [UNTRACKED],