diff --git a/src/librustc/front/feature_gate.rs b/src/librustc/front/feature_gate.rs
index 15056d9d2d842..6873d9b68e7a5 100644
--- a/src/librustc/front/feature_gate.rs
+++ b/src/librustc/front/feature_gate.rs
@@ -43,6 +43,7 @@ static KNOWN_FEATURES: &'static [(&'static str, Status)] = &[
     ("non_ascii_idents", Active),
     ("thread_local", Active),
     ("link_args", Active),
+    ("linkage", Active),
     ("phase", Active),
     ("macro_registrar", Active),
     ("log_syntax", Active),
@@ -188,6 +189,18 @@ impl Visitor<()> for Context {
         visit::walk_item(self, i, ());
     }
 
+    fn visit_foreign_item(&mut self, i: &ast::ForeignItem, _: ()) {
+        match i.node {
+            ast::ForeignItemFn(..) | ast::ForeignItemStatic(..) => {
+                if attr::contains_name(i.attrs, "linkage") {
+                    self.gate_feature("linkage", i.span,
+                                      "the `linkage` attribute is experimental \
+                                       and not portable across platforms")
+                }
+            },
+        }
+    }
+
     fn visit_mac(&mut self, macro: &ast::Mac, _: ()) {
         let ast::MacInvocTT(ref path, _, _) = macro.node;
         let id = path.segments.last().unwrap().identifier;
diff --git a/src/librustc/middle/lint.rs b/src/librustc/middle/lint.rs
index e674af6b3b3e3..3c700558c60b9 100644
--- a/src/librustc/middle/lint.rs
+++ b/src/librustc/middle/lint.rs
@@ -974,7 +974,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",
diff --git a/src/librustc/middle/trans/foreign.rs b/src/librustc/middle/trans/foreign.rs
index bc9dd767ec670..3630dd6e89fe5 100644
--- a/src/librustc/middle/trans/foreign.rs
+++ b/src/librustc/middle/trans/foreign.rs
@@ -11,7 +11,7 @@
 
 use back::{link};
 use lib::llvm::llvm;
-use lib::llvm::{ValueRef, CallConv, StructRetAttribute};
+use lib::llvm::{ValueRef, CallConv, Linkage, StructRetAttribute};
 use lib;
 use middle::trans::base::push_ctxt;
 use middle::trans::base;
@@ -106,6 +106,34 @@ 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_foreign_item_fn(ccx: @CrateContext,
                                 abis: AbiSet,
                                 path: &ast_map::Path,
@@ -158,6 +186,18 @@ pub fn register_foreign_item_fn(ccx: @CrateContext,
                                    llfn_ty,
                                    tys.fn_sig.output);
     };
+
+    match attr::first_attr_value_str_by_name(foreign_item.attrs, "linkage") {
+        Some(name) => {
+            match llvm_linkage_by_name(name.get()) {
+                Some(linkage) => lib::llvm::SetLinkage(llfn, linkage),
+                None => ccx.sess.span_fatal(foreign_item.span,
+                                            format!("Bad linkage `{}`", name)),
+            }
+        },
+        None => {},  // Default "external" linkage.
+    }
+
     add_argument_attributes(&tys, llfn);
 
     return llfn;
diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs
index adce11fed2dad..7828cd5202f7a 100644
--- a/src/libstd/lib.rs
+++ b/src/libstd/lib.rs
@@ -52,7 +52,14 @@
       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(asm,
+          globs,
+          link_args,
+          linkage,
+          macro_rules,
+          managed_boxes,
+          simd,
+          thread_local)];
 
 // Don't link to std. We are std.
 #[no_std];
diff --git a/src/libstd/rt/thread.rs b/src/libstd/rt/thread.rs
index b762c1173f56b..70411054a13ae 100644
--- a/src/libstd/rt/thread.rs
+++ b/src/libstd/rt/thread.rs
@@ -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,51 +261,40 @@ 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.
+    // 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")]
-    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),
-                }
+    fn min_stack_size(attr: *libc::pthread_attr_t) -> libc::size_t {
+        extern {
+            #[linkage = "extern_weak"]
+            fn __pthread_get_minstack(_: *libc::pthread_attr_t) -> libc::size_t;
+        }
+        unsafe fn is_null<T>(thing: T) -> bool {
+            cast::transmute::<T, *u8>(thing) == ptr::null()
+        }
+        unsafe {
+            match is_null(__pthread_get_minstack) {
+                true => PTHREAD_STACK_MIN,
+                false => __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"))]
+    fn min_stack_size(_: *libc::pthread_attr_t) -> libc::size_t {
+        PTHREAD_STACK_MIN
+    }
+
     extern {
         fn pthread_create(native: *mut libc::pthread_t,
                           attr: *libc::pthread_attr_t,
diff --git a/src/test/run-pass/linkage.rs b/src/test/run-pass/linkage.rs
new file mode 100644
index 0000000000000..170d97bad9a4e
--- /dev/null
+++ b/src/test/run-pass/linkage.rs
@@ -0,0 +1,43 @@
+// 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)];
+
+use std::cast;
+use std::ptr;
+
+fn external() {
+    extern {
+        #[linkage = "external"]
+        fn abort();
+    }
+    let ptr: *u8 = unsafe { cast::transmute(abort) };
+    assert!(ptr != ptr::null());
+}
+
+#[cfg(target_os = "linux")]
+fn extern_weak() {
+    extern {
+        #[linkage = "extern_weak"]
+        fn frobnitz();
+    }
+    let ptr: *u8 = unsafe { cast::transmute(frobnitz) };
+    assert!(ptr == ptr::null());
+}
+
+#[cfg(not(target_os = "linux"))]
+fn extern_weak() {
+    // Not supported.
+}
+
+fn main() {
+    external();
+    extern_weak();
+}