From 4c7e27734031d8b57cb51ad498d7f5111032468d Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 20 Feb 2017 14:42:47 -0500 Subject: [PATCH 1/6] add an #[used] attribute similar to GCC's __attribute((used))__. This attribute prevents LLVM from optimizing away a non-exported symbol, within a compilation unit (object file), when there are no references to it. This is better explained with an example: ``` #[used] static LIVE: i32 = 0; static REFERENCED: i32 = 0; static DEAD: i32 = 0; fn internal() {} pub fn exported() -> &'static i32 { &REFERENCED } ``` Without optimizations, LLVM pretty much preserves all the static variables and functions within the compilation unit. ``` $ rustc --crate-type=lib --emit=obj symbols.rs && nm -C symbols.o 0000000000000000 t drop::h1be0f8f27a2ba94a 0000000000000000 r symbols::REFERENCED::hb3bdfd46050bc84c 0000000000000000 r symbols::DEAD::hc2ea8f9bd06f380b 0000000000000000 r symbols::LIVE::h0970cf9889edb56e 0000000000000000 T symbols::exported::h6f096c2b1fc292b2 0000000000000000 t symbols::internal::h0ac1aadbc1e3a494 ``` With optimizations, LLVM will drop dead code. Here `internal` is dropped because it's not a exported function/symbol (i.e. not `pub`lic). `DEAD` is dropped for the same reason. `REFERENCED` is preserved, even though it's not exported, because it's referenced by the `exported` function. Finally, `LIVE` survives because of the `#[used]` attribute even though it's not exported or referenced. ``` $ rustc --crate-type=lib -C opt-level=3 --emit=obj symbols.rs && nm -C symbols.o 0000000000000000 r symbols::REFERENCED::hb3bdfd46050bc84c 0000000000000000 r symbols::LIVE::h0970cf9889edb56e 0000000000000000 T symbols::exported::h6f096c2b1fc292b2 ``` Note that the linker knows nothing about `#[used]` and will drop `LIVE` because no other object references to it. ``` $ echo 'fn main() {}' >> symbols.rs $ rustc symbols.rs && nm -C symbols | grep LIVE ``` At this time, `#[used]` only works on `static` variables. --- src/librustc_trans/base.rs | 20 +++++++++++++++++++- src/librustc_trans/consts.rs | 4 ++++ src/librustc_trans/context.rs | 7 +++++++ src/libsyntax/feature_gate.rs | 8 ++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/librustc_trans/base.rs b/src/librustc_trans/base.rs index ec45c5593632e..63258b7453317 100644 --- a/src/librustc_trans/base.rs +++ b/src/librustc_trans/base.rs @@ -50,7 +50,7 @@ use builder::Builder; use callee; use common::{C_bool, C_bytes_in_context, C_i32, C_uint}; use collector::{self, TransItemCollectionMode}; -use common::{C_struct_in_context, C_u64, C_undef}; +use common::{C_struct_in_context, C_u64, C_undef, C_array}; use common::CrateContext; use common::{type_is_zero_size, val_ty}; use common; @@ -1187,6 +1187,24 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, } } + // Create llvm.used variable + if !ccx.used_statics().borrow().is_empty() { + debug!("llvm.used"); + + let name = CString::new("llvm.used").unwrap(); + let section = CString::new("llvm.metadata").unwrap(); + let array = C_array(Type::i8(&ccx).ptr_to(), &*ccx.used_statics().borrow()); + + unsafe { + let g = llvm::LLVMAddGlobal(ccx.llmod(), + val_ty(array).to_ref(), + name.as_ptr()); + llvm::LLVMSetInitializer(g, array); + llvm::LLVMRustSetLinkage(g, llvm::Linkage::AppendingLinkage); + llvm::LLVMSetSection(g, section.as_ptr()); + } + } + // Finalize debuginfo if ccx.sess().opts.debuginfo != NoDebugInfo { debuginfo::finalize(&ccx); diff --git a/src/librustc_trans/consts.rs b/src/librustc_trans/consts.rs index 0c3d211912add..9974155f7c07d 100644 --- a/src/librustc_trans/consts.rs +++ b/src/librustc_trans/consts.rs @@ -276,6 +276,10 @@ pub fn trans_static<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, base::set_link_section(ccx, g, attrs); + if attr::contains_name(attrs, "used") { + ccx.used_statics().borrow_mut().push(g); + } + Ok(g) } } diff --git a/src/librustc_trans/context.rs b/src/librustc_trans/context.rs index 73602dc420b3f..2eca0a18e2b38 100644 --- a/src/librustc_trans/context.rs +++ b/src/librustc_trans/context.rs @@ -132,6 +132,8 @@ pub struct LocalCrateContext<'tcx> { /// to constants.) statics_to_rauw: RefCell>, + used_statics: RefCell>, + lltypes: RefCell, Type>>, llsizingtypes: RefCell, Type>>, type_hashcodes: RefCell, String>>, @@ -587,6 +589,7 @@ impl<'tcx> LocalCrateContext<'tcx> { impl_method_cache: RefCell::new(FxHashMap()), closure_bare_wrapper_cache: RefCell::new(FxHashMap()), statics_to_rauw: RefCell::new(Vec::new()), + used_statics: RefCell::new(Vec::new()), lltypes: RefCell::new(FxHashMap()), llsizingtypes: RefCell::new(FxHashMap()), type_hashcodes: RefCell::new(FxHashMap()), @@ -754,6 +757,10 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> { &self.local().statics_to_rauw } + pub fn used_statics<'a>(&'a self) -> &'a RefCell> { + &self.local().used_statics + } + pub fn lltypes<'a>(&'a self) -> &'a RefCell, Type>> { &self.local().lltypes } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 12d25ca4274fe..66a813025c437 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -337,11 +337,15 @@ declare_features! ( // `extern "x86-interrupt" fn()` (active, abi_x86_interrupt, "1.17.0", Some(40180)), + // Allows the `catch {...}` expression (active, catch_expr, "1.17.0", Some(31436)), // See rust-lang/rfcs#1414. Allows code like `let x: &'static u32 = &42` to work. (active, rvalue_static_promotion, "1.15.1", Some(38865)), + + // Used to preserve symbols + (active, used, "1.18.0", None), ); declare_features! ( @@ -748,6 +752,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG "unwind_attributes", "#[unwind] is experimental", cfg_fn!(unwind_attributes))), + ("used", Whitelisted, Gated( + Stability::Unstable, "used", + "the `#[used]` attribute is an experimental feature", + cfg_fn!(used))), // used in resolve ("prelude_import", Whitelisted, Gated(Stability::Unstable, From bc1bd8a609823814079996ca3ca0b05774e07a74 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Sun, 5 Mar 2017 23:03:42 -0500 Subject: [PATCH 2/6] add tracking issue and feature-gate and run-make tests --- src/test/compile-fail/feature-gate-used.rs | 15 +++++++++++++++ src/test/run-make/used/Makefile | 12 ++++++++++++ src/test/run-make/used/used.rs | 17 +++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/test/compile-fail/feature-gate-used.rs create mode 100644 src/test/run-make/used/Makefile create mode 100644 src/test/run-make/used/used.rs diff --git a/src/test/compile-fail/feature-gate-used.rs b/src/test/compile-fail/feature-gate-used.rs new file mode 100644 index 0000000000000..68679d7dac896 --- /dev/null +++ b/src/test/compile-fail/feature-gate-used.rs @@ -0,0 +1,15 @@ +// Copyright 2017 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[used] +fn foo() {} +//~^^ ERROR the `#[used]` attribute is an experimental feature + +fn main() {} diff --git a/src/test/run-make/used/Makefile b/src/test/run-make/used/Makefile new file mode 100644 index 0000000000000..70ac2e3802b81 --- /dev/null +++ b/src/test/run-make/used/Makefile @@ -0,0 +1,12 @@ +-include ../tools.mk + +ifdef IS_WINDOWS +# Do nothing on MSVC. +all: + exit 0 +else +all: + $(RUSTC) -C opt-level=3 --emit=obj used.rs + nm -C used.o | grep FOO + nm -C used.o | grep -v BAR +endif diff --git a/src/test/run-make/used/used.rs b/src/test/run-make/used/used.rs new file mode 100644 index 0000000000000..186cd0fdf5e35 --- /dev/null +++ b/src/test/run-make/used/used.rs @@ -0,0 +1,17 @@ +// Copyright 2017 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_type = "lib"] +#![feature(used)] + +#[used] +static FOO: u32 = 0; + +static BAR: u32 = 0; From c759eea7a60941f28e7e7a370ba95eeae06ea013 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 6 Mar 2017 11:18:56 -0500 Subject: [PATCH 3/6] fix location of the emitted object file --- src/test/run-make/used/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/run-make/used/Makefile b/src/test/run-make/used/Makefile index 70ac2e3802b81..650464e4d8454 100644 --- a/src/test/run-make/used/Makefile +++ b/src/test/run-make/used/Makefile @@ -7,6 +7,6 @@ all: else all: $(RUSTC) -C opt-level=3 --emit=obj used.rs - nm -C used.o | grep FOO - nm -C used.o | grep -v BAR + nm -C $(TMPDIR)/used.o | grep FOO + nm -C $(TMPDIR)/used.o | grep -v BAR endif From c1635d7e61533550d6c58d4f92a01d1e6acd28e6 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 5 Apr 2017 21:02:52 -0500 Subject: [PATCH 4/6] cast the #[used] static to *i8 to match the type signature of the llvm.used variable --- src/librustc_trans/base.rs | 2 -- src/librustc_trans/consts.rs | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/librustc_trans/base.rs b/src/librustc_trans/base.rs index 63258b7453317..378e1d7fc63f0 100644 --- a/src/librustc_trans/base.rs +++ b/src/librustc_trans/base.rs @@ -1189,8 +1189,6 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, // Create llvm.used variable if !ccx.used_statics().borrow().is_empty() { - debug!("llvm.used"); - let name = CString::new("llvm.used").unwrap(); let section = CString::new("llvm.metadata").unwrap(); let array = C_array(Type::i8(&ccx).ptr_to(), &*ccx.used_statics().borrow()); diff --git a/src/librustc_trans/consts.rs b/src/librustc_trans/consts.rs index 9974155f7c07d..ae8c2433fed75 100644 --- a/src/librustc_trans/consts.rs +++ b/src/librustc_trans/consts.rs @@ -277,7 +277,8 @@ pub fn trans_static<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, base::set_link_section(ccx, g, attrs); if attr::contains_name(attrs, "used") { - ccx.used_statics().borrow_mut().push(g); + let cast = llvm::LLVMConstPointerCast(g, Type::i8p(ccx).to_ref()); + ccx.used_statics().borrow_mut().push(cast); } Ok(g) From ecddad6920b7640ff0398a52a808703db3d4e62a Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 5 Apr 2017 21:06:53 -0500 Subject: [PATCH 5/6] don't test for the absence of BAR in the rmake test it's not related to this feature --- src/test/run-make/used/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/run-make/used/Makefile b/src/test/run-make/used/Makefile index 650464e4d8454..5fe09e95a828d 100644 --- a/src/test/run-make/used/Makefile +++ b/src/test/run-make/used/Makefile @@ -8,5 +8,4 @@ else all: $(RUSTC) -C opt-level=3 --emit=obj used.rs nm -C $(TMPDIR)/used.o | grep FOO - nm -C $(TMPDIR)/used.o | grep -v BAR endif From bbe54115873eca9d9a889be3d9eff0c01d2ba8be Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 5 Apr 2017 21:11:22 -0500 Subject: [PATCH 6/6] document the implementation a bit more --- src/librustc_trans/base.rs | 3 ++- src/librustc_trans/consts.rs | 1 + src/librustc_trans/context.rs | 2 ++ src/libsyntax/feature_gate.rs | 4 ++-- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/librustc_trans/base.rs b/src/librustc_trans/base.rs index 378e1d7fc63f0..d204703b77598 100644 --- a/src/librustc_trans/base.rs +++ b/src/librustc_trans/base.rs @@ -1187,7 +1187,8 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, } } - // Create llvm.used variable + // Create the llvm.used variable + // This variable has type [N x i8*] and is stored in the llvm.metadata section if !ccx.used_statics().borrow().is_empty() { let name = CString::new("llvm.used").unwrap(); let section = CString::new("llvm.metadata").unwrap(); diff --git a/src/librustc_trans/consts.rs b/src/librustc_trans/consts.rs index ae8c2433fed75..daf1a1ba95f9a 100644 --- a/src/librustc_trans/consts.rs +++ b/src/librustc_trans/consts.rs @@ -277,6 +277,7 @@ pub fn trans_static<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, base::set_link_section(ccx, g, attrs); if attr::contains_name(attrs, "used") { + // This static will be stored in the llvm.used variable which is an array of i8* let cast = llvm::LLVMConstPointerCast(g, Type::i8p(ccx).to_ref()); ccx.used_statics().borrow_mut().push(cast); } diff --git a/src/librustc_trans/context.rs b/src/librustc_trans/context.rs index 2eca0a18e2b38..afb94f546abe8 100644 --- a/src/librustc_trans/context.rs +++ b/src/librustc_trans/context.rs @@ -132,6 +132,8 @@ pub struct LocalCrateContext<'tcx> { /// to constants.) statics_to_rauw: RefCell>, + /// Statics that will be placed in the llvm.used variable + /// See http://llvm.org/docs/LangRef.html#the-llvm-used-global-variable for details used_statics: RefCell>, lltypes: RefCell, Type>>, diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 66a813025c437..5f71900120386 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -344,8 +344,8 @@ declare_features! ( // See rust-lang/rfcs#1414. Allows code like `let x: &'static u32 = &42` to work. (active, rvalue_static_promotion, "1.15.1", Some(38865)), - // Used to preserve symbols - (active, used, "1.18.0", None), + // Used to preserve symbols (see llvm.used) + (active, used, "1.18.0", Some(40289)), ); declare_features! (