Skip to content

Commit eea88f5

Browse files
committed
Clean up MonoItem::instantiation_mode
1 parent 9e48dfe commit eea88f5

File tree

1 file changed

+79
-42
lines changed
  • compiler/rustc_middle/src/mir

1 file changed

+79
-42
lines changed

compiler/rustc_middle/src/mir/mono.rs

+79-42
Original file line numberDiff line numberDiff line change
@@ -92,51 +92,88 @@ impl<'tcx> MonoItem<'tcx> {
9292
}
9393

9494
pub fn instantiation_mode(&self, tcx: TyCtxt<'tcx>) -> InstantiationMode {
95-
let generate_cgu_internal_copies =
96-
(tcx.sess.opts.optimize != OptLevel::No) && !tcx.sess.link_dead_code();
95+
// The case handling here is written in the same style as cross_crate_inlinable, we first
96+
// handle the cases where we must use a particular instantiation mode, then cascade down
97+
// through a sequence of heuristics.
9798

98-
match *self {
99-
MonoItem::Fn(ref instance) => {
100-
let entry_def_id = tcx.entry_fn(()).map(|(id, _)| id);
101-
// If this function isn't inlined or otherwise has an extern
102-
// indicator, then we'll be creating a globally shared version.
103-
let codegen_fn_attrs = tcx.codegen_fn_attrs(instance.def_id());
104-
if codegen_fn_attrs.contains_extern_indicator()
105-
|| codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED)
106-
|| !instance.def.generates_cgu_internal_copy(tcx)
107-
|| Some(instance.def_id()) == entry_def_id
108-
{
109-
return InstantiationMode::GloballyShared { may_conflict: false };
110-
}
111-
112-
if let InlineAttr::Never = tcx.codegen_fn_attrs(instance.def_id()).inline
113-
&& self.is_generic_fn()
114-
{
115-
// Upgrade inline(never) to a globally shared instance.
116-
return InstantiationMode::GloballyShared { may_conflict: true };
117-
}
118-
119-
// At this point we don't have explicit linkage and we're an
120-
// inlined function. If this crate's build settings permit,
121-
// we'll be creating a local copy per CGU.
122-
if generate_cgu_internal_copies {
123-
return InstantiationMode::LocalCopy;
124-
}
125-
126-
// Finally, if this is `#[inline(always)]` we're sure to respect
127-
// that with an inline copy per CGU, but otherwise we'll be
128-
// creating one copy of this `#[inline]` function which may
129-
// conflict with upstream crates as it could be an exported
130-
// symbol.
131-
if tcx.codegen_fn_attrs(instance.def_id()).inline.always() {
132-
InstantiationMode::LocalCopy
133-
} else {
134-
InstantiationMode::GloballyShared { may_conflict: true }
135-
}
136-
}
99+
// The first thing we do is detect MonoItems which we must instantiate exactly once in the
100+
// whole program.
101+
102+
// Statics and global_asm! must be instantiated exactly once.
103+
let instance = match *self {
104+
MonoItem::Fn(instance) => instance,
137105
MonoItem::Static(..) | MonoItem::GlobalAsm(..) => {
138-
InstantiationMode::GloballyShared { may_conflict: false }
106+
return InstantiationMode::GloballyShared { may_conflict: false };
139107
}
108+
};
109+
110+
// Similarly, the executable entrypoint must be instantiated exactly once.
111+
if let Some((entry_def_id, _)) = tcx.entry_fn(()) {
112+
if instance.def_id() == entry_def_id {
113+
return InstantiationMode::GloballyShared { may_conflict: false };
114+
}
115+
}
116+
117+
// If the function is #[naked] or contains any other attribute that requires exactly-once
118+
// instantiation:
119+
let codegen_fn_attrs = tcx.codegen_fn_attrs(instance.def_id());
120+
if codegen_fn_attrs.contains_extern_indicator()
121+
|| codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED)
122+
{
123+
return InstantiationMode::GloballyShared { may_conflict: false };
124+
}
125+
126+
// FIXME: The logic for which functions are permitted to get LocalCopy is actually spread
127+
// across 4 functions:
128+
// * cross_crate_inlinable(def_id)
129+
// * InstanceKind::requires_inline
130+
// * InstanceKind::generate_cgu_internal_copy
131+
// * MonoItem::instantiation_mode
132+
// Since reachable_non_generics calls InstanceKind::generates_cgu_internal_copy to decide
133+
// which symbols this crate exports, we are obligated to only generate LocalCopy when
134+
// generates_cgu_internal_copy returns true.
135+
if !instance.def.generates_cgu_internal_copy(tcx) {
136+
return InstantiationMode::GloballyShared { may_conflict: false };
137+
}
138+
139+
// Beginning of heuristics. The handling of link-dead-code and inline(always) are QoL only,
140+
// the compiler should not crash and linkage should work, but codegen may be undesirable.
141+
142+
// -Clink-dead-code was given an unfortunate name; the point of the flag is to assist
143+
// coverage tools which rely on having every function in the program appear in the
144+
// generated code. If we select LocalCopy, functions which are not used because they are
145+
// missing test coverage will disappear from such coverage reports, defeating the point.
146+
// Note that -Cinstrument-coverage does not require such assistance from us, only coverage
147+
// tools implemented without compiler support ironically require a special compiler flag.
148+
if tcx.sess.link_dead_code() {
149+
return InstantiationMode::GloballyShared { may_conflict: true };
150+
}
151+
152+
// To ensure that #[inline(always)] can be inlined as much as possible, especially in unoptimized
153+
// builds, we always select LocalCopy.
154+
if codegen_fn_attrs.inline.always() {
155+
return InstantiationMode::LocalCopy;
156+
}
157+
158+
// #[inline(never)] functions in general are poor candidates for inlining and thus since
159+
// LocalCopy generally increases code size for the benefit of optimizations from inlining,
160+
// we want to give them GloballyShared codegen.
161+
// The slight problem is that generic functions need to always support cross-crate
162+
// compilation, so all previous stages of the compiler are obligated to treat generic
163+
// functions the same as those that unconditionally get LocalCopy codegen. It's only when
164+
// we get here that we can at least not codegen a #[inline(never)] generic function in all
165+
// of our CGUs.
166+
if let InlineAttr::Never = tcx.codegen_fn_attrs(instance.def_id()).inline
167+
&& self.is_generic_fn()
168+
{
169+
return InstantiationMode::GloballyShared { may_conflict: true };
170+
}
171+
172+
// The fallthrough case is to generate LocalCopy for all optimized builds, and
173+
// GloballyShared with conflict prevention when optimizations are disabled.
174+
match tcx.sess.opts.optimize {
175+
OptLevel::No => InstantiationMode::GloballyShared { may_conflict: true },
176+
_ => InstantiationMode::LocalCopy,
140177
}
141178
}
142179

0 commit comments

Comments
 (0)