rustc: Support various flavors of linkages

It is often convenient to have forms of weak linkage or other various types of
linkage. Sadly, just using these flavors of linkage are not compatible with
Rust's typesystem and how it considers some pointers to be non-null.

As a compromise, this commit adds support for weak linkage to external symbols,
but it requires that this is only placed on extern statics of type `*T`.
Codegen-wise, we get translations like:

    // rust code
    extern {
        #[linkage = "extern_weak"]
        static foo: *i32;
    }

    // generated IR
    @foo = extern_weak global i32
    @_some_internal_symbol = internal global *i32 @foo

All references to the rust value of `foo` then reference `_some_internal_symbol`
instead of the symbol `_foo` itself. This allows us to guarantee that the
address of `foo` will never be null while the value may sometimes be null.

An example was implemented in `std::rt::thread` to determine if
`__pthread_get_minstack()` is available at runtime, and a test is checked in to
use it for a static value as well. Function pointers a little odd because you
still need to transmute the pointer value to a function pointer, but it's
thankfully better than not having this capability at all.
This commit is contained in:
Alex Crichton 2014-02-25 16:15:10 -08:00
parent 0017056105
commit 699b33d060
11 changed files with 243 additions and 78 deletions

View file

@ -53,6 +53,7 @@ static KNOWN_FEATURES: &'static [(&'static str, Status)] = &[
("simd", Active),
("default_type_params", Active),
("quote", Active),
("linkage", Active),
// These are used to test this portion of the compiler, they don't actually
// mean anything
@ -238,6 +239,19 @@ impl Visitor<()> for Context {
}
}
fn visit_foreign_item(&mut self, i: &ast::ForeignItem, _: ()) {
match i.node {
ast::ForeignItemFn(..) | ast::ForeignItemStatic(..) => {
if attr::contains_name(i.attrs.as_slice(), "linkage") {
self.gate_feature("linkage", i.span,
"the `linkage` attribute is experimental \
and not portable across platforms")
}
}
}
visit::walk_foreign_item(self, i, ())
}
fn visit_ty(&mut self, t: &ast::Ty, _: ()) {
match t.node {
ast::TyClosure(closure) if closure.onceness == ast::Once &&

View file

@ -983,7 +983,7 @@ static other_attrs: &'static [&'static str] = &[
// fn-level
"test", "bench", "should_fail", "ignore", "inline", "lang", "main", "start",
"no_split_stack", "cold", "macro_registrar",
"no_split_stack", "cold", "macro_registrar", "linkage",
// internal attribute: bypass privacy inside items
"!resolve_unexported",

View file

@ -2107,7 +2107,6 @@ pub fn get_item_val(ccx: @CrateContext, id: ast::NodeId) -> ValueRef {
}
ast_map::NodeForeignItem(ni) => {
let ty = ty::node_id_to_type(ccx.tcx, ni.id);
foreign = true;
match ni.node {
@ -2116,41 +2115,7 @@ pub fn get_item_val(ccx: @CrateContext, id: ast::NodeId) -> ValueRef {
foreign::register_foreign_item_fn(ccx, abis, ni)
}
ast::ForeignItemStatic(..) => {
// Treat the crate map static specially in order to
// a weak-linkage-like functionality where it's
// dynamically resolved at runtime. If we're
// building a library, then we declare the static
// with weak linkage, but if we're building a
// library then we've already declared the crate map
// so use that instead.
if attr::contains_name(ni.attrs.as_slice(),
"crate_map") {
if ccx.sess.building_library.get() {
let s = "_rust_crate_map_toplevel";
let g = unsafe {
s.with_c_str(|buf| {
let ty = type_of(ccx, ty);
llvm::LLVMAddGlobal(ccx.llmod,
ty.to_ref(),
buf)
})
};
lib::llvm::SetLinkage(g,
lib::llvm::ExternalWeakLinkage);
g
} else {
ccx.crate_map
}
} else {
let ident = foreign::link_name(ni);
unsafe {
ident.get().with_c_str(|buf| {
let ty = type_of(ccx, ty);
llvm::LLVMAddGlobal(ccx.llmod,
ty.to_ref(), buf)
})
}
}
foreign::register_static(ccx, ni)
}
}
}

View file

@ -11,7 +11,7 @@
use back::{link};
use lib::llvm::llvm;
use lib::llvm::{ValueRef, CallConv, StructRetAttribute};
use lib::llvm::{ValueRef, CallConv, StructRetAttribute, Linkage};
use lib;
use middle::trans::base::push_ctxt;
use middle::trans::base;
@ -105,6 +105,105 @@ pub fn llvm_calling_convention(ccx: &CrateContext,
})
}
pub fn llvm_linkage_by_name(name: &str) -> Option<Linkage> {
// Use the names from src/llvm/docs/LangRef.rst here. Most types are only
// applicable to variable declarations and may not really make sense for
// Rust code in the first place but whitelist them anyway and trust that
// the user knows what s/he's doing. Who knows, unanticipated use cases
// may pop up in the future.
//
// ghost, dllimport, dllexport and linkonce_odr_autohide are not supported
// and don't have to be, LLVM treats them as no-ops.
match name {
"appending" => Some(lib::llvm::AppendingLinkage),
"available_externally" => Some(lib::llvm::AvailableExternallyLinkage),
"common" => Some(lib::llvm::CommonLinkage),
"extern_weak" => Some(lib::llvm::ExternalWeakLinkage),
"external" => Some(lib::llvm::ExternalLinkage),
"internal" => Some(lib::llvm::InternalLinkage),
"linker_private" => Some(lib::llvm::LinkerPrivateLinkage),
"linker_private_weak" => Some(lib::llvm::LinkerPrivateWeakLinkage),
"linkonce" => Some(lib::llvm::LinkOnceAnyLinkage),
"linkonce_odr" => Some(lib::llvm::LinkOnceODRLinkage),
"private" => Some(lib::llvm::PrivateLinkage),
"weak" => Some(lib::llvm::WeakAnyLinkage),
"weak_odr" => Some(lib::llvm::WeakODRLinkage),
_ => None,
}
}
pub fn register_static(ccx: @CrateContext,
foreign_item: @ast::ForeignItem) -> ValueRef {
let ty = ty::node_id_to_type(ccx.tcx, foreign_item.id);
let llty = type_of::type_of(ccx, ty);
// Treat the crate map static specially in order to
// a weak-linkage-like functionality where it's
// dynamically resolved at runtime. If we're
// building a library, then we declare the static
// with weak linkage, but if we're building a
// library then we've already declared the crate map
// so use that instead.
if attr::contains_name(foreign_item.attrs.as_slice(), "crate_map") {
return if ccx.sess.building_library.get() {
let s = "_rust_crate_map_toplevel";
let g = unsafe {
s.with_c_str(|buf| {
llvm::LLVMAddGlobal(ccx.llmod, llty.to_ref(), buf)
})
};
lib::llvm::SetLinkage(g, lib::llvm::ExternalWeakLinkage);
g
} else {
ccx.crate_map
}
}
let ident = link_name(foreign_item);
match attr::first_attr_value_str_by_name(foreign_item.attrs.as_slice(),
"linkage") {
// If this is a static with a linkage specified, then we need to handle
// it a little specially. The typesystem prevents things like &T and
// extern "C" fn() from being non-null, so we can't just declare a
// static and call it a day. Some linkages (like weak) will make it such
// that the static actually has a null value.
Some(name) => {
let linkage = match llvm_linkage_by_name(name.get()) {
Some(linkage) => linkage,
None => {
ccx.sess.span_fatal(foreign_item.span,
"invalid linkage specified");
}
};
let llty2 = match ty::get(ty).sty {
ty::ty_ptr(ref mt) => type_of::type_of(ccx, mt.ty),
_ => {
ccx.sess.span_fatal(foreign_item.span,
"must have type `*T` or `*mut T`");
}
};
unsafe {
let g1 = ident.get().with_c_str(|buf| {
llvm::LLVMAddGlobal(ccx.llmod, llty2.to_ref(), buf)
});
lib::llvm::SetLinkage(g1, linkage);
let real_name = "_rust_extern_with_linkage_" + ident.get();
let g2 = real_name.with_c_str(|buf| {
llvm::LLVMAddGlobal(ccx.llmod, llty.to_ref(), buf)
});
lib::llvm::SetLinkage(g2, lib::llvm::InternalLinkage);
llvm::LLVMSetInitializer(g2, g1);
g2
}
}
None => unsafe {
ident.get().with_c_str(|buf| {
llvm::LLVMAddGlobal(ccx.llmod, llty.to_ref(), buf)
})
}
}
}
pub fn register_foreign_item_fn(ccx: @CrateContext, abis: AbiSet,
foreign_item: @ast::ForeignItem) -> ValueRef {

View file

@ -52,10 +52,9 @@
html_favicon_url = "http://www.rust-lang.org/favicon.ico",
html_root_url = "http://static.rust-lang.org/doc/master")];
#[feature(macro_rules, globs, asm, managed_boxes, thread_local, link_args, simd)];
#[feature(macro_rules, globs, asm, managed_boxes, thread_local, link_args,
simd, linkage, default_type_params)];
// Turn on default type parameters.
#[feature(default_type_params)];
// NOTE remove the following two attributes after the next snapshot.
#[allow(unrecognized_lint)];
#[allow(default_type_param_usage)];

