Change:
Likely caused by . cc CI for compilerbuiltins is failing with: The cause seems to be from a combination of factors: compiler-builtins cannot reference any symbols in libcore. It can only call functions marked with , since those are codegen'ed into is marked as , which breaks this. The call is not eliminated because is resolved late in the codegen backend, after any MIR optimizations. This failure happens in debug builds, so LLVM isn't eliminating the branch either.
can probably be avoided by just calling the intrinsic directly. You mentioned is only called indirectly from compiler-builtins; do you know what the entry point into libcore is?
I've verified that this is fixed by my latest iteration of , which is neatly part of my plan for these checks:
Hm didn't notice this because it was not marked as a regression. Assigning P-high () after reading (and following). label -I-prioritize +P-high
It's a nightly-to-nightly regression, I don't think we have a label for that? Nightly features can break after all. It's just that compiler-builtins relies on an incredibly fragile undocumented contract that is not checked by rustc CI.
Thanks for the additional context. IIUC in this case it's nothing risking to reach beta if it's shielded by a nightly-only flag. label -I-prioritize -P-high +P-medium
See
I checked this with the latest nightly: it seems new failures are due to and : However I would have expected to address this since all of these are gated around code like this:
Do you know why the linker errors only happen on Windows?
It now happens on Linux as well.
This works fine on my Linux machine:
The link failure doesn't happen, but that's because (I think) the function is pruned by the linker. If you look at the rlib with then you will see references to panic functions in core. If I understand correctly then these functions should never have reached codegen because they are unreachable.
Hmm actually now that I'm re-reading it only applies to basic blocks within a function and doesn't affect function-level reachability analysis. Perhaps the failure was only observed on msvc because it uses a different linker (MSVC )?
Right. That PR only avoids lowering the calls, it doesn't touch reachability analysis. The previous PR that I based this on did change reachability analysis and MIR collection, but that approach means that some kind of errors under and would not be reported. Which is bad.
Is there a way to print the linker invocation when I'm building for the MSVC target? As far as I can tell from the code in cgssa: which is called from here: we are already asking MSVC to eliminate the failing-to-link code. So either I'm reading this wrong (quite possible) or MSVC's linker optimization here is quite poor, or this is an MSVC linker bug.
Maybe try something like:
I'm doing a new CI run in , this time specifically ignoring whether the symbols are referenced from the .rlib and instead only checking whether linking succeeds.
Still failing on MSVC:
I disabled fast-fast in CI so we can get a clearer picture of the failures: It seems to also fail on windows-gnu targets:
We're passing the linker flags in this compiler-builtins configuration. Even if I make that we still have these undefined references. So as far as I can tell, we are indeed asking the linker to do this optimization and it is not doing what we want. Some of the undefined references here double-checking situations that ought to be impossible. We could simply delete these checks: I would send a PR like that, except that does not remove all the linker errors. On MSVC I still see one error... except that if I also add it goes away. I do not yet understand why.
Actually, it looks like with or higher, we don't need any compiler changes to make the linker errors go away. I suppose the next thing to do is dig into what's different about the CGUs between 88 and 89.
Presumably that would have the effect of splitting it up into more object files which causes the linker to simply drop ones that do not provide a necessary symbol.
Trying to rely on MSVC linker to not pull in external symbols via is not going to work - the linker tries to resolve external symbols first, then it performs optimizations. Messing with the code gen units means that the external symbol for panics is landing in an obj that happens to not be pulled in, hence MSVC doesn't complain about it being missing. This is an incredibly fragile solution... If we can't rely on libcore, would it be possible to implement a "mini panic" in compiler-builtins instead?
I'm not proposing any solution.
The undefined symbols in this case are only referenced by functions that are never called. So that sounds right.
I think I have a solution. I'll put up a PR soon...
This is my idea:
This was marked as fixed by , but that PR doesn't change the library or how code is generated, so it can't by itself change anything about the linker errors, can it?
It does fix the issue by replacing all "external" calls with , which means that no longer depends on libcore at link-time.
Ah, makes sense, thanks.

