1
Fork 0

Rollup merge of #101997 - cuviper:drop-legacy-pm, r=nikic

Remove support for legacy PM

This removes support for optimizing with LLVM's legacy pass manager, as well as the unstable `-Znew-llvm-pass-manager` option. We have been defaulting to the new PM since LLVM 13 (except for s390x that waited for 14), and LLVM 15 removed support altogether. The only place we still use the legacy PM is for writing the output file, just like `llc` does.

cc #74705
r? ``@nikic``
This commit is contained in:
fee1-dead 2022-09-25 22:06:38 +08:00 committed by GitHub
commit 07467c5308
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 28 additions and 685 deletions

View file

@ -11,7 +11,6 @@ doctest = false
bitflags = "1.0"
cstr = "0.2"
libc = "0.2"
libloading = "0.7.1"
measureme = "10.0.0"
object = { version = "0.29.0", default-features = false, features = ["std", "read_core", "archive", "coff", "elf", "macho", "pe"] }
tracing = "0.1"

View file

@ -1,8 +1,6 @@
use crate::back::write::{
self, save_temp_bitcode, to_llvm_opt_settings, with_llvm_pmb, DiagnosticHandlers,
};
use crate::llvm::{self, build_string, False, True};
use crate::{llvm_util, LlvmCodegenBackend, ModuleLlvm};
use crate::back::write::{self, save_temp_bitcode, DiagnosticHandlers};
use crate::llvm::{self, build_string};
use crate::{LlvmCodegenBackend, ModuleLlvm};
use object::read::archive::ArchiveFile;
use rustc_codegen_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule, ThinShared};
use rustc_codegen_ssa::back::symbol_export;
@ -597,61 +595,9 @@ pub(crate) fn run_pass_manager(
1,
);
}
if llvm_util::should_use_new_llvm_pass_manager(
&config.new_llvm_pass_manager,
&cgcx.target_arch,
) {
let opt_stage = if thin { llvm::OptStage::ThinLTO } else { llvm::OptStage::FatLTO };
let opt_level = config.opt_level.unwrap_or(config::OptLevel::No);
write::optimize_with_new_llvm_pass_manager(
cgcx,
diag_handler,
module,
config,
opt_level,
opt_stage,
)?;
debug!("lto done");
return Ok(());
}
let pm = llvm::LLVMCreatePassManager();
llvm::LLVMAddAnalysisPasses(module.module_llvm.tm, pm);
if config.verify_llvm_ir {
let pass = llvm::LLVMRustFindAndCreatePass("verify\0".as_ptr().cast());
llvm::LLVMRustAddPass(pm, pass.unwrap());
}
let opt_level = config
.opt_level
.map(|x| to_llvm_opt_settings(x).0)
.unwrap_or(llvm::CodeGenOptLevel::None);
with_llvm_pmb(module.module_llvm.llmod(), config, opt_level, false, &mut |b| {
if thin {
llvm::LLVMRustPassManagerBuilderPopulateThinLTOPassManager(b, pm);
} else {
llvm::LLVMRustPassManagerBuilderPopulateLTOPassManager(
b, pm, /* Internalize = */ False, /* RunInliner = */ True,
);
}
});
// We always generate bitcode through ThinLTOBuffers,
// which do not support anonymous globals
if config.bitcode_needed() {
let pass = llvm::LLVMRustFindAndCreatePass("name-anon-globals\0".as_ptr().cast());
llvm::LLVMRustAddPass(pm, pass.unwrap());
}
if config.verify_llvm_ir {
let pass = llvm::LLVMRustFindAndCreatePass("verify\0".as_ptr().cast());
llvm::LLVMRustAddPass(pm, pass.unwrap());
}
llvm::LLVMRunPassManager(pm, module.module_llvm.llmod());
llvm::LLVMDisposePassManager(pm);
let opt_stage = if thin { llvm::OptStage::ThinLTO } else { llvm::OptStage::FatLTO };
let opt_level = config.opt_level.unwrap_or(config::OptLevel::No);
write::llvm_optimize(cgcx, diag_handler, module, config, opt_level, opt_stage)?;
}
debug!("lto done");
Ok(())

View file

