Skip to content

Commit 5ef1e7e

Browse files
authored
Auto merge of #34830 - michaelwoerister:internal-closures, r=nikomatsakis
trans: Avoid weak linkage for closures when linking with MinGW. This PR proposes one possible solution to #34793, the problem that prevents servo/servo#12393 from landing. It applies the same strategy, that we already use for monomorphizations, to closures, that is, instead of emitting symbols with `weak_odr` linkage in order to avoid symbol conflicts, we emit them with `internal` linkage, with the side effect that we have to copy code instead of just linking to it, if more than one codegen unit is involved. With this PR, the compiler will only apply this strategy for targets where we would actually run into a problem when using `weak_odr` linkage, in other words nothing will change for platforms except for MinGW. The solution implemented here has one restriction that could be lifted with some more effort, but it does not seem to be worth the trouble since it will go away once we use only MIR-trans: If someone compiles code 1. on MinGW, 2. with more than one codegen unit, 3. *not* using MIR-trans, 4. and runs into a closure inlined from another crate then the compiler will abort and suggest to compile either with just one codegen unit or `-Zorbit`. What's nice about this is that I lays a foundation for also doing the same for generics: using weak linkage where possible and thus enabling some more space optimizations that the linker can do. ~~This PR also contains a test case for compiling a program that contains more than 2^15 closures. It's a huge, generated file with almost 100K LOCs. I did not commit the script for generating the file but could do so. Alternatively, maybe someone wants to come up with a way of doing this with macros.~~ The test file is implemented via macros now (thanks @alexcrichton!) Opinions? Fixes #34793. cc @rust-lang/compiler
2 parents d648a16 + eaea4ac commit 5ef1e7e

File tree

7 files changed

+145
-41
lines changed

7 files changed

+145
-41
lines changed

src/librustc/session/config.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ impl Options {
330330
self.debugging_opts.dump_dep_graph ||
331331
self.debugging_opts.query_dep_graph
332332
}
333+
334+
pub fn single_codegen_unit(&self) -> bool {
335+
self.incremental.is_none() ||
336+
self.cg.codegen_units == 1
337+
}
333338
}
334339

335340
// The type of entry function, so
@@ -655,7 +660,6 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
655660
"panic strategy to compile crate with"),
656661
}
657662

658-
659663
options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
660664
build_debugging_options, "Z", "debugging",
661665
DB_OPTIONS, db_type_desc, dbsetters,