Discussion:
} } <ins> pub fn codegen_block_as_unreachable(&mut self, bb: mir::BasicBlock) { let llbb = match self.try_llbb(bb) { Some(llbb) => llbb, None => return, }; let bx = &mut Bx::build(self.cx, llbb); debug!("codegen_block_as_unreachable({:?})", bb); bx.unreachable(); } </ins> fn codegen_terminator( &mut self, bx: &mut Bx,

// Apply debuginfo to the newly allocated locals. fx.debug_introduce_locals(&mut start_bx); <ins> let reachable_blocks = mir.reachable_blocks_in_mono(cx.tcx(), instance); </ins> // The builders will be created separately for each basic block at `codegen_block`. // So drop the builder of `start_llbb` to avoid having two at the same time. drop(start_bx); // Codegen the body of each block using reverse postorder for (bb, _) in traversal::reverse_postorder(mir) { <del> fx.codegen_block(bb); </del> <ins> if reachable_blocks.contains(bb) { fx.codegen_block(bb); } else { // This may have references to things we didn't monomorphize, so we // don't actually codegen the body. We still create the block so // terminators in other blocks can reference it without worry. fx.codegen_block_as_unreachable(bb); } </ins> } }

use crate::ty::print::{FmtPrinter, Printer}; use crate::ty::visit::TypeVisitableExt; use crate::ty::{self, List, Ty, TyCtxt}; <del> use crate::ty::{AdtDef, InstanceDef, UserTypeAnnotationIndex}; </del> <ins> use crate::ty::{AdtDef, Instance, InstanceDef, UserTypeAnnotationIndex}; </ins> use crate::ty::{GenericArg, GenericArgsRef}; use rustc_data_structures::captures::Captures;

use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::graph::dominators::Dominators; <ins> use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_index::bit_set::BitSet; </ins> use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_serialize::{Decodable, Encodable}; use rustc_span::symbol::Symbol;

self.injection_phase.is_some() } <ins> /// Finds which basic blocks are actually reachable for a specific /// monomorphization of this body. /// /// This is allowed to have false positives; just because this says a block /// is reachable doesn't mean that's necessarily true. It's thus always /// legal for this to return a filled set. /// /// Regardless, the [`BitSet::domain_size`] of the returned set will always /// exactly match the number of blocks in the body so that `contains` /// checks can be done without worrying about panicking. /// /// This is mostly useful because it lets us skip lowering the `false` side /// of `if <T as Trait>::CONST`, as well as `intrinsics::debug_assertions`. pub fn reachable_blocks_in_mono( &self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, ) -> BitSet<BasicBlock> { let mut set = BitSet::new_empty(self.basic_blocks.len()); self.reachable_blocks_in_mono_from(tcx, instance, &mut set, START_BLOCK); set } fn reachable_blocks_in_mono_from( &self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, set: &mut BitSet<BasicBlock>, bb: BasicBlock, ) { if !set.insert(bb) { return; } let data = &self.basic_blocks[bb]; if let Some((bits, targets)) = Self::try_const_mono_switchint(tcx, instance, data) { let target = targets.target_for_value(bits); ensure_sufficient_stack(|| { self.reachable_blocks_in_mono_from(tcx, instance, set, target) }); return; } for target in data.terminator().successors() { ensure_sufficient_stack(|| { self.reachable_blocks_in_mono_from(tcx, instance, set, target) }); } } /// If this basic block ends with a [`TerminatorKind::SwitchInt`] for which we can evaluate the /// dimscriminant in monomorphization, we return the discriminant bits and the /// [`SwitchTargets`], just so the caller doesn't also have to match on the terminator. fn try_const_mono_switchint<'a>( tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, block: &'a BasicBlockData<'tcx>, ) -> Option<(u128, &'a SwitchTargets)> { // There are two places here we need to evaluate a constant. let eval_mono_const = |constant: &ConstOperand<'tcx>| { let env = ty::ParamEnv::reveal_all(); let mono_literal = instance.instantiate_mir_and_normalize_erasing_regions( tcx, env, crate::ty::EarlyBinder::bind(constant.const_), ); let Some(bits) = mono_literal.try_eval_bits(tcx, env) else { bug!("Couldn't evaluate constant {:?} in mono {:?}", constant, instance); }; bits }; let TerminatorKind::SwitchInt { discr, targets } = &block.terminator().kind else { return None; }; // If this is a SwitchInt(const _), then we can just evaluate the constant and return. let discr = match discr { Operand::Constant(constant) => { let bits = eval_mono_const(constant); return Some((bits, targets)); } Operand::Move(place) | Operand::Copy(place) => place, }; // MIR for `if false` actually looks like this: // _1 = const _ // SwitchInt(_1) // // And MIR for if intrinsics::debug_assertions() looks like this: // _1 = cfg!(debug_assertions) // SwitchInt(_1) // // So we're going to try to recognize this pattern. // // If we have a SwitchInt on a non-const place, we find the most recent statement that // isn't a storage marker. If that statement is an assignment of a const to our // discriminant place, we evaluate and return the const, as if we've const-propagated it // into the SwitchInt. let last_stmt = block.statements.iter().rev().find(|stmt| { !matches!(stmt.kind, StatementKind::StorageDead(_) | StatementKind::StorageLive(_)) })?; let (place, rvalue) = last_stmt.kind.as_assign()?; if discr != place { return None; } match rvalue { Rvalue::NullaryOp(NullOp::UbCheck(_), _) => { Some((tcx.sess.opts.debug_assertions as u128, targets)) } Rvalue::Use(Operand::Constant(constant)) => { let bits = eval_mono_const(constant); Some((bits, targets)) } _ => None, } } </ins> /// For a `Location` in this scope, determine what the "caller location" at that point is. This /// is interesting because of inlining: the `#[track_caller]` attribute of inlined functions /// must be honored. Falls back to the `tracked_caller` value for `#[track_caller]` functions,

