about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2019-03-08 05:46:08 +0000
committerbors <bors@rust-lang.org>2019-03-08 05:46:08 +0000
commit0547ceb200f0385ba437e1808b468ec81e683c64 (patch)
tree48abe82915f918cf06b7f419d38a9b663e806737
parent88f755f8a84df1d9e6b17cf10c96ae8b93481b2e (diff)
parentf2ef283b7265a35ee4b6e250e1160ba28b33953a (diff)
Auto merge of #58013 - Zoxc:stable-hash-macro-simple, r=oli-obk
Create a derive macro for HashStable and allow proc macros in rustc

A combination of https://github.com/rust-lang/rust/pull/56864 and https://github.com/rust-lang/rust/pull/56795. There were complications with using `serde_derive` as rustc doesn't know which crate to use for the host when there is a serde_derive in the sysroot and cargo passes another on the command line built from crates.io.

r? @eddyb (for proc macro changes) @alexcrichton (for rustbuild changes) @michaelwoerister (for the macro itself)
-rw-r--r--Cargo.lock11
-rw-r--r--src/bootstrap/bin/rustc.rs21
-rw-r--r--src/bootstrap/builder.rs11
-rw-r--r--src/bootstrap/check.rs12
-rw-r--r--src/bootstrap/compile.rs74
-rw-r--r--src/bootstrap/lib.rs7
-rw-r--r--src/bootstrap/tool.rs3
-rw-r--r--src/librustc/Cargo.toml1
-rw-r--r--src/librustc/hir/mod.rs3
-rw-r--r--src/librustc/ich/impls_hir.rs6
-rw-r--r--src/librustc/lib.rs10
-rw-r--r--src/librustc/session/config.rs2
-rw-r--r--src/librustc/session/filesearch.rs1
-rw-r--r--src/librustc_macros/Cargo.toml14
-rw-r--r--src/librustc_macros/src/hash_stable.rs87
-rw-r--r--src/librustc_macros/src/lib.rs8
-rw-r--r--src/librustc_metadata/creader.rs119
-rw-r--r--src/librustc_metadata/decoder.rs2
-rw-r--r--src/librustc_metadata/encoder.rs6
-rw-r--r--src/librustc_metadata/locator.rs16
-rw-r--r--src/librustc_metadata/schema.rs1
-rw-r--r--src/test/ui-fulldeps/hash-stable-is-unstable.rs15
-rw-r--r--src/test/ui-fulldeps/hash-stable-is-unstable.stderr48
-rw-r--r--src/tools/tidy/src/deps.rs1
24 files changed, 392 insertions, 87 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 19dadaa1400..ad4af76f4b3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2365,6 +2365,7 @@ dependencies = [
  "rustc_data_structures 0.0.0",
  "rustc_errors 0.0.0",
  "rustc_fs_util 0.0.0",
+ "rustc_macros 0.1.0",
  "rustc_target 0.0.0",
  "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serialize 0.0.0",
@@ -2818,6 +2819,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustc_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "rustc_metadata"
 version = "0.0.0"
 dependencies = [
diff --git a/src/bootstrap/bin/rustc.rs b/src/bootstrap/bin/rustc.rs
index 70e4a69a07d..ca86aeb8100 100644
--- a/src/bootstrap/bin/rustc.rs
+++ b/src/bootstrap/bin/rustc.rs
@@ -109,6 +109,12 @@ fn main() {
 
         cmd.arg("-Zexternal-macro-backtrace");
 
+        // Link crates to the proc macro crate for the target, but use a host proc macro crate
+        // to actually run the macros
+        if env::var_os("RUST_DUAL_PROC_MACROS").is_some() {
+            cmd.arg("-Zdual-proc-macros");
+        }
+
         // When we build Rust dylibs they're all intended for intermediate
         // usage, so make sure we pass the -Cprefer-dynamic flag instead of
         // linking all deps statically into the dylib.
@@ -258,13 +264,6 @@ fn main() {
             }
         }
 
-        // Force all crates compiled by this compiler to (a) be unstable and (b)
-        // allow the `rustc_private` feature to link to other unstable crates
-        // also in the sysroot.
-        if env::var_os("RUSTC_FORCE_UNSTABLE").is_some() {
-            cmd.arg("-Z").arg("force-unstable-if-unmarked");
-        }
-
         if let Ok(map) = env::var("RUSTC_DEBUGINFO_MAP") {
             cmd.arg("--remap-path-prefix").arg(&map);
         }
@@ -284,6 +283,14 @@ fn main() {
         }
     }
 
+    // Force all crates compiled by this compiler to (a) be unstable and (b)
+    // allow the `rustc_private` feature to link to other unstable crates
+    // also in the sysroot. We also do this for host crates, since those
+    // may be proc macros, in which case we might ship them.
+    if env::var_os("RUSTC_FORCE_UNSTABLE").is_some() && (stage != "0" || target.is_some()) {
+        cmd.arg("-Z").arg("force-unstable-if-unmarked");
+    }
+
     if env::var_os("RUSTC_PARALLEL_COMPILER").is_some() {
         cmd.arg("--cfg").arg("parallel_compiler");
     }
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 7e6c0a9f52a..eb1a2a59fa3 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -812,6 +812,17 @@ impl<'a> Builder<'a> {
             cargo.env("RUST_CHECK", "1");
         }
 
+        match mode {
+            Mode::Std | Mode::Test | Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolTest=> {},
+            Mode::Rustc | Mode::Codegen | Mode::ToolRustc => {
+                // Build proc macros both for the host and the target
+                if target != compiler.host && cmd != "check" {
+                    cargo.arg("-Zdual-proc-macros");
+                    cargo.env("RUST_DUAL_PROC_MACROS", "1");
+                }
+            },
+        }
+
         cargo.arg("-j").arg(self.jobs().to_string());
         // Remove make-related flags to ensure Cargo can correctly set things up
         cargo.env_remove("MAKEFLAGS");
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index 20370372082..a30b465698e 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -42,7 +42,8 @@ impl Step for Std {
                   true);
 
         let libdir = builder.sysroot_libdir(compiler, target);
-        add_to_sysroot(&builder, &libdir, &libstd_stamp(builder, compiler, target));
+        let hostdir = builder.sysroot_libdir(compiler, compiler.host);
+        add_to_sysroot(&builder, &libdir, &hostdir, &libstd_stamp(builder, compiler, target));
     }
 }
 