View file

@ -221,7 +221,7 @@ mod imp {
PTHREAD_CREATE_JOINABLE), 0);
// Reserve room for the red zone, the runtime's stack of last resort.
let stack_size = cmp::max(stack, RED_ZONE + __pthread_get_minstack(&attr) as uint);
let stack_size = cmp::max(stack, RED_ZONE + min_stack_size(&attr) as uint);
match pthread_attr_setstacksize(&mut attr, stack_size as libc::size_t) {
0 => {
},
@ -261,49 +261,37 @@ mod imp {
#[cfg(not(target_os = "macos"), not(target_os = "android"))]
pub unsafe fn yield_now() { assert_eq!(pthread_yield(), 0); }
#[cfg(not(target_os = "linux"))]
unsafe fn __pthread_get_minstack(_: *libc::pthread_attr_t) -> libc::size_t {
libc::PTHREAD_STACK_MIN
}
// glibc >= 2.15 has a __pthread_get_minstack() function that returns
// PTHREAD_STACK_MIN plus however many bytes are needed for thread-local
// storage. We need that information to avoid blowing up when a small stack
// is created in an application with big thread-local storage requirements.
// See #6233 for rationale and details.
//
// Dynamically resolve the symbol for compatibility with older versions
// of glibc. Assumes that we've been dynamically linked to libpthread
// but that is currently always the case. Note that this means we take
// a dlopen/dlsym/dlclose hit for every new thread. Mitigating that by
// caching the symbol or the function's return value has its drawbacks:
//
// * Caching the symbol breaks when libpthread.so is reloaded because
// its address changes.
//
// * Caching the return value assumes that it's a fixed quantity.
// Not very future-proof and untrue in the presence of guard pages
// The reason __pthread_get_minstack() takes a *libc::pthread_attr_t
// as its argument is because it takes pthread_attr_setguardsize() into
// account.
//
// A better solution is to define __pthread_get_minstack() as a weak symbol
// but there is currently no way to express that in Rust code.
#[cfg(target_os = "linux")]
unsafe fn __pthread_get_minstack(attr: *libc::pthread_attr_t) -> libc::size_t {
use option::None;
use result::{Err, Ok};
use unstable::dynamic_lib;
match dynamic_lib::DynamicLibrary::open(None) {
Err(err) => fail!("DynamicLibrary::open(): {}", err),
Ok(handle) => {
match handle.symbol::<extern "C" fn(*libc::pthread_attr_t) ->
libc::size_t>("__pthread_get_minstack") {
Err(_) => libc::PTHREAD_STACK_MIN,
Ok(__pthread_get_minstack) => __pthread_get_minstack(attr),
}
}
// Link weakly to the symbol for compatibility with older versions of glibc.
// Assumes that we've been dynamically linked to libpthread but that is
// currently always the case. Note that you need to check that the symbol
// is non-null before calling it!
#[cfg(target_os = "linux", not(stage0))]
fn min_stack_size(attr: *libc::pthread_attr_t) -> libc::size_t {
use ptr::RawPtr;
type F = extern "C" unsafe fn(*libc::pthread_attr_t) -> libc::size_t;
extern {
#[linkage = "extern_weak"]
static __pthread_get_minstack: *();
}
if __pthread_get_minstack.is_null() {
PTHREAD_STACK_MIN
} else {
unsafe { cast::transmute::<*(), F>(__pthread_get_minstack)(attr) }
}
}
// __pthread_get_minstack() is marked as weak but extern_weak linkage is
// not supported on OS X, hence this kludge...
#[cfg(not(target_os = "linux"))]
#[cfg(stage0)]
fn min_stack_size(_: *libc::pthread_attr_t) -> libc::size_t {
PTHREAD_STACK_MIN
}
extern {
@ -347,3 +335,4 @@ mod tests {
assert_eq!(42, Thread::start_stack(1, proc () 42).join());
}
}

View file

@ -0,0 +1,12 @@
// Copyright 2014 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.
#[no_mangle]
pub static foo: int = 3;

View file

@ -0,0 +1,14 @@
// Copyright 2014 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.
extern {
#[linkage = "extern_weak"] static foo: int;
//~^ ERROR: the `linkage` attribute is experimental and not portable
}

View file

@ -0,0 +1,20 @@
// Copyright 2014 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(linkage)];
extern {
#[linkage = "extern_weak"] static foo: i32;
//~^ ERROR: must have type `*T`
}
fn main() {
println!("{}", foo);
}

View file

@ -0,0 +1,21 @@
// Copyright 2014 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(linkage)];
extern {
#[linkage = "foo"] static foo: *i32;
//~^ ERROR: invalid linkage specified
}
fn main() {
println!("{}", foo);
}

View file

@ -0,0 +1,32 @@
// Copyright 2014 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.
// ignore-win32
// ignore-fast
// ignore-android
// ignore-macos
// aux-build:linkage1.rs
#[feature(linkage)];
extern crate other = "linkage1";
extern {
#[linkage = "extern_weak"]
static foo: *int;
#[linkage = "extern_weak"]
static something_that_should_never_exist: *mut int;
}
fn main() {
assert!(!foo.is_null());
assert_eq!(unsafe { *foo }, 3);
assert!(something_that_should_never_exist.is_null());
}