rustc: Tweak visibility of some lang items

This commit tweaks the linker-level visibility of some lang items that rustc
uses and defines. Notably this means that `#[panic_implementation]` and
`#[alloc_error_handler]` functions are never marked as `internal`. It's up to
the linker to eliminate these, not rustc.

Additionally `#[global_allocator]` generated symbols are no longer forced to
`Default` visibility (fully exported), but rather they're relaxed to `Hidden`
visibility). This symbols are *not* needed across DLL boundaries, only as a
local implementation detail of the compiler-injected allocator symbols, so
`Hidden` should suffice.

Closes #51342
Closes #52795
This commit is contained in:
Alex Crichton 2018-08-02 12:30:43 -07:00
parent 38eeebdfed
commit 7c58ab671f
9 changed files with 238 additions and 56 deletions

View File

@ -15,7 +15,7 @@
// makes all other generics or inline functions that it references
// reachable as well.
use hir::CodegenFnAttrs;
use hir::{CodegenFnAttrs, CodegenFnAttrFlags};
use hir::map as hir_map;
use hir::def::Def;
use hir::def_id::{DefId, CrateNum};
@ -28,7 +28,6 @@
use rustc_target::spec::abi::Abi;
use syntax::ast;
use syntax::attr;
use hir;
use hir::def_id::LOCAL_CRATE;
use hir::intravisit::{Visitor, NestedVisitorMap};
@ -359,8 +358,12 @@ struct CollectPrivateImplItemsVisitor<'a, 'tcx: 'a> {
impl<'a, 'tcx: 'a> ItemLikeVisitor<'tcx> for CollectPrivateImplItemsVisitor<'a, 'tcx> {
fn visit_item(&mut self, item: &hir::Item) {
// Anything which has custom linkage gets thrown on the worklist no
// matter where it is in the crate.
if attr::contains_name(&item.attrs, "linkage") {
// matter where it is in the crate, along with "special std symbols"
// which are currently akin to allocator symbols.
let def_id = self.tcx.hir.local_def_id(item.id);
let codegen_attrs = self.tcx.codegen_fn_attrs(def_id);
if codegen_attrs.linkage.is_some() ||
codegen_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) {
self.worklist.push(item.id);
}

View File

@ -13,7 +13,7 @@
use syntax::{
ast::{
self, Arg, Attribute, Crate, Expr, FnHeader, Generics, Ident, Item, ItemKind,
LitKind, Mac, Mod, Mutability, StrStyle, Ty, TyKind, Unsafety, VisibilityKind,
Mac, Mod, Mutability, Ty, TyKind, Unsafety, VisibilityKind,
},
attr,
codemap::{
@ -236,17 +236,12 @@ fn call_allocator(&self, method: &str, mut args: Vec<P<Expr>>) -> P<Expr> {
}
fn attrs(&self) -> Vec<Attribute> {
let key = Symbol::intern("linkage");
let value = LitKind::Str(Symbol::intern("external"), StrStyle::Cooked);
let linkage = self.cx.meta_name_value(self.span, key, value);
let no_mangle = Symbol::intern("no_mangle");
let no_mangle = self.cx.meta_word(self.span, no_mangle);
let special = Symbol::intern("rustc_std_internal_symbol");
let special = self.cx.meta_word(self.span, special);
vec![
self.cx.attribute(self.span, linkage),
self.cx.attribute(self.span, no_mangle),
self.cx.attribute(self.span, special),
]

View File

@ -67,14 +67,15 @@ pub(crate) unsafe fn codegen(tcx: TyCtxt, mods: &ModuleLlvm, kind: AllocatorKind
if tcx.sess.target.target.options.default_hidden_visibility {
llvm::LLVMRustSetVisibility(llfn, llvm::Visibility::Hidden);
}
if tcx.sess.target.target.options.requires_uwtable {
attributes::emit_uwtable(llfn, true);
}
if tcx.sess.target.target.options.requires_uwtable {
attributes::emit_uwtable(llfn, true);
}
let callee = CString::new(kind.fn_name(method.name)).unwrap();
let callee = llvm::LLVMRustGetOrInsertFunction(llmod,
callee.as_ptr(),
ty);
llvm::LLVMRustSetVisibility(callee, llvm::Visibility::Hidden);
let llbb = llvm::LLVMAppendBasicBlockInContext(llcx,
llfn,

View File

@ -809,8 +809,28 @@ pub fn codegen_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
rx,
codegen_units.len());
// Codegen an allocator shim, if any
let allocator_module = if let Some(kind) = *tcx.sess.allocator_kind.get() {
// Codegen an allocator shim, if necessary.
//
// If the crate doesn't have an `allocator_kind` set then there's definitely
// no shim to generate. Otherwise we also check our dependency graph for all
// our output crate types. If anything there looks like its a `Dynamic`
// linkage, then it's already got an allocator shim and we'll be using that
// one instead. If nothing exists then it's our job to generate the
// allocator!
let any_dynamic_crate = tcx.sess.dependency_formats.borrow()
.iter()
.any(|(_, list)| {
use rustc::middle::dependency_format::Linkage;
list.iter().any(|linkage| {
match linkage {
Linkage::Dynamic => true,
_ => false,
}
})
});
let allocator_module = if any_dynamic_crate {
None
} else if let Some(kind) = *tcx.sess.allocator_kind.get() {
unsafe {
let llmod_id = "allocator";
let modules = ModuleLlvm::new(tcx.sess, llmod_id);

View File

@ -104,6 +104,7 @@
use monomorphize::collector::InliningMap;
use rustc::dep_graph::WorkProductId;
use rustc::hir::CodegenFnAttrFlags;
use rustc::hir::def_id::DefId;
use rustc::hir::map::DefPathData;
use rustc::mir::mono::{Linkage, Visibility};
@ -300,6 +301,13 @@ fn place_root_mono_items<'a, 'tcx, I>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
let is_incremental_build = tcx.sess.opts.incremental.is_some();
let mut internalization_candidates = FxHashSet();
// Determine if monomorphizations instantiated in this crate will be made
// available to downstream crates. This depends on whether we are in
// share-generics mode and whether the current crate can even have
// downstream crates.
let export_generics = tcx.sess.opts.share_generics() &&
tcx.local_crate_exports_generics();
for mono_item in mono_items {
match mono_item.instantiation_mode(tcx) {
InstantiationMode::GloballyShared { .. } => {}
@ -323,29 +331,7 @@ fn place_root_mono_items<'a, 'tcx, I>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
tcx,
&mono_item,
&mut can_be_internalized,
&|id, is_generic| {
if !tcx.sess.target.target.options.default_hidden_visibility {
return Visibility::Default
}
// Generic functions never have export level C
if is_generic {
return Visibility::Hidden
}
// Things with export level C don't get instantiated in
// downstream crates
if !id.is_local() {
return Visibility::Hidden
}
// C-export level items remain at `Default`, all other internal
// items become `Hidden`
match tcx.reachable_non_generics(id.krate).get(&id) {
Some(SymbolExportLevel::C) => Visibility::Default,
_ => Visibility::Hidden,
}
},
export_generics,
);
if visibility == Visibility::Hidden && can_be_internalized {
internalization_candidates.insert(mono_item);
@ -376,12 +362,17 @@ fn mono_item_linkage_and_visibility(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
mono_item: &MonoItem<'tcx>,
can_be_internalized: &mut bool,
default: &dyn Fn(DefId, bool) -> Visibility,
export_generics: bool,
) -> (Linkage, Visibility) {
if let Some(explicit_linkage) = mono_item.explicit_linkage(tcx) {
return (explicit_linkage, Visibility::Default)
}
let vis = mono_item_visibility(tcx, mono_item, can_be_internalized, default);
let vis = mono_item_visibility(
tcx,
mono_item,
can_be_internalized,
export_generics,
);
(Linkage::External, vis)
}
@ -389,7 +380,7 @@ fn mono_item_visibility(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
mono_item: &MonoItem<'tcx>,
can_be_internalized: &mut bool,
default_visibility: &dyn Fn(DefId, bool) -> Visibility,
export_generics: bool,
) -> Visibility {
let instance = match mono_item {
// This is pretty complicated, go below
@ -399,7 +390,7 @@ fn mono_item_visibility(
MonoItem::Static(def_id) => {
return if tcx.is_reachable_non_generic(*def_id) {
*can_be_internalized = false;
default_visibility(*def_id, false)
default_visibility(tcx, *def_id, false)
} else {
Visibility::Hidden
};
@ -408,7 +399,7 @@ fn mono_item_visibility(
let def_id = tcx.hir.local_def_id(*node_id);
return if tcx.is_reachable_non_generic(def_id) {
*can_be_internalized = false;
default_visibility(def_id, false)
default_visibility(tcx, def_id, false)
} else {
Visibility::Hidden
};
@ -440,18 +431,13 @@ fn mono_item_visibility(
// hidden visibility, it should indeed be a candidate for
// internalization, but we have to understand that it's referenced
// from the `main` symbol we'll generate later.
//
// This may be fixable with a new `InstanceDef` perhaps? Unsure!
if tcx.lang_items().start_fn() == Some(def_id) {
*can_be_internalized = false;
return Visibility::Hidden
}
// Determine if monomorphizations instantiated in this crate will be made
// available to downstream crates. This depends on whether we are in
// share-generics mode and whether the current crate can even have
// downstream crates.
let export_generics = tcx.sess.opts.share_generics() &&
tcx.local_crate_exports_generics();
let is_generic = instance.substs.types().next().is_some();
// Upstream `DefId` instances get different handling than local ones
@ -461,7 +447,7 @@ fn mono_item_visibility(
// and we export generics, we must make
// it available to downstream crates.
*can_be_internalized = false;
default_visibility(def_id, true)
default_visibility(tcx, def_id, true)
} else {
Visibility::Hidden
}
@ -477,7 +463,7 @@ fn mono_item_visibility(
// This instance might be useful in
// a downstream crate.
*can_be_internalized = false;
default_visibility(def_id, true)
default_visibility(tcx, def_id, true)
}
} else {
// We are not exporting generics or
@ -487,14 +473,82 @@ fn mono_item_visibility(
Visibility::Hidden
}
} else {
// This isn't a generic function.
// If this isn't a generic function then we mark this a `Default` if
// this is a reachable item, meaning that it's a symbol other crates may
// access when they link to us.
if tcx.is_reachable_non_generic(def_id) {
*can_be_internalized = false;
debug_assert!(!is_generic);
default_visibility(def_id, false)
} else {
Visibility::Hidden
return default_visibility(tcx, def_id, false)
}
// If this isn't reachable then we're gonna tag this with `Hidden`
// visibility. In some situations though we'll want to prevent this
// symbol from being internalized.
//
// There's two categories of items here:
//
// * First is weak lang items. These are basically mechanisms for
// libcore to forward-reference symbols defined later in crates like
// the standard library or `#[panic_implementation]` definitions. The
// definition of these weak lang items needs to be referenceable by
// libcore, so we're no longer a candidate for internalization.
// Removal of these functions can't be done by LLVM but rather must be
// done by the linker as it's a non-local decision.
//
// * Second is "std internal symbols". Currently this is primarily used
// for allocator symbols. Allocators are a little weird in their
// implementation, but the idea is that the compiler, at the last
// minute, defines an allocator with an injected object file. The
// `alloc` crate references these symbols (`__rust_alloc`) and the
// definition doesn't get hooked up until a linked crate artifact is
// generated.
//
// The symbols synthesized by the compiler (`__rust_alloc`) are thin
// veneers around the actual implementation, some other symbol which
// implements the same ABI. These symbols (things like `__rg_alloc`,
// `__rdl_alloc`, `__rde_alloc`, etc), are all tagged with "std
// internal symbols".
//
// The std-internal symbols here **should not show up in a dll as an
// exported interface**, so they return `false` from
// `is_reachable_non_generic` above and we'll give them `Hidden`
// visibility below. Like the weak lang items, though, we can't let
// LLVM internalize them as this decision is left up to the linker to
// omit them, so prevent them from being internalized.
let codegen_fn_attrs = tcx.codegen_fn_attrs(def_id);
let std_internal_symbol = codegen_fn_attrs.flags
.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL);
if tcx.is_weak_lang_item(def_id) || std_internal_symbol {
*can_be_internalized = false;
}
Visibility::Hidden
}
}
fn default_visibility(tcx: TyCtxt, id: DefId, is_generic: bool) -> Visibility {
if !tcx.sess.target.target.options.default_hidden_visibility {
return Visibility::Default
}
// Generic functions never have export level C
if is_generic {
return Visibility::Hidden
}
// Things with export level C don't get instantiated in
// downstream crates
if !id.is_local() {
return Visibility::Hidden
}
// C-export level items remain at `Default`, all other internal
// items become `Hidden`
match tcx.reachable_non_generics(id.krate).get(&id) {
Some(SymbolExportLevel::C) => Visibility::Default,
_ => Visibility::Hidden,
}
}

View File

@ -0,0 +1,16 @@
-include ../../run-make-fulldeps/tools.mk
ifeq ($(TARGET),wasm32-unknown-unknown)
all:
$(RUSTC) foo.rs --target wasm32-unknown-unknown
$(NODE) verify-exported-symbols.js $(TMPDIR)/foo.wasm
$(RUSTC) foo.rs --target wasm32-unknown-unknown -O
$(NODE) verify-exported-symbols.js $(TMPDIR)/foo.wasm
$(RUSTC) bar.rs --target wasm32-unknown-unknown
$(NODE) verify-exported-symbols.js $(TMPDIR)/bar.wasm
$(RUSTC) bar.rs --target wasm32-unknown-unknown -O
$(NODE) verify-exported-symbols.js $(TMPDIR)/bar.wasm
else
all:
endif

View File

@ -0,0 +1,45 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(panic_implementation, alloc_error_handler)]
#![crate_type = "cdylib"]
#![no_std]
use core::alloc::*;
struct B;
unsafe impl GlobalAlloc for B {
unsafe fn alloc(&self, x: Layout) -> *mut u8 {
1 as *mut u8
}
unsafe fn dealloc(&self, ptr: *mut u8, x: Layout) {
}
}
#[global_allocator]
static A: B = B;
#[no_mangle]
pub extern fn foo(a: u32) -> u32 {
assert_eq!(a, 3);
a * 2
}
#[alloc_error_handler]
fn a(_: core::alloc::Layout) -> ! {
loop {}
}
#[panic_implementation]
fn b(_: &core::panic::PanicInfo) -> ! {
loop {}
}

View File

@ -0,0 +1,17 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![crate_type = "cdylib"]
#[no_mangle]
pub extern fn foo() {
println!("foo");
panic!("test");
}

View File

@ -0,0 +1,31 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
const fs = require('fs');
const process = require('process');
const assert = require('assert');
const buffer = fs.readFileSync(process.argv[2]);
let m = new WebAssembly.Module(buffer);
let list = WebAssembly.Module.exports(m);
console.log('exports', list);
let bad = false;
for (let i = 0; i < list.length; i++) {
const e = list[i];
if (e.name == "foo" || e.kind != "function")
continue;
console.log('unexpected exported symbol:', e.name);
bad = true;
}
if (bad)
process.exit(1);