From 6810f13f1d5de7d00e380b74c303a72b5bec5dbe Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 24 Jun 2026 21:29:11 +0200 Subject: [PATCH] add `-Zforce-intrinsic-fallback` flag --- .../rustc_codegen_ssa/src/mir/intrinsic.rs | 11 ++++++ compiler/rustc_monomorphize/src/collector.rs | 11 +++++- compiler/rustc_session/src/options.rs | 3 ++ .../force-intrinsic-fallback.md | 6 +++ .../codegen-llvm/force-intrinsic-fallback.rs | 38 +++++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md create mode 100644 tests/codegen-llvm/force-intrinsic-fallback.rs diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index 99546bea65959..413cb5f2c860a 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -63,6 +63,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { result_place: Option>, source_info: SourceInfo, ) -> IntrinsicResult<'tcx, Bx::Value> { + // When `-Zforce-intrinsic-fallback` is enabled, always use the fallback body if it exists, + if bx.tcx().sess.opts.unstable_opts.force_intrinsic_fallback + && let Some(def) = bx.tcx().intrinsic(instance.def_id()) + && !def.must_be_overridden + { + return IntrinsicResult::Fallback(ty::Instance::new_raw( + instance.def_id(), + instance.args, + )); + } + let span = source_info.span; let name = bx.tcx().item_name(instance.def_id()); diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index ceb044c102f69..bd608583d818c 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -995,11 +995,18 @@ fn visit_instance_use<'tcx>( output.push(create_fn_mono_item(tcx, panic_instance, source)); } } else if !intrinsic.must_be_overridden - && !tcx.sess.replaced_intrinsics.contains(&intrinsic.name) + && (tcx.sess.opts.unstable_opts.force_intrinsic_fallback + || !tcx.sess.replaced_intrinsics.contains(&intrinsic.name)) { // Codegen the fallback body of intrinsics with fallback bodies. // We have to skip this otherwise as there's no body to codegen. - // We also skip intrinsics the backend handles, to reduce monomorphizations. + // + // We also skip `replaced_intrinsics` which are always replaced by the backend and hence + // monomorphizing the fallback body would be pointless. + // + // However, when -Zforce-intrinsic-fallback is set (e.g. to test the fallback + // implementations) we ignore the optimization hint and do monomorphize + // the fallback body. let instance = ty::Instance::new_raw(instance.def_id(), instance.args); if tcx.should_codegen_locally(instance) { output.push(create_fn_mono_item(tcx, instance, source)); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 4e5b38d34b824..493cf801a2be1 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2417,6 +2417,9 @@ options! { fmt_debug: FmtDebug = (FmtDebug::Full, parse_fmt_debug, [TRACKED], "how detailed `#[derive(Debug)]` should be. `full` prints types recursively, \ `shallow` prints only type names, `none` prints nothing and disables `{:?}`. (default: `full`)"), + force_intrinsic_fallback: bool = (false, parse_bool, [TRACKED], + "always use the fallback body of an intrinsic, if it has one, instead of lowering \ + the intrinsic in the codegen backend (default: no)."), force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], "force all crates to be `rustc_private` unstable (default: no)"), function_return: FunctionReturn = (FunctionReturn::default(), parse_function_return, [TRACKED], diff --git a/src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md b/src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md new file mode 100644 index 0000000000000..41f6ae3237908 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md @@ -0,0 +1,6 @@ +## `force-intrinsic-fallback` + +Configures codegen to always use the fallback body of an intrinsic, if it has one, +instead of lowering the intrinsic in the codegen backend. + +This is useful for testing the fallback implementation. diff --git a/tests/codegen-llvm/force-intrinsic-fallback.rs b/tests/codegen-llvm/force-intrinsic-fallback.rs new file mode 100644 index 0000000000000..1fb80b4e83f12 --- /dev/null +++ b/tests/codegen-llvm/force-intrinsic-fallback.rs @@ -0,0 +1,38 @@ +//@ compile-flags: --crate-type=lib -C no-prepopulate-passes -Copt-level=3 +// +//@ revisions: NORMAL FALLBACK +//@ [FALLBACK] compile-flags: -Zforce-intrinsic-fallback +#![feature(core_intrinsics, funnel_shifts)] + +// Check the effect of `-Zforce-intrinsic-fallback`. +// +// Without the flag, the dedicated backend lowering of the intrinsic is used. +// With the flag, the fallback body is called instead. + +#[no_mangle] +pub fn call_minimumf32(x: f32, y: f32) -> f32 { + // CHECK-LABEL: @call_minimumf32 + + // NORMAL: call float @llvm.minimum.f32 + // NORMAL-NOT: minimumf32 + + // FALLBACK-NOT: @llvm.minimum + // FALLBACK: call {{.*}}minimumf32 + core::intrinsics::minimumf32(x, y) +} + +// Codegen backends can return a list of `replaced_intrinsics`, for which codegen of the fallback is +// normally skipped. `unchecked_funnel_shl` is in that list for the LLVM backend, so we test it here +// to ensure that with the flag enabled the fallback body is actually code generated and called. + +#[no_mangle] +pub fn call_funnel_shl(a: u32, b: u32, shift: u32) -> u32 { + // CHECK-LABEL: @call_funnel_shl + + // NORMAL: call i32 @llvm.fshl.i32 + // NORMAL-NOT: funnel_shl + + // FALLBACK-NOT: @llvm.fshl + // FALLBACK: call {{.*}}funnel_shl + unsafe { core::intrinsics::unchecked_funnel_shl(a, b, shift) } +}