diff --git a/Cargo.lock b/Cargo.lock
index 34f05e83a33..5f7c52e0a17 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -424,6 +424,17 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "goblin"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd5e3132801a1ac34ac53b97acde50c4685414dd2f291b9ea52afa6f07468c8"
+dependencies = [
+ "log",
+ "plain",
+ "scroll",
+]
+
 [[package]]
 name = "heck"
 version = "0.3.1"
@@ -586,6 +597,15 @@ version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
 
+[[package]]
+name = "libloading"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c979a19ffb457f0273965c333053f3d586bf759bf7b683fbebc37f9a9ebedc4"
+dependencies = [
+ "winapi 0.3.8",
+]
+
 [[package]]
 name = "linked-hash-map"
 version = "0.5.2"
@@ -825,6 +845,12 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ad1f1b834a05d42dae330066e9699a173b28185b3bdc3dbf14ca239585de8cc"
 
+[[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.6"
@@ -1081,6 +1107,8 @@ version = "0.1.0"
 dependencies = [
  "cargo_metadata",
  "difference",
+ "goblin",
+ "libloading",
  "ra_mbe",
  "ra_proc_macro",
  "ra_tt",
@@ -1396,6 +1424,26 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
+[[package]]
+name = "scroll"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1"
+dependencies = [
+ "scroll_derive",
+]
+
+[[package]]
+name = "scroll_derive"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "semver"
 version = "0.9.0"
diff --git a/crates/ra_proc_macro_srv/Cargo.toml b/crates/ra_proc_macro_srv/Cargo.toml
index f08de5fc771..437b8f475f1 100644
--- a/crates/ra_proc_macro_srv/Cargo.toml
+++ b/crates/ra_proc_macro_srv/Cargo.toml
@@ -12,9 +12,11 @@ doctest = false
 ra_tt = { path = "../ra_tt" }
 ra_mbe = { path = "../ra_mbe" }
 ra_proc_macro = { path = "../ra_proc_macro" }
+goblin = "0.2.1"
+libloading = "0.6.0"
 
 [dev-dependencies]
 cargo_metadata = "0.9.1"
 difference = "2.0.0"
 # used as proc macro test target
-serde_derive = "=1.0.104"
\ No newline at end of file
+serde_derive = "=1.0.104"
diff --git a/crates/ra_proc_macro_srv/src/dylib.rs b/crates/ra_proc_macro_srv/src/dylib.rs
new file mode 100644
index 00000000000..525c7ac7b5a
--- /dev/null
+++ b/crates/ra_proc_macro_srv/src/dylib.rs
@@ -0,0 +1,220 @@
+//! Handles dynamic library loading for proc macro
+
+use crate::{proc_macro::bridge, rustc_server::TokenStream};
+use std::fs::File;
+use std::io::Read;
+use std::path::Path;
+
+use goblin::{mach::Mach, Object};
+use libloading::Library;
+use ra_proc_macro::ProcMacroKind;
+
+static NEW_REGISTRAR_SYMBOL: &str = "__rustc_proc_macro_decls_";
+static _OLD_REGISTRAR_SYMBOL: &str = "__rustc_derive_registrar_";
+
+fn read_bytes(file: &Path) -> Option<Vec<u8>> {
+    let mut fd = File::open(file).ok()?;
+    let mut buffer = Vec::new();
+    fd.read_to_end(&mut buffer).ok()?;
+
+    Some(buffer)
+}
+
+fn get_symbols_from_lib(file: &Path) -> Option<Vec<String>> {
+    let buffer = read_bytes(file)?;
+    let object = Object::parse(&buffer).ok()?;
+
+    return match object {
+        Object::Elf(elf) => {
+            let symbols = elf.dynstrtab.to_vec().ok()?;
+            let names = symbols.iter().map(|s| s.to_string()).collect();
+
+            Some(names)
+        }
+
+        Object::PE(pe) => {
+            let symbol_names =
+                pe.exports.iter().flat_map(|s| s.name).map(|n| n.to_string()).collect();
+            Some(symbol_names)
+        }
+
+        Object::Mach(mach) => match mach {
+            Mach::Binary(binary) => {
+                let exports = binary.exports().ok()?;
+                let names = exports.iter().map(|s| s.name.clone()).collect();
+
+                Some(names)
+            }
+
+            Mach::Fat(_) => None,
+        },
+
+        Object::Archive(_) | Object::Unknown(_) => None,
+    };
+}
+
+fn is_derive_registrar_symbol(symbol: &str) -> bool {
+    symbol.contains(NEW_REGISTRAR_SYMBOL)
+}
+
+fn find_registrar_symbol(file: &Path) -> Option<String> {
+    let symbols = get_symbols_from_lib(file)?;
+
+    symbols.iter().find(|s| is_derive_registrar_symbol(s)).map(|s| s.to_string())
+}
+
+/// Loads dynamic library in platform dependent manner.
+///
+/// For unix, you have to use RTLD_DEEPBIND flag to escape problems described
+/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample)
+/// and [here](https://github.com/rust-lang/rust/issues/60593).
+///
+/// Usage of RTLD_DEEPBIND
+/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample/issues/1)
+///
+/// It seems that on Windows that behaviour is default, so we do nothing in that case.
+#[cfg(windows)]
+fn load_library(file: &Path) -> Result<Library, libloading::Error> {
+    Library::new(file)
+}
+
+#[cfg(unix)]
+fn load_library(file: &Path) -> Result<Library, libloading::Error> {
+    use libloading::os::unix::Library as UnixLibrary;
+    use std::os::raw::c_int;
+
+    const RTLD_NOW: c_int = 0x00002;
+    const RTLD_DEEPBIND: c_int = 0x00008;
+
+    UnixLibrary::open(Some(file), RTLD_NOW | RTLD_DEEPBIND).map(|lib| lib.into())
+}
+
+struct ProcMacroLibraryLibloading {
+    // Hold the dylib to prevent it for unloadeding
+    #[allow(dead_code)]
+    lib: Library,
+    exported_macros: Vec<bridge::client::ProcMacro>,
+}
+
+impl ProcMacroLibraryLibloading {
+    fn open(file: &Path) -> Result<Self, String> {
+        let symbol_name = find_registrar_symbol(file)
+            .ok_or(format!("Cannot find registrar symbol in file {:?}", file))?;
+
+        let lib = load_library(file).map_err(|e| e.to_string())?;
+
+        let exported_macros = {
+            let macros: libloading::Symbol<&&[bridge::client::ProcMacro]> =
+                unsafe { lib.get(symbol_name.as_bytes()) }.map_err(|e| e.to_string())?;
+
+            macros.to_vec()
+        };
+
+        Ok(ProcMacroLibraryLibloading { lib, exported_macros })
+    }
+}
+
+type ProcMacroLibraryImpl = ProcMacroLibraryLibloading;
+
+pub struct Expander {
+    libs: Vec<ProcMacroLibraryImpl>,
+}
+
+impl Expander {
+    pub fn new<P: AsRef<Path>>(lib: &P) -> Result<Expander, String> {
+        let mut libs = vec![];
+
+        /* Some libraries for dynamic loading require canonicalized path (even when it is
+        already absolute
+        */
+        let lib =
+            lib.as_ref().canonicalize().expect(&format!("Cannot canonicalize {:?}", lib.as_ref()));
+
+        let library = ProcMacroLibraryImpl::open(&lib)?;
+        libs.push(library);
+
+        Ok(Expander { libs })
+    }
+
+    pub fn expand(
+        &self,
+        macro_name: &str,
+        macro_body: &ra_tt::Subtree,
+        attributes: Option<&ra_tt::Subtree>,
+    ) -> Result<ra_tt::Subtree, bridge::PanicMessage> {
+        let parsed_body = TokenStream::with_subtree(macro_body.clone());
+
+        let parsed_attributes = attributes
+            .map_or(crate::rustc_server::TokenStream::new(), |attr| {
+                TokenStream::with_subtree(attr.clone())
+            });
+
+        for lib in &self.libs {
+            for proc_macro in &lib.exported_macros {
+                match proc_macro {
+                    bridge::client::ProcMacro::CustomDerive { trait_name, client, .. }
+                        if *trait_name == macro_name =>
+                    {
+                        let res = client.run(
+                            &crate::proc_macro::bridge::server::SameThread,
+                            crate::rustc_server::Rustc::default(),
+                            parsed_body,
+                        );
+
+                        return res.map(|it| it.subtree);
+                    }
+
+                    bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => {
+                        let res = client.run(
+                            &crate::proc_macro::bridge::server::SameThread,
+                            crate::rustc_server::Rustc::default(),
+                            parsed_body,
+                        );
+
+                        return res.map(|it| it.subtree);
+                    }
+
+                    bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => {
+                        let res = client.run(
+                            &crate::proc_macro::bridge::server::SameThread,
+                            crate::rustc_server::Rustc::default(),
+                            parsed_attributes,
+                            parsed_body,
+                        );
+
+                        return res.map(|it| it.subtree);
+                    }
+
+                    _ => {
+                        continue;
+                    }
+                }
+            }
+        }
+
+        Err(bridge::PanicMessage::String("Nothing to expand".to_string()))
+    }
+
+    pub fn list_macros(&self) -> Result<Vec<(String, ProcMacroKind)>, bridge::PanicMessage> {
+        let mut result = vec![];
+
+        for lib in &self.libs {
+            for proc_macro in &lib.exported_macros {
+                let res = match proc_macro {
+                    bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
+                        (trait_name.to_string(), ProcMacroKind::CustomDerive)
+                    }
+                    bridge::client::ProcMacro::Bang { name, .. } => {
+                        (name.to_string(), ProcMacroKind::FuncLike)
+                    }
+                    bridge::client::ProcMacro::Attr { name, .. } => {
+                        (name.to_string(), ProcMacroKind::Attr)
+                    }
+                };
+                result.push(res);
+            }
+        }
+
+        Ok(result)
+    }
+}
diff --git a/crates/ra_proc_macro_srv/src/lib.rs b/crates/ra_proc_macro_srv/src/lib.rs
index f376df2367f..f5a526dbf35 100644
--- a/crates/ra_proc_macro_srv/src/lib.rs
+++ b/crates/ra_proc_macro_srv/src/lib.rs
@@ -17,6 +17,8 @@ mod proc_macro;
 #[doc(hidden)]
 mod rustc_server;
 
+mod dylib;
+
 use proc_macro::bridge::client::TokenStream;
 use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask};
 
diff --git a/crates/ra_proc_macro_srv/src/rustc_server.rs b/crates/ra_proc_macro_srv/src/rustc_server.rs
index 92d1fd989d3..ec0d356922d 100644
--- a/crates/ra_proc_macro_srv/src/rustc_server.rs
+++ b/crates/ra_proc_macro_srv/src/rustc_server.rs
@@ -34,6 +34,10 @@ impl TokenStream {
         TokenStream { subtree: Default::default() }
     }
 
+    pub fn with_subtree(subtree: tt::Subtree) -> Self {
+        TokenStream { subtree }
+    }
+
     pub fn is_empty(&self) -> bool {
         self.subtree.token_trees.is_empty()
     }