Skip to content

Commit

Permalink
Auto merge of #39987 - japaric:used, r=arielb1
Browse files Browse the repository at this point in the history
#[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
  • Loading branch information
bors committed Apr 7, 2017
2 parents 2277f4b + 98037ca commit b9c5197
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/doc/unstable-book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@
- [unwind_attributes](unwind-attributes.md)
- [update_panic_count](update-panic-count.md)
- [use_extern_macros](use-extern-macros.md)
- [used](used.md)
- [utf8_error_error_len](utf8-error-error-len.md)
- [vec_remove_item](vec-remove-item.md)
- [windows_c](windows-c.md)
Expand Down
153 changes: 153 additions & 0 deletions src/doc/unstable-book/src/used.md
Original file line number Diff line number Diff line change
@@ -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>
```
19 changes: 18 additions & 1 deletion src/librustc_trans/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
if ccx.sess().opts.debuginfo != NoDebugInfo {
debuginfo::finalize(&ccx);
Expand Down
6 changes: 6 additions & 0 deletions src/librustc_trans/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ 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);
}

Ok(g)
}
}
9 changes: 9 additions & 0 deletions src/librustc_trans/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub struct LocalCrateContext<'tcx> {
/// to constants.)
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>>,
llsizingtypes: RefCell<FxHashMap<Ty<'tcx>, Type>>,
type_hashcodes: RefCell<FxHashMap<Ty<'tcx>, String>>,
Expand Down Expand Up @@ -587,6 +591,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()),
Expand Down Expand Up @@ -754,6 +759,10 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
&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>> {
&self.local().lltypes
}
Expand Down
8 changes: 8 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,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 (see llvm.used)
(active, used, "1.18.0", Some(40289)),
);

declare_features! (
Expand Down Expand Up @@ -746,6 +750,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,
Expand Down
15 changes: 15 additions & 0 deletions src/test/compile-fail/feature-gate-used.rs
Original file line number Diff line number Diff line change
@@ -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() {}
11 changes: 11 additions & 0 deletions src/test/run-make/used/Makefile
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions src/test/run-make/used/used.rs
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit b9c5197

Please sign in to comment.