@ -21,7 +21,6 @@ use rustc_data_structures::profiling::SelfProfilerRef;
use rustc_data_structures::small_c_str::SmallCStr;
use rustc_errors::{FatalError, Handler, Level};
use rustc_fs_util::{link_or_copy, path_to_c_string};
use rustc_middle::bug;
use rustc_middle::ty::TyCtxt;
use rustc_session::config::{self, Lto, OutputType, Passes, SplitDwarfKind, SwitchWithOptPath};
use rustc_session::Session;
@ -417,7 +416,7 @@ fn get_instr_profile_output_path(config: &ModuleConfig) -> Option<CString> {
}
}
pub(crate) unsafe fn optimize_with_new_llvm_pass_manager(
pub(crate) unsafe fn llvm_optimize(
cgcx: &CodegenContext<LlvmCodegenBackend>,
diag_handler: &Handler,
module: &ModuleCodegen<ModuleLlvm>,
@ -465,7 +464,7 @@ pub(crate) unsafe fn optimize_with_new_llvm_pass_manager(
// FIXME: NewPM doesn't provide a facility to pass custom InlineParams.
// We would have to add upstream support for this first, before we can support
// config.inline_threshold and our more aggressive default thresholds.
let result = llvm::LLVMRustOptimizeWithNewPassManager(
let result = llvm::LLVMRustOptimize(
module.module_llvm.llmod(),
&*module.module_llvm.tm,
to_pass_builder_opt_level(opt_level),
@ -509,18 +508,11 @@ pub(crate) unsafe fn optimize(
let llmod = module.module_llvm.llmod();
let llcx = &*module.module_llvm.llcx;
let tm = &*module.module_llvm.tm;
let _handlers = DiagnosticHandlers::new(cgcx, diag_handler, llcx);
let module_name = module.name.clone();
let module_name = Some(&module_name[..]);
if let Some(false) = config.new_llvm_pass_manager && llvm_util::get_version() >= (15, 0, 0) {
diag_handler.warn(
"ignoring `-Z new-llvm-pass-manager=no`, which is no longer supported with LLVM 15",
);
}
if config.emit_no_opt_bc {
let out = cgcx.output_filenames.temp_path_ext("no-opt.bc", module_name);
let out = path_to_c_string(&out);
@ -528,184 +520,17 @@ pub(crate) unsafe fn optimize(
}
if let Some(opt_level) = config.opt_level {
if llvm_util::should_use_new_llvm_pass_manager(
&config.new_llvm_pass_manager,
&cgcx.target_arch,
) {
let opt_stage = match cgcx.lto {
Lto::Fat => llvm::OptStage::PreLinkFatLTO,
Lto::Thin | Lto::ThinLocal => llvm::OptStage::PreLinkThinLTO,
_ if cgcx.opts.cg.linker_plugin_lto.enabled() => llvm::OptStage::PreLinkThinLTO,
_ => llvm::OptStage::PreLinkNoLTO,
};
return optimize_with_new_llvm_pass_manager(
cgcx,
diag_handler,
module,
config,
opt_level,
opt_stage,
);
}
if cgcx.prof.llvm_recording_enabled() {
diag_handler
.warn("`-Z self-profile-events = llvm` requires `-Z new-llvm-pass-manager`");
}
// Create the two optimizing pass managers. These mirror what clang
// does, and are by populated by LLVM's default PassManagerBuilder.
// Each manager has a different set of passes, but they also share
// some common passes.
let fpm = llvm::LLVMCreateFunctionPassManagerForModule(llmod);
let mpm = llvm::LLVMCreatePassManager();
{
let find_pass = |pass_name: &str| {
let pass_name = SmallCStr::new(pass_name);
llvm::LLVMRustFindAndCreatePass(pass_name.as_ptr())
};
if config.verify_llvm_ir {
// Verification should run as the very first pass.
llvm::LLVMRustAddPass(fpm, find_pass("verify").unwrap());
}
let mut extra_passes = Vec::new();
let mut have_name_anon_globals_pass = false;
for pass_name in &config.passes {
if pass_name == "lint" {
// Linting should also be performed early, directly on the generated IR.
llvm::LLVMRustAddPass(fpm, find_pass("lint").unwrap());
continue;
}
if let Some(pass) = find_pass(pass_name) {
extra_passes.push(pass);
} else {
diag_handler.warn(&format!("unknown pass `{}`, ignoring", pass_name));
}
if pass_name == "name-anon-globals" {
have_name_anon_globals_pass = true;
}
}
// Instrumentation must be inserted before optimization,
// otherwise LLVM may optimize some functions away which
// breaks llvm-cov.
//
// This mirrors what Clang does in lib/CodeGen/BackendUtil.cpp.
if config.instrument_gcov {
llvm::LLVMRustAddPass(mpm, find_pass("insert-gcov-profiling").unwrap());
}
if config.instrument_coverage {
llvm::LLVMRustAddPass(mpm, find_pass("instrprof").unwrap());
}
if config.debug_info_for_profiling {
llvm::LLVMRustAddPass(mpm, find_pass("add-discriminators").unwrap());
}
add_sanitizer_passes(config, &mut extra_passes);
// Some options cause LLVM bitcode to be emitted, which uses ThinLTOBuffers, so we need
// to make sure we run LLVM's NameAnonGlobals pass when emitting bitcode; otherwise
// we'll get errors in LLVM.
let using_thin_buffers = config.bitcode_needed();
if !config.no_prepopulate_passes {
llvm::LLVMAddAnalysisPasses(tm, fpm);
llvm::LLVMAddAnalysisPasses(tm, mpm);
let opt_level = to_llvm_opt_settings(opt_level).0;
let prepare_for_thin_lto = cgcx.lto == Lto::Thin
|| cgcx.lto == Lto::ThinLocal
|| (cgcx.lto != Lto::Fat && cgcx.opts.cg.linker_plugin_lto.enabled());
with_llvm_pmb(llmod, config, opt_level, prepare_for_thin_lto, &mut |b| {
llvm::LLVMRustAddLastExtensionPasses(
b,
extra_passes.as_ptr(),
extra_passes.len() as size_t,
);
llvm::LLVMRustPassManagerBuilderPopulateFunctionPassManager(b, fpm);
llvm::LLVMRustPassManagerBuilderPopulateModulePassManager(b, mpm);
});
have_name_anon_globals_pass = have_name_anon_globals_pass || prepare_for_thin_lto;
if using_thin_buffers && !prepare_for_thin_lto {
llvm::LLVMRustAddPass(mpm, find_pass("name-anon-globals").unwrap());
have_name_anon_globals_pass = true;
}
} else {
// If we don't use the standard pipeline, directly populate the MPM
// with the extra passes.
for pass in extra_passes {
llvm::LLVMRustAddPass(mpm, pass);
}
}
if using_thin_buffers && !have_name_anon_globals_pass {
// As described above, this will probably cause an error in LLVM
if config.no_prepopulate_passes {
diag_handler.err(
"The current compilation is going to use thin LTO buffers \
without running LLVM's NameAnonGlobals pass. \
This will likely cause errors in LLVM. Consider adding \
-C passes=name-anon-globals to the compiler command line.",
);
} else {
bug!(
"We are using thin LTO buffers without running the NameAnonGlobals pass. \
This will likely cause errors in LLVM and should never happen."
);
}
}
}
diag_handler.abort_if_errors();
// Finally, run the actual optimization passes
{
let _timer = cgcx.prof.extra_verbose_generic_activity(
"LLVM_module_optimize_function_passes",
&*module.name,
);
llvm::LLVMRustRunFunctionPassManager(fpm, llmod);
}
{
let _timer = cgcx.prof.extra_verbose_generic_activity(
"LLVM_module_optimize_module_passes",
&*module.name,
);
llvm::LLVMRunPassManager(mpm, llmod);
}
// Deallocate managers that we're now done with
llvm::LLVMDisposePassManager(fpm);
llvm::LLVMDisposePassManager(mpm);
let opt_stage = match cgcx.lto {
Lto::Fat => llvm::OptStage::PreLinkFatLTO,
Lto::Thin | Lto::ThinLocal => llvm::OptStage::PreLinkThinLTO,
_ if cgcx.opts.cg.linker_plugin_lto.enabled() => llvm::OptStage::PreLinkThinLTO,
_ => llvm::OptStage::PreLinkNoLTO,
};
return llvm_optimize(cgcx, diag_handler, module, config, opt_level, opt_stage);
}
Ok(())
}
unsafe fn add_sanitizer_passes(config: &ModuleConfig, passes: &mut Vec<&'static mut llvm::Pass>) {
if config.sanitizer.contains(SanitizerSet::ADDRESS) {
let recover = config.sanitizer_recover.contains(SanitizerSet::ADDRESS);
passes.push(llvm::LLVMRustCreateAddressSanitizerFunctionPass(recover));
passes.push(llvm::LLVMRustCreateModuleAddressSanitizerPass(recover));
}
if config.sanitizer.contains(SanitizerSet::MEMORY) {
let track_origins = config.sanitizer_memory_track_origins as c_int;
let recover = config.sanitizer_recover.contains(SanitizerSet::MEMORY);
passes.push(llvm::LLVMRustCreateMemorySanitizerPass(track_origins, recover));
}
if config.sanitizer.contains(SanitizerSet::THREAD) {
passes.push(llvm::LLVMRustCreateThreadSanitizerPass());
}
if config.sanitizer.contains(SanitizerSet::HWADDRESS) {
let recover = config.sanitizer_recover.contains(SanitizerSet::HWADDRESS);
passes.push(llvm::LLVMRustCreateHWAddressSanitizerPass(recover));
}
}
pub(crate) fn link(
cgcx: &CodegenContext<LlvmCodegenBackend>,
diag_handler: &Handler,
@ -1072,72 +897,6 @@ unsafe fn embed_bitcode(
}
}
pub unsafe fn with_llvm_pmb(
llmod: &llvm::Module,
config: &ModuleConfig,
opt_level: llvm::CodeGenOptLevel,
prepare_for_thin_lto: bool,
f: &mut dyn FnMut(&llvm::PassManagerBuilder),
) {
use std::ptr;
// Create the PassManagerBuilder for LLVM. We configure it with
// reasonable defaults and prepare it to actually populate the pass
// manager.
let builder = llvm::LLVMRustPassManagerBuilderCreate();
let opt_size = config.opt_size.map_or(llvm::CodeGenOptSizeNone, |x| to_llvm_opt_settings(x).1);
let inline_threshold = config.inline_threshold;
let pgo_gen_path = get_pgo_gen_path(config);
let pgo_use_path = get_pgo_use_path(config);
let pgo_sample_use_path = get_pgo_sample_use_path(config);
llvm::LLVMRustConfigurePassManagerBuilder(
builder,
opt_level,
config.merge_functions,
config.vectorize_slp,
config.vectorize_loop,
prepare_for_thin_lto,
pgo_gen_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
pgo_use_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
pgo_sample_use_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
opt_size as c_int,
);
llvm::LLVMRustAddBuilderLibraryInfo(builder, llmod, config.no_builtins);
// Here we match what clang does (kinda). For O0 we only inline
// always-inline functions (but don't add lifetime intrinsics), at O1 we
// inline with lifetime intrinsics, and O2+ we add an inliner with a
// thresholds copied from clang.
match (opt_level, opt_size, inline_threshold) {
(.., Some(t)) => {
llvm::LLVMRustPassManagerBuilderUseInlinerWithThreshold(builder, t);
}
(llvm::CodeGenOptLevel::Aggressive, ..) => {
llvm::LLVMRustPassManagerBuilderUseInlinerWithThreshold(builder, 275);
}
(_, llvm::CodeGenOptSizeDefault, _) => {
llvm::LLVMRustPassManagerBuilderUseInlinerWithThreshold(builder, 75);
}
(_, llvm::CodeGenOptSizeAggressive, _) => {
llvm::LLVMRustPassManagerBuilderUseInlinerWithThreshold(builder, 25);
}
(llvm::CodeGenOptLevel::None, ..) => {
llvm::LLVMRustAddAlwaysInlinePass(builder, config.emit_lifetime_markers);
}
(llvm::CodeGenOptLevel::Less, ..) => {
llvm::LLVMRustAddAlwaysInlinePass(builder, config.emit_lifetime_markers);
}
(llvm::CodeGenOptLevel::Default, ..) => {
llvm::LLVMRustPassManagerBuilderUseInlinerWithThreshold(builder, 225);
}
}
f(builder);
llvm::LLVMRustPassManagerBuilderDispose(builder);
}
// Create a `__imp_<symbol> = &symbol` global for every public static `symbol`.
// This is required to satisfy `dllimport` references to static data in .rlibs
// when using MSVC linker. We do this only for data, as linker can fix up

View file

@ -1792,18 +1792,9 @@ extern "C" {
/// Writes a module to the specified path. Returns 0 on success.
pub fn LLVMWriteBitcodeToFile(M: &Module, Path: *const c_char) -> c_int;
/// Creates a pass manager.
/// Creates a legacy pass manager -- only used for final codegen.
pub fn LLVMCreatePassManager<'a>() -> &'a mut PassManager<'a>;
/// Creates a function-by-function pass manager
pub fn LLVMCreateFunctionPassManagerForModule(M: &Module) -> &mut PassManager<'_>;
/// Disposes a pass manager.
pub fn LLVMDisposePassManager<'a>(PM: &'a mut PassManager<'a>);
/// Runs a pass manager on a module.
pub fn LLVMRunPassManager<'a>(PM: &PassManager<'a>, M: &'a Module) -> Bool;
pub fn LLVMInitializePasses();
pub fn LLVMTimeTraceProfilerInitialize();
@ -1814,32 +1805,6 @@ extern "C" {
pub fn LLVMAddAnalysisPasses<'a>(T: &'a TargetMachine, PM: &PassManager<'a>);
pub fn LLVMRustPassManagerBuilderCreate() -> &'static mut PassManagerBuilder;
pub fn LLVMRustPassManagerBuilderDispose(PMB: &'static mut PassManagerBuilder);
pub fn LLVMRustPassManagerBuilderUseInlinerWithThreshold(
PMB: &PassManagerBuilder,
threshold: c_uint,
);
pub fn LLVMRustPassManagerBuilderPopulateModulePassManager(
PMB: &PassManagerBuilder,
PM: &PassManager<'_>,
);
pub fn LLVMRustPassManagerBuilderPopulateFunctionPassManager(
PMB: &PassManagerBuilder,
PM: &PassManager<'_>,
);
pub fn LLVMRustPassManagerBuilderPopulateLTOPassManager(
PMB: &PassManagerBuilder,
PM: &PassManager<'_>,
Internalize: Bool,
RunInliner: Bool,
);
pub fn LLVMRustPassManagerBuilderPopulateThinLTOPassManager(
PMB: &PassManagerBuilder,
PM: &PassManager<'_>,
);
pub fn LLVMGetHostCPUFeatures() -> *mut c_char;
pub fn LLVMDisposeMessage(message: *mut c_char);
@ -2244,22 +2209,6 @@ extern "C" {
pub fn LLVMIsAConstantInt(value_ref: &Value) -> Option<&ConstantInt>;
pub fn LLVMRustFindAndCreatePass(Pass: *const c_char) -> Option<&'static mut Pass>;
pub fn LLVMRustCreateAddressSanitizerFunctionPass(Recover: bool) -> &'static mut Pass;
pub fn LLVMRustCreateModuleAddressSanitizerPass(Recover: bool) -> &'static mut Pass;
pub fn LLVMRustCreateMemorySanitizerPass(
TrackOrigins: c_int,
Recover: bool,
) -> &'static mut Pass;
pub fn LLVMRustCreateThreadSanitizerPass() -> &'static mut Pass;
pub fn LLVMRustCreateHWAddressSanitizerPass(Recover: bool) -> &'static mut Pass;
pub fn LLVMRustAddPass(PM: &PassManager<'_>, Pass: &'static mut Pass);
pub fn LLVMRustAddLastExtensionPasses(
PMB: &PassManagerBuilder,
Passes: *const &'static mut Pass,
NumPasses: size_t,
);
pub fn LLVMRustHasFeature(T: &TargetMachine, s: *const c_char) -> bool;
pub fn LLVMRustPrintTargetCPUs(T: &TargetMachine);
@ -2293,29 +2242,11 @@ extern "C" {
SplitDwarfFile: *const c_char,
) -> Option<&'static mut TargetMachine>;
pub fn LLVMRustDisposeTargetMachine(T: &'static mut TargetMachine);
pub fn LLVMRustAddBuilderLibraryInfo<'a>(
PMB: &'a PassManagerBuilder,
M: &'a Module,
DisableSimplifyLibCalls: bool,
);
pub fn LLVMRustConfigurePassManagerBuilder(
PMB: &PassManagerBuilder,
OptLevel: CodeGenOptLevel,
MergeFunctions: bool,
SLPVectorize: bool,
LoopVectorize: bool,
PrepareForThinLTO: bool,
PGOGenPath: *const c_char,
PGOUsePath: *const c_char,
PGOSampleUsePath: *const c_char,
SizeLevel: c_int,
);
pub fn LLVMRustAddLibraryInfo<'a>(
PM: &PassManager<'a>,
M: &'a Module,
DisableSimplifyLibCalls: bool,
);
pub fn LLVMRustRunFunctionPassManager<'a>(PM: &PassManager<'a>, M: &'a Module);
pub fn LLVMRustWriteOutputFile<'a>(
T: &'a TargetMachine,
PM: &PassManager<'a>,
@ -2324,7 +2255,7 @@ extern "C" {
DwoOutput: *const c_char,
FileType: FileType,
) -> LLVMRustResult;
pub fn LLVMRustOptimizeWithNewPassManager<'a>(
pub fn LLVMRustOptimize<'a>(
M: &'a Module,
TM: &'a TargetMachine,
OptLevel: PassBuilderOptLevel,
@ -2362,7 +2293,6 @@ extern "C" {
pub fn LLVMRustSetLLVMOptions(Argc: c_int, Argv: *const *const c_char);
pub fn LLVMRustPrintPasses();
pub fn LLVMRustSetNormalizedTarget(M: &Module, triple: *const c_char);
pub fn LLVMRustAddAlwaysInlinePass(P: &PassManagerBuilder, AddLifetimes: bool);
pub fn LLVMRustRunRestrictionPass(M: &Module, syms: *const *const c_char, len: size_t);
pub fn LLVMRustOpenArchive(path: *const c_char) -> Option<&'static mut Archive>;

View file

@ -1,7 +1,6 @@
use crate::back::write::create_informational_target_machine;
use crate::{llvm, llvm_util};
use crate::llvm;
use libc::c_int;
use libloading::Library;
use rustc_codegen_ssa::target_features::{
supported_target_features, tied_target_features, RUSTC_SPECIFIC_FEATURES,
};
@ -16,7 +15,6 @@ use rustc_target::spec::{MergeFunctions, PanicStrategy};
use smallvec::{smallvec, SmallVec};
use std::ffi::{CStr, CString};
use std::mem;
use std::path::Path;
use std::ptr;
use std::slice;
@ -120,22 +118,6 @@ unsafe fn configure_llvm(sess: &Session) {
llvm::LLVMInitializePasses();
// Use the legacy plugin registration if we don't use the new pass manager
if !should_use_new_llvm_pass_manager(
&sess.opts.unstable_opts.new_llvm_pass_manager,
&sess.target.arch,
) {
// Register LLVM plugins by loading them into the compiler process.
for plugin in &sess.opts.unstable_opts.llvm_plugins {
let lib = Library::new(plugin).unwrap_or_else(|e| bug!("couldn't load plugin: {}", e));
debug!("LLVM plugin loaded successfully {:?} ({})", lib, plugin);
// Intentionally leak the dynamic library. We can't ever unload it
// since the library can make things that will live arbitrarily long.
mem::forget(lib);
}
}
rustc_llvm::initialize_available_targets();
llvm::LLVMRustSetLLVMOptions(llvm_args.len() as c_int, llvm_args.as_ptr());
@ -539,19 +521,3 @@ pub fn tune_cpu(sess: &Session) -> Option<&str> {
let name = sess.opts.unstable_opts.tune_cpu.as_ref()?;
Some(handle_native(name))
}
pub(crate) fn should_use_new_llvm_pass_manager(user_opt: &Option<bool>, target_arch: &str) -> bool {
// The new pass manager is enabled by default for LLVM >= 13.
// This matches Clang, which also enables it since Clang 13.
// Since LLVM 15, the legacy pass manager is no longer supported.
if llvm_util::get_version() >= (15, 0, 0) {
return true;
}
// There are some perf issues with the new pass manager when targeting
// s390x with LLVM 13, so enable the new pass manager only with LLVM 14.
// See https://github.com/rust-lang/rust/issues/89609.
let min_version = if target_arch == "s390x" { 14 } else { 13 };
user_opt.unwrap_or_else(|| llvm_util::get_version() >= (min_version, 0, 0))
}