Auto merge of #39987 - japaric:used, r=arielb1
#[used] attribute (For an explanation of what this feature does, read the commit message) I'd like to propose landing this as an experimental feature (experimental as in: no clear stabilization path -- like `asm!`, `#[linkage]`) as it's low maintenance (I think) and relevant to the "Usage in resource-constrained environments" exploration area. The main use case I see is running code before `main`. This could be used, for instance, to cheaply initialize an allocator before `main` where the alternative is to use `lazy_static` to initialize the allocator on its first use which it's more expensive (atomics) and doesn't work on ARM Cortex-M0 microcontrollers (no `AtomicUsize` on that platform) Here's a `std` example of that: ``` rust unsafe extern "C" fn before_main_1() { println!("Hello"); } unsafe extern "C" fn before_main_2() { println!("World"); } #[link_section = ".init_arary"] #[used] static INIT_ARRAY: [unsafe extern "C" fn(); 2] = [before_main_1, before_main_2]; fn main() { println!("Goodbye"); } ``` ``` $ rustc -C lto -C opt-level=3 before_main.rs $ ./before_main Hello World Goodbye ``` In general, this pattern could be used to let *dependencies* run code before `main` (which sounds like it could go very wrong in some cases). There are probably other use cases; I hope that the people I have cc-ed can comment on those. Note that I'm personally unsure if the above pattern is something we want to promote / allow and that's why I'm proposing this feature as experimental. If this leads to more footguns than benefits then we can just axe the feature. cc @nikomatsakis ^ I know you have some thoughts on having a process for experimental features though I'm fine with writing an RFC before landing this. - `dead_code` lint will have to be updated to special case `#[used]` symbols. - Should we extend `#[used]` to work on non-generic functions? cc rust-lang/rfcs#1002 cc rust-lang/rfcs#1459 cc @dpc @JinShil
This commit is contained in:
commit
b9c5197d48
|
@ -205,6 +205,7 @@
|
||||||
- [unwind_attributes](unwind-attributes.md)
|
- [unwind_attributes](unwind-attributes.md)
|
||||||
- [update_panic_count](update-panic-count.md)
|
- [update_panic_count](update-panic-count.md)
|
||||||
- [use_extern_macros](use-extern-macros.md)
|
- [use_extern_macros](use-extern-macros.md)
|
||||||
|
- [used](used.md)
|
||||||
- [utf8_error_error_len](utf8-error-error-len.md)
|
- [utf8_error_error_len](utf8-error-error-len.md)
|
||||||
- [vec_remove_item](vec-remove-item.md)
|
- [vec_remove_item](vec-remove-item.md)
|
||||||
- [windows_c](windows-c.md)
|
- [windows_c](windows-c.md)
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
# `used`
|
||||||
|
|
||||||
|
The tracking issue for this feature
|
||||||
|
is: [40289](https://github.com/rust-lang/rust/issues/40289).
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The `#[used]` attribute can be applied to `static` variables to prevent the Rust
|
||||||
|
compiler from optimizing them away even if they appear to be unused by the crate
|
||||||
|
(appear to be "dead code").
|
||||||
|
|
||||||
|
``` rust
|
||||||
|
#![feature(used)]
|
||||||
|
|
||||||
|
#[used]
|
||||||
|
static FOO: i32 = 1;
|
||||||
|
|
||||||
|
static BAR: i32 = 2;
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you compile this program into an object file, you'll see that `FOO` makes it
|
||||||
|
to the object file but `BAR` doesn't. Neither static variable is used by the
|
||||||
|
program.
|
||||||
|
|
||||||
|
``` text
|
||||||
|
$ rustc -C opt-level=3 --emit=obj used.rs
|
||||||
|
|
||||||
|
$ nm -C used.o
|
||||||
|
0000000000000000 T main
|
||||||
|
U std::rt::lang_start
|
||||||
|
0000000000000000 r used::FOO
|
||||||
|
0000000000000000 t used::main
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the *linker* knows nothing about the `#[used]` attribute and will
|
||||||
|
remove `#[used]` symbols if they are not referenced by other parts of the
|
||||||
|
program:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
$ rustc -C opt-level=3 used.rs
|
||||||
|
|
||||||
|
$ nm -C used | grep FOO
|
||||||
|
```
|
||||||
|
|
||||||
|
"This doesn't sound too useful then!" you may think but keep reading.
|
||||||
|
|
||||||
|
To preserve the symbols all the way to the final binary, you'll need the
|
||||||
|
cooperation of the linker. Here's one example:
|
||||||
|
|
||||||
|
The ELF standard defines two special sections, `.init_array` and
|
||||||
|
`.pre_init_array`, that may contain function pointers which will be executed
|
||||||
|
*before* the `main` function is invoked. The linker will preserve symbols placed
|
||||||
|
in these sections (at least when linking programs that target the `*-*-linux-*`
|
||||||
|
targets).
|
||||||
|
|
||||||
|
``` rust,ignore
|
||||||
|
#![feature(used)]
|
||||||
|
|
||||||
|
extern "C" fn before_main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link_section = ".init_array"]
|
||||||
|
#[used]
|
||||||
|
static INIT_ARRAY: [extern "C" fn(); 1] = [before_main];
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
So, `#[used]` and `#[link_section]` can be combined to obtain "life before
|
||||||
|
main".
|
||||||
|
|
||||||
|
``` text
|
||||||
|
$ rustc -C opt-level=3 before-main.rs
|
||||||
|
|
||||||
|
$ ./before-main
|
||||||
|
Hello, world!
|
||||||
|
```
|
||||||
|
|
||||||
|
Another example: ARM Cortex-M microcontrollers need their reset handler, a
|
||||||
|
pointer to the function that will executed right after the microcontroller is
|
||||||
|
turned on, to be placed near the start of their FLASH memory to boot properly.
|
||||||
|
|
||||||
|
This condition can be met using `#[used]` and `#[link_section]` plus a linker
|
||||||
|
script.
|
||||||
|
|
||||||
|
``` rust,ignore
|
||||||
|
#![feature(lang_items)]
|
||||||
|
#![feature(used)]
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
extern "C" fn reset_handler() -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link_section = ".reset_handler"]
|
||||||
|
#[used]
|
||||||
|
static RESET_HANDLER: extern "C" fn() -> ! = reset_handler;
|
||||||
|
|
||||||
|
#[lang = "panic_fmt"]
|
||||||
|
fn panic_fmt() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` text
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
|
RAM : ORIGIN = 0x20000000, LENGTH = 20K
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text ORIGIN(FLASH) :
|
||||||
|
{
|
||||||
|
/* Vector table */
|
||||||
|
LONG(ORIGIN(RAM) + LENGTH(RAM)); /* initial SP value */
|
||||||
|
KEEP(*(.reset_handler));
|
||||||
|
|
||||||
|
/* Omitted: The rest of the vector table */
|
||||||
|
|
||||||
|
*(.text.*);
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
/DISCARD/ :
|
||||||
|
{
|
||||||
|
/* Unused unwinding stuff */
|
||||||
|
*(.ARM.exidx.*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` text
|
||||||
|
$ xargo rustc --target thumbv7m-none-eabi --release -- \
|
||||||
|
-C link-arg=-Tlink.x -C link-arg=-nostartfiles
|
||||||
|
|
||||||
|
$ arm-none-eabi-objdump -Cd target/thumbv7m-none-eabi/release/app
|
||||||
|
./target/thumbv7m-none-eabi/release/app: file format elf32-littlearm
|
||||||
|
|
||||||
|
|
||||||
|
Disassembly of section .text:
|
||||||
|
|
||||||
|
08000000 <app::RESET_HANDLER-0x4>:
|
||||||
|
8000000: 20005000 .word 0x20005000
|
||||||
|
|
||||||
|
08000004 <app::RESET_HANDLER>:
|
||||||
|
8000004: 08000009 ....
|
||||||
|
|
||||||
|
08000008 <app::reset_handler>:
|
||||||
|
8000008: e7fe b.n 8000008 <app::reset_handler>
|
||||||
|
```
|
|
@ -50,7 +50,7 @@ use builder::Builder;
|
||||||
use callee;
|
use callee;
|
||||||
use common::{C_bool, C_bytes_in_context, C_i32, C_uint};
|
use common::{C_bool, C_bytes_in_context, C_i32, C_uint};
|
||||||
use collector::{self, TransItemCollectionMode};
|
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::CrateContext;
|
||||||
use common::{type_is_zero_size, val_ty};
|
use common::{type_is_zero_size, val_ty};
|
||||||
use common;
|
use common;
|
||||||
|
@ -1187,6 +1187,23 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
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
|
// Finalize debuginfo
|
||||||
if ccx.sess().opts.debuginfo != NoDebugInfo {
|
if ccx.sess().opts.debuginfo != NoDebugInfo {
|
||||||
debuginfo::finalize(&ccx);
|
debuginfo::finalize(&ccx);
|
||||||
|
|
|
@ -276,6 +276,12 @@ pub fn trans_static<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
|
||||||
|
|
||||||
base::set_link_section(ccx, g, attrs);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(g)
|
Ok(g)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,10 @@ pub struct LocalCrateContext<'tcx> {
|
||||||
/// to constants.)
|
/// to constants.)
|
||||||
statics_to_rauw: RefCell<Vec<(ValueRef, ValueRef)>>,
|
statics_to_rauw: RefCell<Vec<(ValueRef, ValueRef)>>,
|
||||||
|
|
||||||
|
/// 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<Vec<ValueRef>>,
|
||||||
|
|
||||||
lltypes: RefCell<FxHashMap<Ty<'tcx>, Type>>,
|
lltypes: RefCell<FxHashMap<Ty<'tcx>, Type>>,
|
||||||
llsizingtypes: RefCell<FxHashMap<Ty<'tcx>, Type>>,
|
llsizingtypes: RefCell<FxHashMap<Ty<'tcx>, Type>>,
|
||||||
type_hashcodes: RefCell<FxHashMap<Ty<'tcx>, String>>,
|
type_hashcodes: RefCell<FxHashMap<Ty<'tcx>, String>>,
|
||||||
|
@ -587,6 +591,7 @@ impl<'tcx> LocalCrateContext<'tcx> {
|
||||||
impl_method_cache: RefCell::new(FxHashMap()),
|
impl_method_cache: RefCell::new(FxHashMap()),
|
||||||
closure_bare_wrapper_cache: RefCell::new(FxHashMap()),
|
closure_bare_wrapper_cache: RefCell::new(FxHashMap()),
|
||||||
statics_to_rauw: RefCell::new(Vec::new()),
|
statics_to_rauw: RefCell::new(Vec::new()),
|
||||||
|
used_statics: RefCell::new(Vec::new()),
|
||||||
lltypes: RefCell::new(FxHashMap()),
|
lltypes: RefCell::new(FxHashMap()),
|
||||||
llsizingtypes: RefCell::new(FxHashMap()),
|
llsizingtypes: RefCell::new(FxHashMap()),
|
||||||
type_hashcodes: RefCell::new(FxHashMap()),
|
type_hashcodes: RefCell::new(FxHashMap()),
|
||||||
|
@ -754,6 +759,10 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
|
||||||
&self.local().statics_to_rauw
|
&self.local().statics_to_rauw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn used_statics<'a>(&'a self) -> &'a RefCell<Vec<ValueRef>> {
|
||||||
|
&self.local().used_statics
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lltypes<'a>(&'a self) -> &'a RefCell<FxHashMap<Ty<'tcx>, Type>> {
|
pub fn lltypes<'a>(&'a self) -> &'a RefCell<FxHashMap<Ty<'tcx>, Type>> {
|
||||||
&self.local().lltypes
|
&self.local().lltypes
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,11 +334,15 @@ declare_features! (
|
||||||
// `extern "x86-interrupt" fn()`
|
// `extern "x86-interrupt" fn()`
|
||||||
(active, abi_x86_interrupt, "1.17.0", Some(40180)),
|
(active, abi_x86_interrupt, "1.17.0", Some(40180)),
|
||||||
|
|
||||||
|
|
||||||
// Allows the `catch {...}` expression
|
// Allows the `catch {...}` expression
|
||||||
(active, catch_expr, "1.17.0", Some(31436)),
|
(active, catch_expr, "1.17.0", Some(31436)),
|
||||||
|
|
||||||
// See rust-lang/rfcs#1414. Allows code like `let x: &'static u32 = &42` to work.
|
// See rust-lang/rfcs#1414. Allows code like `let x: &'static u32 = &42` to work.
|
||||||
(active, rvalue_static_promotion, "1.15.1", Some(38865)),
|
(active, rvalue_static_promotion, "1.15.1", Some(38865)),
|
||||||
|
|
||||||
|
// Used to preserve symbols (see llvm.used)
|
||||||
|
(active, used, "1.18.0", Some(40289)),
|
||||||
);
|
);
|
||||||
|
|
||||||
declare_features! (
|
declare_features! (
|
||||||
|
@ -746,6 +750,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
|
||||||
"unwind_attributes",
|
"unwind_attributes",
|
||||||
"#[unwind] is experimental",
|
"#[unwind] is experimental",
|
||||||
cfg_fn!(unwind_attributes))),
|
cfg_fn!(unwind_attributes))),
|
||||||
|
("used", Whitelisted, Gated(
|
||||||
|
Stability::Unstable, "used",
|
||||||
|
"the `#[used]` attribute is an experimental feature",
|
||||||
|
cfg_fn!(used))),
|
||||||
|
|
||||||
// used in resolve
|
// used in resolve
|
||||||
("prelude_import", Whitelisted, Gated(Stability::Unstable,
|
("prelude_import", Whitelisted, Gated(Stability::Unstable,
|
||||||
|
|
|
@ -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 <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.
|
||||||
|
|
||||||
|
#[used]
|
||||||
|
fn foo() {}
|
||||||
|
//~^^ ERROR the `#[used]` attribute is an experimental feature
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,11 @@
|
||||||
|
-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 $(TMPDIR)/used.o | grep FOO
|
||||||
|
endif
|
|
@ -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 <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 = "lib"]
|
||||||
|
#![feature(used)]
|
||||||
|
|
||||||
|
#[used]
|
||||||
|
static FOO: u32 = 0;
|
||||||
|
|
||||||
|
static BAR: u32 = 0;
|
Loading…
Reference in New Issue