Skip to main content

revmc_codegen/
linker.rs

1use std::{
2    ffi::OsString,
3    path::{Path, PathBuf},
4};
5
6/// Returns the path for a platform-native shared library filename.
7pub fn shared_library_path(dir: &Path, stem: &str) -> PathBuf {
8    let mut file_name = OsString::from(stem);
9    file_name.push(std::env::consts::DLL_SUFFIX);
10    dir.join(file_name)
11}
12
13/// EVM bytecode compiler linker.
14#[derive(Debug)]
15pub struct Linker {
16    cc: Option<PathBuf>,
17    linker: Option<PathBuf>,
18    cflags: Vec<String>,
19}
20
21impl Default for Linker {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl Linker {
28    /// Creates a new linker.
29    pub fn new() -> Self {
30        Self { cc: None, linker: None, cflags: vec![] }
31    }
32
33    /// Sets the C compiler to use for linking. Default: "cc".
34    pub fn cc(&mut self, cc: Option<PathBuf>) {
35        self.cc = cc;
36    }
37
38    /// Sets the linker to use for linking. Default: `lld`.
39    pub fn linker(&mut self, linker: Option<PathBuf>) {
40        self.linker = linker;
41    }
42
43    /// Sets the C compiler flags to use for linking.
44    pub fn cflags(&mut self, cflags: impl IntoIterator<Item = impl Into<String>>) {
45        self.cflags.extend(cflags.into_iter().map(Into::into));
46    }
47
48    /// Links the given object files into a shared library at the given path.
49    #[instrument(level = "debug", skip_all)]
50    pub fn link(
51        &self,
52        out: &Path,
53        objects: impl IntoIterator<Item = impl AsRef<std::ffi::OsStr>>,
54    ) -> std::io::Result<()> {
55        let storage;
56        let cc = match &self.cc {
57            Some(cc) => cc,
58            None => {
59                let str = match std::env::var_os("CC") {
60                    Some(cc) => {
61                        storage = cc;
62                        storage.as_os_str()
63                    }
64                    None => "cc".as_ref(),
65                };
66                Path::new(str)
67            }
68        };
69
70        let mut cmd = std::process::Command::new(cc);
71        cmd.arg("-o").arg(out);
72        cmd.arg("-shared");
73        cmd.arg("-O3");
74        if let Some(linker) = &self.linker {
75            cmd.arg(format!("-fuse-ld={}", linker.display()));
76        } else if !cfg!(target_vendor = "apple") {
77            cmd.arg("-fuse-ld=lld");
78        }
79        if cfg!(target_vendor = "apple") {
80            cmd.arg("-Wl,-dead_strip,-undefined,dynamic_lookup");
81        } else {
82            cmd.arg("-Wl,--gc-sections,--strip-debug");
83        }
84        cmd.args(&self.cflags);
85        cmd.args(objects);
86        debug!(cmd=?cmd.get_program(), "linking");
87        trace!(?cmd, "full linking command");
88        let output = cmd.output()?;
89        if !output.status.success() {
90            return Err(std::io::Error::other(format!("cc failed with {output:#?}")));
91        }
92        Ok(())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn shared_library_path_uses_platform_suffix() {
102        let dir = Path::new("/tmp");
103        let path = shared_library_path(dir, "out");
104        let expected = format!("out{}", std::env::consts::DLL_SUFFIX);
105
106        assert_eq!(path, dir.join(expected));
107    }
108
109    #[cfg(feature = "llvm")]
110    #[test]
111    fn basic() {
112        use revm_primitives::hardfork::SpecId;
113
114        let tmp = tempfile::tempdir().expect("could not create temp dir");
115        let obj = tmp.path().join("out.o");
116        let shared_lib = shared_library_path(tmp.path(), "out");
117
118        // Compile and build object file.
119        let mut compiler = crate::EvmCompiler::new_llvm(true).unwrap();
120        if let Err(e) = compiler.translate("link_test_basic", &[][..], SpecId::CANCUN) {
121            panic!("failed to compile: {e}");
122        }
123
124        if let Err(e) = compiler.write_object_to_file(&obj) {
125            panic!("failed to write object: {e}");
126        }
127        assert!(obj.exists());
128
129        // Link object to shared library.
130        let mut linker = Linker::new();
131        let mut n = 0;
132        for driver in ["cc", "gcc", "clang"] {
133            if !command_v(driver) {
134                continue;
135            }
136            n += 1;
137
138            let _ = std::fs::remove_file(&shared_lib);
139            linker.cc = Some(driver.into());
140            if let Err(e) = linker.link(&shared_lib, [&obj]) {
141                panic!("failed to link with {driver}: {e}");
142            }
143            assert!(shared_lib.exists());
144        }
145        assert!(n > 0, "no C compiler found");
146    }
147
148    fn command_v(cmd: &str) -> bool {
149        let Ok(output) = std::process::Command::new(cmd).arg("--version").output() else {
150            return false;
151        };
152        if !output.status.success() {
153            eprintln!("command {cmd} failed: {output:#?}");
154            return false;
155        }
156        true
157    }
158}