@@ -88,7 +89,8 @@ impl Step for Rustc {
                   true);
 
         let libdir = builder.sysroot_libdir(compiler, target);
-        add_to_sysroot(&builder, &libdir, &librustc_stamp(builder, compiler, target));
+        let hostdir = builder.sysroot_libdir(compiler, compiler.host);
+        add_to_sysroot(&builder, &libdir, &hostdir, &librustc_stamp(builder, compiler, target));
     }
 }
 
@@ -175,7 +177,8 @@ impl Step for Test {
                   true);
 
         let libdir = builder.sysroot_libdir(compiler, target);
-        add_to_sysroot(builder, &libdir, &libtest_stamp(builder, compiler, target));
+        let hostdir = builder.sysroot_libdir(compiler, compiler.host);
+        add_to_sysroot(builder, &libdir, &hostdir, &libtest_stamp(builder, compiler, target));
     }
 }
 
@@ -222,7 +225,8 @@ impl Step for Rustdoc {
                   true);
 
         let libdir = builder.sysroot_libdir(compiler, target);
-        add_to_sysroot(&builder, &libdir, &rustdoc_stamp(builder, compiler, target));
+        let hostdir = builder.sysroot_libdir(compiler, compiler.host);
+        add_to_sysroot(&builder, &libdir, &hostdir, &rustdoc_stamp(builder, compiler, target));
         builder.cargo(compiler, Mode::ToolRustc, target, "clean");
     }
 }
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 249a1831890..9498dbb5952 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -224,7 +224,8 @@ impl Step for StdLink {
                 target_compiler.host,
                 target));
         let libdir = builder.sysroot_libdir(target_compiler, target);
-        add_to_sysroot(builder, &libdir, &libstd_stamp(builder, compiler, target));
+        let hostdir = builder.sysroot_libdir(target_compiler, compiler.host);
+        add_to_sysroot(builder, &libdir, &hostdir, &libstd_stamp(builder, compiler, target));
 
         if builder.config.sanitizers && compiler.stage != 0 && target == "x86_64-apple-darwin" {
             // The sanitizers are only built in stage1 or above, so the dylibs will
@@ -431,8 +432,12 @@ impl Step for TestLink {
                 &compiler.host,
                 target_compiler.host,
                 target));
-        add_to_sysroot(builder, &builder.sysroot_libdir(target_compiler, target),
-                    &libtest_stamp(builder, compiler, target));
+        add_to_sysroot(
+            builder,
+            &builder.sysroot_libdir(target_compiler, target),
+            &builder.sysroot_libdir(target_compiler, compiler.host),
+            &libtest_stamp(builder, compiler, target)
+        );
 
         builder.cargo(target_compiler, Mode::ToolTest, target, "clean");
     }
@@ -496,8 +501,8 @@ impl Step for Rustc {
             return;
         }
 
-        // Ensure that build scripts have a std to link against.
-        builder.ensure(Std {
+        // Ensure that build scripts and proc macros have a std / libproc_macro to link against.
+        builder.ensure(Test {
             compiler: builder.compiler(self.compiler.stage, builder.config.build),
             target: builder.config.build,
         });
@@ -592,8 +597,12 @@ impl Step for RustcLink {
                  &compiler.host,
                  target_compiler.host,
                  target));
-        add_to_sysroot(builder, &builder.sysroot_libdir(target_compiler, target),
-                       &librustc_stamp(builder, compiler, target));
+        add_to_sysroot(
+            builder,
+            &builder.sysroot_libdir(target_compiler, target),
+            &builder.sysroot_libdir(target_compiler, compiler.host),
+            &librustc_stamp(builder, compiler, target)
+        );
         builder.cargo(target_compiler, Mode::ToolRustc, target, "clean");
     }
 }
