Эх сурвалжийг харах

Use ld.lld/wasm-ld as a library

This has the advantage that solang does not depend on the ld.lld
or wasm-ld executable.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 5 жил өмнө
parent
commit
c046e64db8
7 өөрчлөгдсөн 170 нэмэгдсэн , 59 устгасан
  1. 2 1
      Cargo.toml
  2. 0 2
      Dockerfile
  3. 41 0
      build.rs
  4. 31 26
      src/linker/bpf.rs
  5. 16 0
      src/linker/linker.cpp
  6. 46 0
      src/linker/mod.rs
  7. 34 30
      src/linker/wasm.rs

+ 2 - 1
Cargo.toml

@@ -11,6 +11,7 @@ keywords = [ "solidity", "compiler", "ewasm", "llvm", "substrate" ]
 
 [build-dependencies]
 lalrpop = "0.19"
+cc = "1.0"
 
 [dependencies]
 lalrpop-util = "0.19"
@@ -34,6 +35,7 @@ handlebars = "3.4"
 contract-metadata = "0.1.0"
 semver = { version = "0.10.0", features = ["serde"] }
 tempfile = "3.1"
+libc = "0.2"
 
 [dev-dependencies]
 parity-scale-codec-derive = "1.2"
@@ -45,7 +47,6 @@ rand = "0.7"
 sha2 = "0.9"
 solana_rbpf = "=0.1.32"
 byteorder = "1.3"
-libc = "0.2"
 
 [profile.release]
 lto = true

+ 0 - 2
Dockerfile

@@ -17,8 +17,6 @@ RUN cargo build --release
 
 FROM ubuntu:18.04
 COPY --from=builder /src/target/release/solang /usr/bin/solang
-COPY --from=builder /llvm10.0/bin/wasm-ld /usr/bin/
-COPY --from=builder /llvm10.0/bin/ld.lld /usr/bin/
 
 ENV PATH="/llvm10.0/bin:${PATH}"
 

+ 41 - 0
build.rs

@@ -1,4 +1,6 @@
+extern crate cc;
 extern crate lalrpop;
