1use std::{
2 ffi::OsString,
3 path::{Path, PathBuf},
4};
5
6pub 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#[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 pub fn new() -> Self {
30 Self { cc: None, linker: None, cflags: vec![] }
31 }
32
33 pub fn cc(&mut self, cc: Option<PathBuf>) {
35 self.cc = cc;
36 }
37
38 pub fn linker(&mut self, linker: Option<PathBuf>) {
40 self.linker = linker;
41 }
42
43 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 #[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 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 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}