@@ -1015,10 +1024,20 @@ impl Step for Assemble {
 ///
 /// For a particular stage this will link the file listed in `stamp` into the
 /// `sysroot_dst` provided.
-pub fn add_to_sysroot(builder: &Builder<'_>, sysroot_dst: &Path, stamp: &Path) {
+pub fn add_to_sysroot(
+    builder: &Builder<'_>,
+    sysroot_dst: &Path,
+    sysroot_host_dst: &Path,
+    stamp: &Path
+) {
     t!(fs::create_dir_all(&sysroot_dst));
-    for path in builder.read_stamp_file(stamp) {
-        builder.copy(&path, &sysroot_dst.join(path.file_name().unwrap()));
+    t!(fs::create_dir_all(&sysroot_host_dst));
+    for (path, host) in builder.read_stamp_file(stamp) {
+        if host {
+            builder.copy(&path, &sysroot_host_dst.join(path.file_name().unwrap()));
+        } else {
+            builder.copy(&path, &sysroot_dst.join(path.file_name().unwrap()));
+        }
     }
 }
 
@@ -1047,8 +1066,14 @@ pub fn run_cargo(builder: &Builder<'_>,
     let mut deps = Vec::new();
     let mut toplevel = Vec::new();
     let ok = stream_cargo(builder, cargo, &mut |msg| {
-        let filenames = match msg {
-            CargoMessage::CompilerArtifact { filenames, .. } => filenames,
+        let (filenames, crate_types) = match msg {
+            CargoMessage::CompilerArtifact {
+                filenames,
+                target: CargoTarget {
+                    crate_types,
+                },
+                ..
+            } => (filenames, crate_types),
             _ => return,
         };
         for filename in filenames {
@@ -1063,15 +1088,19 @@ pub fn run_cargo(builder: &Builder<'_>,
             let filename = Path::new(&*filename);
 
             // If this was an output file in the "host dir" we don't actually
-            // worry about it, it's not relevant for us.
+            // worry about it, it's not relevant for us
             if filename.starts_with(&host_root_dir) {
+                // Unless it's a proc macro used in the compiler
+                if crate_types.iter().any(|t| t == "proc-macro") {
+                    deps.push((filename.to_path_buf(), true));
+                }
                 continue;
             }
 
             // If this was output in the `deps` dir then this is a precise file
             // name (hash included) so we start tracking it.
             if filename.starts_with(&target_deps_dir) {
-                deps.push(filename.to_path_buf());
+                deps.push((filename.to_path_buf(), false));
                 continue;
             }
 
@@ -1124,10 +1153,10 @@ pub fn run_cargo(builder: &Builder<'_>,
             let candidate = format!("{}.lib", path_to_add);
             let candidate = PathBuf::from(candidate);
             if candidate.exists() {
-                deps.push(candidate);
+                deps.push((candidate, false));
             }
         }
-        deps.push(path_to_add.into());
+        deps.push((path_to_add.into(), false));
     }
 
     // Now we want to update the contents of the stamp file, if necessary. First
@@ -1140,12 +1169,13 @@ pub fn run_cargo(builder: &Builder<'_>,
     let mut new_contents = Vec::new();
     let mut max = None;
     let mut max_path = None;
-    for dep in deps.iter() {
+    for (dep, proc_macro) in deps.iter() {
         let mtime = mtime(dep);
         if Some(mtime) > max {
             max = Some(mtime);
             max_path = Some(dep.clone());
         }
+        new_contents.extend(if *proc_macro { b"h" } else { b"t" });
         new_contents.extend(dep.to_str().unwrap().as_bytes());
         new_contents.extend(b"\0");
     }
@@ -1157,7 +1187,7 @@ pub fn run_cargo(builder: &Builder<'_>,
     if contents_equal && max <= stamp_mtime {
         builder.verbose(&format!("not updating {:?}; contents equal and {:?} <= {:?}",
                 stamp, max, stamp_mtime));
-        return deps
+        return deps.into_iter().map(|(d, _)| d).collect()
     }
     if max > stamp_mtime {
         builder.verbose(&format!("updating {:?} as {:?} changed", stamp, max_path));
@@ -1165,7 +1195,7 @@ pub fn run_cargo(builder: &Builder<'_>,
         builder.verbose(&format!("updating {:?} as deps changed", stamp));
     }
     t!(fs::write(&stamp, &new_contents));
-    deps
+    deps.into_iter().map(|(d, _)| d).collect()
 }
 
 pub fn stream_cargo(
@@ -1212,12 +1242,18 @@ pub fn stream_cargo(
 }
 
 #[derive(Deserialize)]
+pub struct CargoTarget<'a> {
+    crate_types: Vec<Cow<'a, str>>,
+}
+
+#[derive(Deserialize)]
 #[serde(tag = "reason", rename_all = "kebab-case")]
 pub enum CargoMessage<'a> {
     CompilerArtifact {
         package_id: Cow<'a, str>,
         features: Vec<Cow<'a, str>>,
         filenames: Vec<Cow<'a, str>>,
+        target: CargoTarget<'a>,
     },
     BuildScriptExecuted {
         package_id: Cow<'a, str>,
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 84e2c5aab54..9317a40545e 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -1129,7 +1129,7 @@ impl Build {
         ret
     }
 
-    fn read_stamp_file(&self, stamp: &Path) -> Vec<PathBuf> {
+    fn read_stamp_file(&self, stamp: &Path) -> Vec<(PathBuf, bool)> {
         if self.config.dry_run {
             return Vec::new();
         }
@@ -1142,8 +1142,9 @@ impl Build {
             if part.is_empty() {
                 continue
             }
-            let path = PathBuf::from(t!(str::from_utf8(part)));
-            paths.push(path);
+            let host = part[0] as char == 'h';
+            let path = PathBuf::from(t!(str::from_utf8(&part[1..])));
+            paths.push((path, host));
         }
         paths
     }
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index fc1a17d5466..9dbcacf7026 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -91,7 +91,8 @@ impl Step for ToolBuild {
                 compile::CargoMessage::CompilerArtifact {
                     package_id,
                     features,
-                    filenames
+                    filenames,
+                    target: _,
                 } => {
                     (package_id, features, filenames)
                 }
diff --git a/src/librustc/Cargo.toml b/src/librustc/Cargo.toml
index 26a27ea88e2..31e10c19c7a 100644
--- a/src/librustc/Cargo.toml
+++ b/src/librustc/Cargo.toml
@@ -24,6 +24,7 @@ rustc-rayon = "0.1.2"
 rustc-rayon-core = "0.1.2"
 rustc_apfloat = { path = "../librustc_apfloat" }
 rustc_target = { path = "../librustc_target" }
+rustc_macros = { path = "../librustc_macros" }
 rustc_data_structures = { path = "../librustc_data_structures" }
 errors = { path = "../librustc_errors", package = "rustc_errors" }
 serialize = { path = "../libserialize" }
diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs
index 2d0296aa38c..4b5670af138 100644
--- a/src/librustc/hir/mod.rs
+++ b/src/librustc/hir/mod.rs
@@ -32,6 +32,7 @@ use crate::ty::query::Providers;
 
 use rustc_data_structures::sync::{ParallelIterator, par_iter, Send, Sync};
 use rustc_data_structures::thin_vec::ThinVec;
+use rustc_macros::HashStable;
 
 use serialize::{self, Encoder, Encodable, Decoder, Decodable};
 use std::collections::{BTreeSet, BTreeMap};
@@ -149,7 +150,7 @@ pub const DUMMY_HIR_ID: HirId = HirId {
 
 pub const DUMMY_ITEM_LOCAL_ID: ItemLocalId = ItemLocalId::MAX;
 
-#[derive(Clone, RustcEncodable, RustcDecodable, Copy)]
+#[derive(Clone, RustcEncodable, RustcDecodable, Copy, HashStable)]
 pub struct Lifetime {
     pub hir_id: HirId,
     pub span: Span,
diff --git a/src/librustc/ich/impls_hir.rs b/src/librustc/ich/impls_hir.rs
index 0803816fb03..d8d4157e20e 100644
--- a/src/librustc/ich/impls_hir.rs
+++ b/src/librustc/ich/impls_hir.rs
@@ -157,12 +157,6 @@ impl_stable_hash_for!(struct ast::Label {
     ident
 });
 
-impl_stable_hash_for!(struct hir::Lifetime {
-    hir_id,
-    span,
-    name
-});
-
 impl_stable_hash_for!(struct hir::Path {
     span,
     def,
diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs
index 205ea6126fc..6adfaa53946 100644
--- a/src/librustc/lib.rs
+++ b/src/librustc/lib.rs
@@ -141,14 +141,8 @@ pub mod util {
     pub mod bug;
 }
 
-// A private module so that macro-expanded idents like
-// `::rustc::lint::Lint` will also work in `rustc` itself.
-//
-// `libstd` uses the same trick.
-#[doc(hidden)]
-mod rustc {
-    pub use crate::lint;
-}
+// Allows macros to refer to this crate as `::rustc`
+extern crate self as rustc;
 
 // FIXME(#27438): right now the unit tests of librustc don't refer to any actual
 //                functions generated in librustc_data_structures (all
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 774ab0333db..a4c587b5ca7 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -1232,6 +1232,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
          Use with RUST_REGION_GRAPH=help for more info"),
     parse_only: bool = (false, parse_bool, [UNTRACKED],
         "parse only; do not compile, assemble, or link"),
+    dual_proc_macros: bool = (false, parse_bool, [TRACKED],
+        "load proc macros for both target and host, but only link to the target"),
     no_codegen: bool = (false, parse_bool, [TRACKED],
         "run all passes except codegen; no output"),
     treat_err_as_bug: bool = (false, parse_bool, [TRACKED],
diff --git a/src/librustc/session/filesearch.rs b/src/librustc/session/filesearch.rs
index 77f190e2812..cf09d45ca38 100644
--- a/src/librustc/session/filesearch.rs
+++ b/src/librustc/session/filesearch.rs
@@ -18,6 +18,7 @@ pub enum FileMatch {
 
 // A module for searching for libraries
 
+#[derive(Clone)]
 pub struct FileSearch<'a> {
     sysroot: &'a Path,
     triple: &'a str,
diff --git a/src/librustc_macros/Cargo.toml b/src/librustc_macros/Cargo.toml
new file mode 100644
index 00000000000..2fe51a22fb4
--- /dev/null
+++ b/src/librustc_macros/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "rustc_macros"
+version = "0.1.0"
+authors = ["The Rust Project Developers"]
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+synstructure = "0.10.1"
+syn = { version = "0.15.22", features = ["full"] }
+proc-macro2 = "0.4.24"
+quote = "0.6.10"
diff --git a/src/librustc_macros/src/hash_stable.rs b/src/librustc_macros/src/hash_stable.rs
new file mode 100644
index 00000000000..6d7590c7d1c
--- /dev/null
+++ b/src/librustc_macros/src/hash_stable.rs
@@ -0,0 +1,87 @@
+use synstructure;
+use syn::{self, Meta, NestedMeta, parse_quote};
+use proc_macro2::{self, Ident};
+use quote::quote;
+
+struct Attributes {
+    ignore: bool,
+    project: Option<Ident>,
+}
+
+fn parse_attributes(field: &syn::Field) -> Attributes {
+    let mut attrs = Attributes {
+        ignore: false,
+        project: None,
+    };
+    for attr in &field.attrs {
+        if let Ok(meta) = attr.parse_meta() {
+            if &meta.name().to_string() != "stable_hasher" {
+                continue;
+            }
+            let mut any_attr = false;
+            if let Meta::List(list) = meta {
+                for nested in list.nested.iter() {
+                    if let NestedMeta::Meta(meta) = nested {
+                        if &meta.name().to_string() == "ignore" {
+                            attrs.ignore = true;
+                            any_attr = true;
+                        }
+                        if &meta.name().to_string() == "project" {
+                            if let Meta::List(list) = meta {
+                                if let Some(nested) = list.nested.iter().next() {
+                                    if let NestedMeta::Meta(meta) = nested {
+                                        attrs.project = Some(meta.name());
+                                        any_attr = true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if !any_attr {
+                panic!("error parsing stable_hasher");
+            }
+        }
+    }
+    attrs
+}
+
+pub fn hash_stable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+    let generic: syn::GenericParam = parse_quote!('__ctx);
+    s.add_bounds(synstructure::AddBounds::Generics);
+    s.add_impl_generic(generic);
+    let body = s.each(|bi| {
+        let attrs = parse_attributes(bi.ast());
+        if attrs.ignore {
+             quote!{}
+        } else if let Some(project) = attrs.project {
+            quote!{
+                &#bi.#project.hash_stable(__hcx, __hasher);
+            }
+        } else {
+            quote!{
+                #bi.hash_stable(__hcx, __hasher);
+            }
+        }
+    });
+
+    let discriminant = match s.ast().data {
+        syn::Data::Enum(_) => quote! {
+            ::std::mem::discriminant(self).hash_stable(__hcx, __hasher);
+        },
+        syn::Data::Struct(_) => quote! {},
+        syn::Data::Union(_) => panic!("cannot derive on union"),
+    };
+
+    s.bound_impl(quote!(::rustc_data_structures::stable_hasher::HashStable
+                        <::rustc::ich::StableHashingContext<'__ctx>>), quote!{
+        fn hash_stable<__W: ::rustc_data_structures::stable_hasher::StableHasherResult>(
+            &self,
+            __hcx: &mut ::rustc::ich::StableHashingContext<'__ctx>,
+            __hasher: &mut ::rustc_data_structures::stable_hasher::StableHasher<__W>) {
+            #discriminant
+            match *self { #body }
+        }
+    })
+}
diff --git a/src/librustc_macros/src/lib.rs b/src/librustc_macros/src/lib.rs
new file mode 100644
index 00000000000..cad31264b05
--- /dev/null
+++ b/src/librustc_macros/src/lib.rs
@@ -0,0 +1,8 @@
+#![feature(proc_macro_hygiene)]
+#![deny(rust_2018_idioms)]
+
+use synstructure::decl_derive;
+
+mod hash_stable;
+
+decl_derive!([HashStable, attributes(stable_hasher)] => hash_stable::hash_stable_derive);
diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs
index 6f2718381c2..36d9bf9f50d 100644
--- a/src/librustc_metadata/creader.rs
+++ b/src/librustc_metadata/creader.rs
@@ -188,13 +188,15 @@ impl<'a> CrateLoader<'a> {
         });
     }
 
-    fn register_crate(&mut self,
-                      root: &Option<CratePaths>,
-                      ident: Symbol,
-                      span: Span,
-                      lib: Library,
-                      dep_kind: DepKind)
-                      -> (CrateNum, Lrc<cstore::CrateMetadata>) {
+    fn register_crate(
+        &mut self,
+        host_lib: Option<Library>,
+        root: &Option<CratePaths>,
+        ident: Symbol,
+        span: Span,
+        lib: Library,
+        dep_kind: DepKind
+    ) -> (CrateNum, Lrc<cstore::CrateMetadata>) {
         let crate_root = lib.metadata.get_root();
         info!("register crate `extern crate {} as {}`", crate_root.name, ident);
         self.verify_no_symbol_conflicts(span, &crate_root);
@@ -222,7 +224,16 @@ impl<'a> CrateLoader<'a> {
         let dependencies: Vec<CrateNum> = cnum_map.iter().cloned().collect();
 
         let proc_macros = crate_root.proc_macro_decls_static.map(|_| {
-            self.load_derive_macros(&crate_root, dylib.clone().map(|p| p.0), span)
+            if self.sess.opts.debugging_opts.dual_proc_macros {
+                let host_lib = host_lib.unwrap();
+                self.load_derive_macros(
+                    &host_lib.metadata.get_root(),
+                    host_lib.dylib.clone().map(|p| p.0),
+                    span
+                )
+            } else {
+                self.load_derive_macros(&crate_root, dylib.clone().map(|p| p.0), span)
+            }
         });
 
         let def_path_table = record_time(&self.sess.perf_stats.decode_def_path_tables_time, || {
@@ -269,6 +280,61 @@ impl<'a> CrateLoader<'a> {
         (cnum, cmeta)
     }
 
+    fn load_proc_macro<'b> (
+        &mut self,
+        locate_ctxt: &mut locator::Context<'b>,
+        path_kind: PathKind,
+    ) -> Option<(LoadResult, Option<Library>)>
+    where
+        'a: 'b
+    {
+        // Use a new locator Context so trying to load a proc macro doesn't affect the error
+        // message we emit
+        let mut proc_macro_locator = locate_ctxt.clone();
+
+        // Try to load a proc macro
+        proc_macro_locator.is_proc_macro = Some(true);
+
+        // Load the proc macro crate for the target
+        let (locator, target_result) = if self.sess.opts.debugging_opts.dual_proc_macros {
+            proc_macro_locator.reset();
+            let result = match self.load(&mut proc_macro_locator)? {
+                LoadResult::Previous(cnum) => return Some((LoadResult::Previous(cnum), None)),
+                LoadResult::Loaded(library) => Some(LoadResult::Loaded(library))
+            };
+            // Don't look for a matching hash when looking for the host crate.
+            // It won't be the same as the target crate hash
+            locate_ctxt.hash = None;
+            // Use the locate_ctxt when looking for the host proc macro crate, as that is required
+            // so we want it to affect the error message
+            (locate_ctxt, result)
+        } else {
+            (&mut proc_macro_locator, None)
+        };
+
+        // Load the proc macro crate for the host
+
+        locator.reset();
+        locator.is_proc_macro = Some(true);
+        locator.target = &self.sess.host;
+        locator.triple = TargetTriple::from_triple(config::host_triple());
+        locator.filesearch = self.sess.host_filesearch(path_kind);
+
+        let host_result = self.load(locator)?;
+
+        Some(if self.sess.opts.debugging_opts.dual_proc_macros {
+            let host_result = match host_result {
+                LoadResult::Previous(..) => {
+                    panic!("host and target proc macros must be loaded in lock-step")
+                }
+                LoadResult::Loaded(library) => library
+            };
+            (target_result.unwrap(), Some(host_result))
+        } else {
+            (host_result, None)
+        })
+    }
+
     fn resolve_crate<'b>(
         &'b mut self,
         root: &'b Option<CratePaths>,
@@ -282,7 +348,7 @@ impl<'a> CrateLoader<'a> {
     ) -> Result<(CrateNum, Lrc<cstore::CrateMetadata>), LoadError<'b>> {
         info!("resolving crate `extern crate {} as {}`", name, ident);
         let result = if let Some(cnum) = self.existing_match(name, hash, path_kind) {
-            LoadResult::Previous(cnum)
+            (LoadResult::Previous(cnum), None)
         } else {
             info!("falling back to a load");
             let mut locate_ctxt = locator::Context {
@@ -294,7 +360,7 @@ impl<'a> CrateLoader<'a> {
                 extra_filename: extra_filename,
                 filesearch: self.sess.target_filesearch(path_kind),
                 target: &self.sess.target.target,
-                triple: &self.sess.opts.target_triple,
+                triple: self.sess.opts.target_triple.clone(),
                 root,
                 rejected_via_hash: vec![],
                 rejected_via_triple: vec![],
@@ -306,28 +372,14 @@ impl<'a> CrateLoader<'a> {
                 metadata_loader: &*self.cstore.metadata_loader,
             };
 
-            self.load(&mut locate_ctxt).or_else(|| {
+            self.load(&mut locate_ctxt).map(|r| (r, None)).or_else(|| {
                 dep_kind = DepKind::UnexportedMacrosOnly;
-
-                let mut proc_macro_locator = locator::Context {
-                    target: &self.sess.host,
-                    triple: &TargetTriple::from_triple(config::host_triple()),
-                    filesearch: self.sess.host_filesearch(path_kind),
-                    rejected_via_hash: vec![],
-                    rejected_via_triple: vec![],
-                    rejected_via_kind: vec![],
-                    rejected_via_version: vec![],
-                    rejected_via_filename: vec![],
-                    is_proc_macro: Some(true),
-                    ..locate_ctxt
-                };
-
-                self.load(&mut proc_macro_locator)
+                self.load_proc_macro(&mut locate_ctxt, path_kind)
             }).ok_or_else(move || LoadError::LocatorError(locate_ctxt))?
         };
 
         match result {
-            LoadResult::Previous(cnum) => {
+            (LoadResult::Previous(cnum), None) => {
                 let data = self.cstore.get_crate_data(cnum);
                 if data.root.proc_macro_decls_static.is_some() {
                     dep_kind = DepKind::UnexportedMacrosOnly;
@@ -337,9 +389,10 @@ impl<'a> CrateLoader<'a> {
                 });
                 Ok((cnum, data))
             }
-            LoadResult::Loaded(library) => {
-                Ok(self.register_crate(root, ident, span, library, dep_kind))
+            (LoadResult::Loaded(library), host_library) => {
+                Ok(self.register_crate(host_library, root, ident, span, library, dep_kind))
             }
+            _ => panic!()
         }
     }
 
@@ -355,7 +408,7 @@ impl<'a> CrateLoader<'a> {
         // don't want to match a host crate against an equivalent target one
         // already loaded.
         let root = library.metadata.get_root();
-        if locate_ctxt.triple == &self.sess.opts.target_triple {
+        if locate_ctxt.triple == self.sess.opts.target_triple {
             let mut result = LoadResult::Loaded(library);
             self.cstore.iter_crate_data(|cnum, data| {
                 if data.root.name == root.name && root.hash == data.root.hash {
@@ -451,9 +504,9 @@ impl<'a> CrateLoader<'a> {
     fn read_extension_crate(&mut self, span: Span, orig_name: Symbol, rename: Symbol)
                             -> ExtensionCrate {
         info!("read extension crate `extern crate {} as {}`", orig_name, rename);
-        let target_triple = &self.sess.opts.target_triple;
+        let target_triple = self.sess.opts.target_triple.clone();
         let host_triple = TargetTriple::from_triple(config::host_triple());
-        let is_cross = target_triple != &host_triple;
+        let is_cross = target_triple != host_triple;
         let mut target_only = false;
         let mut locate_ctxt = locator::Context {
             sess: self.sess,
@@ -464,7 +517,7 @@ impl<'a> CrateLoader<'a> {
             extra_filename: None,
             filesearch: self.sess.host_filesearch(PathKind::Crate),
             target: &self.sess.host,
-            triple: &host_triple,
+            triple: host_triple,
             root: &None,
             rejected_via_hash: vec![],
             rejected_via_triple: vec![],
diff --git a/src/librustc_metadata/decoder.rs b/src/librustc_metadata/decoder.rs
index 1c4e3bc6a50..6fe00a4ad2f 100644
--- a/src/librustc_metadata/decoder.rs
+++ b/src/librustc_metadata/decoder.rs
@@ -648,7 +648,7 @@ impl<'a, 'tcx> CrateMetadata {
 
     pub fn get_stability(&self, id: DefIndex) -> Option<attr::Stability> {
         match self.is_proc_macro(id) {
-            true => None,
+            true => self.root.proc_macro_stability.clone(),
             false => self.entry(id).stability.map(|stab| stab.decode(self)),
         }
     }
diff --git a/src/librustc_metadata/encoder.rs b/src/librustc_metadata/encoder.rs
index f79cfa3b773..683056c9b33 100644
--- a/src/librustc_metadata/encoder.rs
+++ b/src/librustc_metadata/encoder.rs
@@ -490,7 +490,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             } else {
                 None
             },
-
+            proc_macro_stability: if is_proc_macro {
+                tcx.lookup_stability(DefId::local(CRATE_DEF_INDEX)).map(|stab| stab.clone())
+            } else {
+                None
+            },
             compiler_builtins: attr::contains_name(&attrs, "compiler_builtins"),
             needs_allocator: attr::contains_name(&attrs, "needs_allocator"),
             needs_panic_runtime: attr::contains_name(&attrs, "needs_panic_runtime"),
diff --git a/src/librustc_metadata/locator.rs b/src/librustc_metadata/locator.rs
index 6a1aada5ac7..81878c4f687 100644
--- a/src/librustc_metadata/locator.rs
+++ b/src/librustc_metadata/locator.rs
@@ -244,11 +244,13 @@ use rustc_data_structures::owning_ref::OwningRef;
 
 use log::{debug, info, warn};
 
+#[derive(Clone)]
 pub struct CrateMismatch {
     path: PathBuf,
     got: String,
 }
 
+#[derive(Clone)]
 pub struct Context<'a> {
     pub sess: &'a Session,
     pub span: Span,
@@ -258,7 +260,7 @@ pub struct Context<'a> {
     pub extra_filename: Option<&'a str>,
     // points to either self.sess.target.target or self.sess.host, must match triple
     pub target: &'a Target,
-    pub triple: &'a TargetTriple,
+    pub triple: TargetTriple,
     pub filesearch: FileSearch<'a>,
     pub root: &'a Option<CratePaths>,
     pub rejected_via_hash: Vec<CrateMismatch>,
@@ -302,6 +304,14 @@ impl CratePaths {
 }
 
 impl<'a> Context<'a> {
+    pub fn reset(&mut self) {
+        self.rejected_via_hash.clear();
+        self.rejected_via_triple.clear();
+        self.rejected_via_kind.clear();
+        self.rejected_via_version.clear();
+        self.rejected_via_filename.clear();
+    }
+
     pub fn maybe_load_library_crate(&mut self) -> Option<Library> {
         let mut seen_paths = FxHashSet::default();
         match self.extra_filename {
@@ -399,7 +409,7 @@ impl<'a> Context<'a> {
                                            add);
 
             if (self.ident == "std" || self.ident == "core")
-                && self.triple != &TargetTriple::from_triple(config::host_triple()) {
+                && self.triple != TargetTriple::from_triple(config::host_triple()) {
                 err.note(&format!("the `{}` target may not be installed", self.triple));
             }
             err.span_label(self.span, "can't find crate");
@@ -718,7 +728,7 @@ impl<'a> Context<'a> {
             }
         }
 
-        if &root.triple != self.triple {
+        if root.triple != self.triple {
             info!("Rejecting via crate triple: expected {} got {}",
                   self.triple,
                   root.triple);
diff --git a/src/librustc_metadata/schema.rs b/src/librustc_metadata/schema.rs
index af79ea37dff..afeea9947b5 100644
--- a/src/librustc_metadata/schema.rs
+++ b/src/librustc_metadata/schema.rs
@@ -187,6 +187,7 @@ pub struct CrateRoot {
     pub has_default_lib_allocator: bool,
     pub plugin_registrar_fn: Option<DefIndex>,
     pub proc_macro_decls_static: Option<DefIndex>,
+    pub proc_macro_stability: Option<attr::Stability>,
 
     pub crate_deps: LazySeq<CrateDep>,
     pub dylib_dependency_formats: LazySeq<Option<LinkagePreference>>,
diff --git a/src/test/ui-fulldeps/hash-stable-is-unstable.rs b/src/test/ui-fulldeps/hash-stable-is-unstable.rs
new file mode 100644
index 00000000000..9f67f642df1
--- /dev/null
+++ b/src/test/ui-fulldeps/hash-stable-is-unstable.rs
@@ -0,0 +1,15 @@
+// ignore-stage1
+
+extern crate rustc_data_structures;
+//~^ use of unstable library feature 'rustc_private'
+extern crate rustc;
+//~^ use of unstable library feature 'rustc_private'
+extern crate rustc_macros;
+//~^ use of unstable library feature 'rustc_private'
+
+use rustc_macros::HashStable;
+//~^ use of unstable library feature 'rustc_private'
+
+#[derive(HashStable)]
+//~^ use of unstable library feature 'rustc_private'
+struct Test;
diff --git a/src/test/ui-fulldeps/hash-stable-is-unstable.stderr b/src/test/ui-fulldeps/hash-stable-is-unstable.stderr
new file mode 100644
index 00000000000..e7007204d38
--- /dev/null
+++ b/src/test/ui-fulldeps/hash-stable-is-unstable.stderr
@@ -0,0 +1,48 @@
+error[E0601]: `main` function not found in crate `hash_stable_is_unstable`
+   |
+   = note: consider adding a `main` function to `$DIR/hash-stable-is-unstable.rs`
+
+error[E0658]: use of unstable library feature 'rustc_private': this crate is being loaded from the sysroot, an unstable location; did you mean to load this crate from crates.io via `Cargo.toml` instead? (see issue #27812)
+  --> $DIR/hash-stable-is-unstable.rs:3:1
+   |
+LL | extern crate rustc_data_structures;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(rustc_private)] to the crate attributes to enable
+
+error[E0658]: use of unstable library feature 'rustc_private': this crate is being loaded from the sysroot, an unstable location; did you mean to load this crate from crates.io via `Cargo.toml` instead? (see issue #27812)
+  --> $DIR/hash-stable-is-unstable.rs:5:1
+   |
+LL | extern crate rustc;
+   | ^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(rustc_private)] to the crate attributes to enable
+
+error[E0658]: use of unstable library feature 'rustc_private': this crate is being loaded from the sysroot, an unstable location; did you mean to load this crate from crates.io via `Cargo.toml` instead? (see issue #27812)
+  --> $DIR/hash-stable-is-unstable.rs:7:1
+   |
+LL | extern crate rustc_macros;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(rustc_private)] to the crate attributes to enable
+
+error[E0658]: use of unstable library feature 'rustc_private': this crate is being loaded from the sysroot, an unstable location; did you mean to load this crate from crates.io via `Cargo.toml` instead? (see issue #27812)
+  --> $DIR/hash-stable-is-unstable.rs:10:5
+   |
+LL | use rustc_macros::HashStable;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(rustc_private)] to the crate attributes to enable
+
+error[E0658]: use of unstable library feature 'rustc_private': this crate is being loaded from the sysroot, an unstable location; did you mean to load this crate from crates.io via `Cargo.toml` instead? (see issue #27812)
+  --> $DIR/hash-stable-is-unstable.rs:13:10
+   |
+LL | #[derive(HashStable)]
+   |          ^^^^^^^^^^
+   |
+   = help: add #![feature(rustc_private)] to the crate attributes to enable
+
+error: aborting due to 6 previous errors
+
+Some errors occurred: E0601, E0658.
+For more information about an error, try `rustc --explain E0601`.
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index f581ce1f5a3..d7683aae841 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -137,6 +137,7 @@ const WHITELIST: &[Crate<'_>] = &[
     Crate("smallvec"),
     Crate("stable_deref_trait"),
     Crate("syn"),
+    Crate("synstructure"),
     Crate("tempfile"),
     Crate("termcolor"),
     Crate("terminon"),