src/librustc_back/target/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ pub struct TargetOptions {
292292
pub is_like_android: bool,
293293
/// Whether the linker support GNU-like arguments such as -O. Defaults to false.
294294
pub linker_is_gnu: bool,
295+
/// The MinGW toolchain has a known issue that prevents it from correctly
296+
/// handling COFF object files with more than 2^15 sections. Since each weak
297+
/// symbol needs its own COMDAT section, weak linkage implies a large
298+
/// number sections that easily exceeds the given limit for larger
299+
/// codebases. Consequently we want a way to disallow weak linkage on some
300+
/// platforms.
301+
pub allows_weak_linkage: bool,
295302
/// Whether the linker support rpaths or not. Defaults to false.
296303
pub has_rpath: bool,
297304
/// Whether to disable linking to compiler-rt. Defaults to false, as LLVM
@@ -367,6 +374,7 @@ impl Default for TargetOptions {
367374
is_like_android: false,
368375
is_like_msvc: false,
369376
linker_is_gnu: false,
377+
allows_weak_linkage: true,
370378
has_rpath: false,
371379
no_compiler_rt: false,
372380
no_default_libraries: true,
@@ -509,6 +517,7 @@ impl Target {
509517
key!(is_like_msvc, bool);
510518
key!(is_like_android, bool);
511519
key!(linker_is_gnu, bool);
520+
key!(allows_weak_linkage, bool);
512521
key!(has_rpath, bool);
513522
key!(no_compiler_rt, bool);
514523
key!(no_default_libraries, bool);
@@ -651,6 +660,7 @@ impl ToJson for Target {
651660
target_option_val!(is_like_msvc);
652661
target_option_val!(is_like_android);
653662
target_option_val!(linker_is_gnu);
663+
target_option_val!(allows_weak_linkage);
654664
target_option_val!(has_rpath);
655665
target_option_val!(no_compiler_rt);
656666
target_option_val!(no_default_libraries);

src/librustc_back/target/windows_base.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub fn opts() -> TargetOptions {
2525
staticlib_suffix: ".lib".to_string(),
2626
no_default_libraries: true,
2727
is_like_windows: true,
28+
allows_weak_linkage: false,
2829
pre_link_args: vec!(
2930
// And here, we see obscure linker flags #45. On windows, it has been
3031
// found to be necessary to have this flag to compile liblibc.

src/librustc_trans/closure.rs

+75-2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,41 @@ fn get_or_create_closure_declaration<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
181181
llfn
182182
}
183183

184+
fn translating_closure_body_via_mir_will_fail(ccx: &CrateContext,
185+
closure_def_id: DefId)
186+
-> bool {
187+
let default_to_mir = ccx.sess().opts.debugging_opts.orbit;
188+
let invert = if default_to_mir { "rustc_no_mir" } else { "rustc_mir" };
189+
let use_mir = default_to_mir ^ ccx.tcx().has_attr(closure_def_id, invert);
190+
191+
!use_mir
192+
}
193+
194+
pub fn trans_closure_body_via_mir<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
195+
closure_def_id: DefId,
196+
closure_substs: ty::ClosureSubsts<'tcx>) {
197+
use syntax::ast::DUMMY_NODE_ID;
198+
use syntax_pos::DUMMY_SP;
199+
use syntax::ptr::P;
200+
201+
trans_closure_expr(Dest::Ignore(ccx),
202+
&hir::FnDecl {
203+
inputs: P::new(),
204+
output: hir::NoReturn(DUMMY_SP),
205+
variadic: false
206+
},
207+
&hir::Block {
208+
stmts: P::new(),
209+
expr: None,
210+
id: DUMMY_NODE_ID,
211+
rules: hir::DefaultBlock,
212+
span: DUMMY_SP
213+
},
214+
DUMMY_NODE_ID,
215+
closure_def_id,
216+
closure_substs);
217+
}
218+
184219
pub enum Dest<'a, 'tcx: 'a> {
185220
SaveIn(Block<'a, 'tcx>, ValueRef),
186221
Ignore(&'a CrateContext<'a, 'tcx>)
@@ -213,8 +248,13 @@ pub fn trans_closure_expr<'a, 'tcx>(dest: Dest<'a, 'tcx>,
213248
// If we have not done so yet, translate this closure's body
214249
if !ccx.instances().borrow().contains_key(&instance) {
215250
let llfn = get_or_create_closure_declaration(ccx, closure_def_id, closure_substs);
216-
llvm::SetLinkage(llfn, llvm::WeakODRLinkage);
217-
llvm::SetUniqueComdat(ccx.llmod(), llfn);
251+
252+
if ccx.sess().target.target.options.allows_weak_linkage {
253+
llvm::SetLinkage(llfn, llvm::WeakODRLinkage);
254+
llvm::SetUniqueComdat(ccx.llmod(), llfn);
255+
} else {
256+
llvm::SetLinkage(llfn, llvm::InternalLinkage);
257+
}
218258

219259
// set an inline hint for all closures
220260
attributes::inline(llfn, attributes::InlineAttr::Hint);
@@ -296,6 +336,39 @@ pub fn trans_closure_method<'a, 'tcx>(ccx: &'a CrateContext<'a, 'tcx>,
296336
// If this is a closure, redirect to it.
297337
let llfn = get_or_create_closure_declaration(ccx, closure_def_id, substs);
298338

339+
// If weak linkage is not allowed, we have to make sure that a local,
340+
// private copy of the closure is available in this codegen unit
341+
if !ccx.sess().target.target.options.allows_weak_linkage &&
342+
!ccx.sess().opts.single_codegen_unit() {
343+
344+
if let Some(node_id) = ccx.tcx().map.as_local_node_id(closure_def_id) {
345+
// If the closure is defined in the local crate, we can always just
346+
// translate it.
347+
let (decl, body) = match ccx.tcx().map.expect_expr(node_id).node {
348+
hir::ExprClosure(_, ref decl, ref body, _) => (decl, body),
349+
_ => { unreachable!() }
350+
};
351+
352+
trans_closure_expr(Dest::Ignore(ccx),
353+
decl,
354+
body,
355+
node_id,
356+
closure_def_id,
357+
substs);
358+
} else {
359+
// If the closure is defined in an upstream crate, we can only
360+
// translate it if MIR-trans is active.
361+
362+
if translating_closure_body_via_mir_will_fail(ccx, closure_def_id) {
363+
ccx.sess().fatal("You have run into a known limitation of the \
364+
MingW toolchain. Either compile with -Zorbit or \
365+
with -Ccodegen-units=1 to work around it.");
366+
}
367+
368+
trans_closure_body_via_mir(ccx, closure_def_id, substs);
369+
}
370+
}
371+
299372
// If the closure is a Fn closure, but a FnOnce is needed (etc),
300373
// then adapt the self type
301374
let llfn_closure_kind = ccx.tcx().closure_kind(closure_def_id);

src/librustc_trans/mir/constant.rs

+3-19
Original file line numberDiff line numberDiff line change
@@ -530,26 +530,10 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> {
530530

531531
// FIXME Shouldn't need to manually trigger closure instantiations.
532532
if let mir::AggregateKind::Closure(def_id, substs) = *kind {
533-
use rustc::hir;
534-
use syntax::ast::DUMMY_NODE_ID;
535-
use syntax::ptr::P;
536533
use closure;
537-
538-
closure::trans_closure_expr(closure::Dest::Ignore(self.ccx),
539-
&hir::FnDecl {
540-
inputs: P::new(),
541-
output: hir::NoReturn(DUMMY_SP),
542-
variadic: false
543-
},
544-
&hir::Block {
545-
stmts: P::new(),
546-
expr: None,
547-
id: DUMMY_NODE_ID,
548-
rules: hir::DefaultBlock,
549-
span: DUMMY_SP
550-
},
551-
DUMMY_NODE_ID, def_id,
552-
self.monomorphize(&substs));
534+
closure::trans_closure_body_via_mir(self.ccx,
535+
def_id,
536+
self.monomorphize(&substs));
553537
}
554538

555539
let val = if let mir::AggregateKind::Adt(adt_def, index, _) = *kind {

src/librustc_trans/mir/rvalue.rs

+3-19
Original file line numberDiff line numberDiff line change
@@ -131,27 +131,11 @@ impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
131131
_ => {
132132
// FIXME Shouldn't need to manually trigger closure instantiations.
133133
if let mir::AggregateKind::Closure(def_id, substs) = *kind {
134-
use rustc::hir;
135-
use syntax::ast::DUMMY_NODE_ID;
136-
use syntax::ptr::P;
137-
use syntax_pos::DUMMY_SP;
138134
use closure;
139135

140-
closure::trans_closure_expr(closure::Dest::Ignore(bcx.ccx()),
141-
&hir::FnDecl {
142-
inputs: P::new(),
143-
output: hir::NoReturn(DUMMY_SP),
144-
variadic: false
145-
},
146-
&hir::Block {
147-
stmts: P::new(),
148-
expr: None,
149-
id: DUMMY_NODE_ID,
150-
rules: hir::DefaultBlock,
151-
span: DUMMY_SP
152-
},
153-
DUMMY_NODE_ID, def_id,
154-
bcx.monomorphize(&substs));
136+
closure::trans_closure_body_via_mir(bcx.ccx(),
137+
def_id,
138+
bcx.monomorphize(&substs));
155139
}
156140

157141
for (i, operand) in operands.iter().enumerate() {

src/test/run-pass/myriad-closures.rs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// This test case tests whether we can handle code bases that contain a high
12+
// number of closures, something that needs special handling in the MingGW
13+
// toolchain.
14+
// See https://github.com/rust-lang/rust/issues/34793 for more information.
15+
16+
// Expand something exponentially
17+
macro_rules! go_bacterial {
18+
($mac:ident) => ($mac!());
19+
($mac:ident 1 $($t:tt)*) => (
20+
go_bacterial!($mac $($t)*);
21+
go_bacterial!($mac $($t)*);
22+
)
23+
}
24+
25+
macro_rules! mk_closure {
26+
() => ({
27+
let c = |a: u32| a + 4;
28+
let _ = c(2);
29+
})
30+
}
31+
32+
macro_rules! mk_fn {
33+
() => {
34+
{
35+
fn function() {
36+
// Make 16 closures
37+
go_bacterial!(mk_closure 1 1 1 1);
38+
}
39+
let _ = function();
40+
}
41+
}
42+
}
43+
44+
fn main() {
45+
// Make 2^12 functions, each containing 16 closures,
46+
// resulting in 2^16 closures overall.
47+
go_bacterial!(mk_fn 1 1 1 1 1 1 1 1 1 1 1 1);
48+
}

0 commit comments

Comments
 (0)