+
 use std::process::Command;
 
 fn main() {
@@ -8,6 +10,45 @@ fn main() {
         .process()
         .unwrap();
 
+    // compile our linker
+    let cxxflags = Command::new("llvm-config")
+        .args(&["--cxxflags"])
+        .output()
+        .unwrap();
+
+    let cxxflags = String::from_utf8(cxxflags.stdout).unwrap();
+
+    let mut build = cc::Build::new();
+
+    build.file("src/linker/linker.cpp").cpp(true);
+
+    if !cfg!(target_os = "windows") {
+        build.flag("-Wno-unused-parameter");
+    }
+
+    for flag in cxxflags.split_whitespace() {
+        build.flag(flag);
+    }
+
+    build.compile("liblinker.a");
+
+    // add the llvm linker
+    let libdir = Command::new("llvm-config")
+        .args(&["--libdir"])
+        .output()
+        .unwrap();
+    let libdir = String::from_utf8(libdir.stdout).unwrap();
+
+    println!("cargo:libdir={}", libdir);
+    for lib in &["lldELF", "lldDriver", "lldCore", "lldCommon", "lldWasm"] {
+        println!("cargo:rustc-link-lib=static={}", lib);
+    }
+
+    // And all the symbols were not using, needed by Windows and debug builds
+    for lib in &["lldReaderWriter", "lldMachO", "lldYAML"] {
+        println!("cargo:rustc-link-lib=static={}", lib);
+    }
+
     // note: add error checking yourself.
     let output = Command::new("git")
         .args(&["describe", "--tags"])

+ 31 - 26
src/linker/bpf.rs

@@ -7,17 +7,18 @@
 // Using the llvm linker does give some possibilities around linking non-Solidity files
 // and doing link time optimizations
 
+use std::ffi::CString;
 use std::fs::File;
 use std::io::Read;
 use std::io::Write;
-use std::process::Command;
 use tempfile::tempdir;
+
 pub fn link(input: &[u8], name: &str) -> Vec<u8> {
     let dir = tempdir().expect("failed to create temp directory for linking");
 
     let object_filename = dir.path().join(&format!("{}.o", name));
-    let ldscript_filename = dir.path().join("bpf.ld");
     let res_filename = dir.path().join(&format!("{}.so", name));
+    let linker_script_filename = dir.path().join("linker.ld");
 
     let mut objectfile =
         File::create(object_filename.clone()).expect("failed to create object file");
@@ -27,7 +28,7 @@ pub fn link(input: &[u8], name: &str) -> Vec<u8> {
         .expect("failed to write object file to temp file");
 
     let mut linker_script =
-        File::create(ldscript_filename.clone()).expect("failed to create object file");
+        File::create(linker_script_filename.clone()).expect("failed to create linker script");
 
     linker_script
         .write_all(
@@ -53,31 +54,35 @@ SECTIONS
         )
         .expect("failed to write linker script to temp file");
 
-    let command_line = format!(
-        "ld.lld  -z notext -shared --Bdynamic {} --entry entrypoint {} -o {}",
-        ldscript_filename
-            .to_str()
-            .expect("temp path should be unicode"),
-        object_filename
-            .to_str()
-            .expect("temp path should be unicode"),
-        res_filename.to_str().expect("temp path should be unicode"),
+    let mut command_line = Vec::new();
+
+    command_line.push(CString::new("-z").unwrap());
+    command_line.push(CString::new("notext").unwrap());
+    command_line.push(CString::new("-shared").unwrap());
+    command_line.push(CString::new("--Bdynamic").unwrap());
+    command_line.push(
+        CString::new(
+            linker_script_filename
+                .to_str()
+                .expect("temp path should be unicode"),
+        )
+        .unwrap(),
+    );
+    command_line.push(CString::new("--entry").unwrap());
+    command_line.push(CString::new("entrypoint").unwrap());
+    command_line.push(
+        CString::new(
+            object_filename
+                .to_str()
+                .expect("temp path should be unicode"),
+        )
+        .unwrap(),
     );
+    command_line.push(CString::new("-o").unwrap());
+    command_line
+        .push(CString::new(res_filename.to_str().expect("temp path should be unicode")).unwrap());
 
-    let status = if cfg!(target_os = "windows") {
-        Command::new("cmd")
-            .args(&["/C", &command_line])
-            .status()
-            .expect("linker failed")
-    } else {
-        Command::new("sh")
-            .arg("-c")
-            .arg(command_line)
-            .status()
-            .expect("linker failed")
-    };
-
-    if !status.success() {
+    if super::elf_linker(&command_line) {
         panic!("linker failed");
     }
 

+ 16 - 0
src/linker/linker.cpp

@@ -0,0 +1,16 @@
+// Call the LLD linker
+#include "lld/Common/Driver.h"
+
+extern "C" bool LLDWasmLink(const char *argv[], size_t length)
+{
+	llvm::ArrayRef<const char *> args(argv, length);
+
+	return lld::wasm::link(args, false, llvm::outs(), llvm::errs());
+}
+
+extern "C" bool LLDELFLink(const char *argv[], size_t length)
+{
+	llvm::ArrayRef<const char *> args(argv, length);
+
+	return lld::elf::link(args, false, llvm::outs(), llvm::errs());
+}

+ 46 - 0
src/linker/mod.rs

@@ -2,12 +2,58 @@ mod bpf;
 mod wasm;
 
 use crate::Target;
+use std::ffi::CString;
+use std::sync::Mutex;
+
+lazy_static::lazy_static! {
+    static ref LINKER_MUTEX: Mutex<i32> = Mutex::new(0i32);
+}
 
 /// Take an object file and turn it into a final linked binary ready for deployment
 pub fn link(input: &[u8], name: &str, target: Target) -> Vec<u8> {
+    // The lld linker is totally not thread-safe; it uses many globals
+    // We should fix this one day
+    let _lock = LINKER_MUTEX.lock().unwrap();
+
     if target == Target::Solana {
         bpf::link(input, name)
     } else {
         wasm::link(input, name, target)
     }
 }
+
+extern "C" {
+    fn LLDELFLink(args: *const *const libc::c_char, size: libc::size_t) -> libc::c_int;
+}
+
+pub fn elf_linker(args: &[CString]) -> bool {
+    let mut command_line: Vec<*const libc::c_char> = Vec::with_capacity(args.len() + 1);
+
+    let executable_name = CString::new("ld.lld").unwrap();
+
+    command_line.push(executable_name.as_ptr());
+
+    for arg in args {
+        command_line.push(arg.as_ptr());
+    }
+
+    unsafe { LLDELFLink(command_line.as_ptr(), command_line.len()) == 0 }
+}
+
+extern "C" {
+    fn LLDWasmLink(args: *const *const libc::c_char, size: libc::size_t) -> libc::c_int;
+}
+
+pub fn wasm_linker(args: &[CString]) -> bool {
+    let mut command_line: Vec<*const libc::c_char> = Vec::with_capacity(args.len() + 1);
+
+    let executable_name = CString::new("wasm-ld").unwrap();
+
+    command_line.push(executable_name.as_ptr());
+
+    for arg in args {
+        command_line.push(arg.as_ptr());
+    }
+
+    unsafe { LLDWasmLink(command_line.as_ptr(), command_line.len()) == 0 }
+}

+ 34 - 30
src/linker/wasm.rs

@@ -1,10 +1,10 @@
 use parity_wasm;
 use parity_wasm::builder;
 use parity_wasm::elements::{InitExpr, Instruction, Module};
+use std::ffi::CString;
 use std::fs::File;
 use std::io::Read;
 use std::io::Write;
-use std::process::Command;
 use tempfile::tempdir;
 use Target;
 
@@ -26,45 +26,49 @@ pub fn link(input: &[u8], name: &str, target: Target) -> Vec<u8> {
         .write_all(input)
         .expect("failed to write object file to temp file");
 
-    let mut command_line =
-        String::from("wasm-ld -O3 --no-entry --allow-undefined --gc-sections --global-base=0");
+    let mut command_line = Vec::new();
+
+    command_line.push(CString::new("-O3").unwrap());
+    command_line.push(CString::new("--no-entry").unwrap());
+    command_line.push(CString::new("--allow-undefined").unwrap());
+    command_line.push(CString::new("--gc-sections").unwrap());
+    command_line.push(CString::new("--global-base=0").unwrap());
 
     match target {
         Target::Ewasm => {
-            command_line.push_str(" --export main");
+            command_line.push(CString::new("--export").unwrap());
+            command_line.push(CString::new("main").unwrap());
         }
         Target::Sabre => {
-            command_line.push_str(" --export entrypoint");
+            command_line.push(CString::new("--export").unwrap());
+            command_line.push(CString::new("entrypoint").unwrap());
         }
         Target::Substrate => {
-            command_line.push_str(" --export deploy --export call");
-            command_line.push_str(" --import-memory --initial-memory=1048576 --max-memory=1048576");
+            command_line.push(CString::new("--export").unwrap());
+            command_line.push(CString::new("deploy").unwrap());
+            command_line.push(CString::new("--export").unwrap());
+            command_line.push(CString::new("call").unwrap());
+
+            command_line.push(CString::new("--import-memory").unwrap());
+            command_line.push(CString::new("--initial-memory=1048576").unwrap());
+            command_line.push(CString::new("--max-memory=1048576").unwrap());
         }
-        _ => unreachable!(),
+        _ => (),
     }
 
-    command_line.push_str(&format!(
-        " {} -o {}",
-        object_filename
-            .to_str()
-            .expect("temp path should be unicode"),
-        res_filename.to_str().expect("temp path should be unicode")
-    ));
-
-    let status = if cfg!(target_os = "windows") {
-        Command::new("cmd")
-            .args(&["/C", &command_line])
-            .status()
-            .expect("linker failed")
-    } else {
-        Command::new("sh")
-            .arg("-c")
-            .arg(command_line)
-            .status()
-            .expect("linker failed")
-    };
-
-    if !status.success() {
+    command_line.push(
+        CString::new(
+            object_filename
+                .to_str()
+                .expect("temp path should be unicode"),
+        )
+        .unwrap(),
+    );
+    command_line.push(CString::new("-o").unwrap());
+    command_line
+        .push(CString::new(res_filename.to_str().expect("temp path should be unicode")).unwrap());
+
+    if super::wasm_linker(&command_line) {
         panic!("linker failed");
     }