<del> use rustc_index::bit_set::BitSet; </del> use super::*; /// Preorder traversal of a graph.

<ins> //@ compile-flags: -Cno-prepopulate-passes -Copt-level=0 -Cdebug-assertions=no // This test ensures that in a debug build which turns off debug assertions, we do not monomorphize // any of the standard library's unsafe precondition checks. // The naive codegen of those checks contains the actual check underneath an `if false`, which // could be optimized out if optimizations are enabled. But if we rely on optimizations to remove // panic branches, then we can't link compiler_builtins without optimizing it, which means that // -Zbuild-std doesn't work with -Copt-level=0. // // In other words, this tests for a mandatory optimization. #![crate_type = "lib"] use std::ptr::NonNull; // CHECK-LABEL: ; core::ptr::non_null::NonNull<T>::new_unchecked // CHECK-NOT: call // CHECK: } // CHECK-LABEL: @nonnull_new #[no_mangle] pub unsafe fn nonnull_new(ptr: *mut u8) -> NonNull<u8> { // CHECK: ; call core::ptr::non_null::NonNull<T>::new_unchecked unsafe { NonNull::new_unchecked(ptr) } } </ins>

<ins> //@ compile-flags: -Cno-prepopulate-passes -Copt-level=0 #![crate_type = "lib"] #[no_mangle] pub fn demo_for_i32() { generic_impl::<i32>(); } // Two important things here: // - We replace the "then" block with `unreachable` to avoid linking problems // - We neither declare nor define the `big_impl` that said block "calls". // CHECK-LABEL: ; skip_mono_inside_if_false::generic_impl // CHECK: start: // CHECK-NEXT: br label %[[ELSE_BRANCH:bb[0-9]+]] // CHECK: [[ELSE_BRANCH]]: // CHECK-NEXT: call skip_mono_inside_if_false::small_impl // CHECK: bb{{[0-9]+}}: // CHECK-NEXT: ret void // CHECK: bb{{[0-9+]}}: // CHECK-NEXT: unreachable fn generic_impl<T>() { trait MagicTrait { const IS_BIG: bool; } impl<T> MagicTrait for T { const IS_BIG: bool = std::mem::size_of::<T>() > 10; } if T::IS_BIG { big_impl::<T>(); } else { small_impl::<T>(); } } #[inline(never)] fn small_impl<T>() {} #[inline(never)] fn big_impl<T>() {} </ins>