Separate compilation and linker phases (#376)

Separate between compilation and linker phases to allow deploy time
linking and back-porting era compiler changes to fix #91. Unlinked
contract binaries (caused by missing libraries or missing factory
dependencies in turn) are emitted as raw ELF object.

Few drive by fixes:
- #98
- A compiler panic on missing libraries definitions.
- Fixes some incosistent type forwarding in JSON output (empty string
vs. null object).
- Remove the unused fallback for size optimization setting.
- Remove the broken `--lvm-ir`  mode.
- CI workflow fixes.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
xermicus
2025-09-27 20:52:22 +02:00
committed by GitHub
parent 13faedf08a
commit 94ec34c4d5
169 changed files with 6288 additions and 5206 deletions
+114
View File
@@ -0,0 +1,114 @@
//! The revive ELF object linker library.
use std::{ffi::CString, fs, path::PathBuf, sync::Mutex};
use lld_sys::LLDELFLink;
use tempfile::TempDir;
use revive_builtins::COMPILER_RT;
static GUARD: Mutex<()> = Mutex::new(());
/// The revive ELF object linker.
pub struct ElfLinker {
temporary_directory: TempDir,
output_path: PathBuf,
object_path: PathBuf,
symbols_path: PathBuf,
linker_script_path: PathBuf,
}
impl ElfLinker {
const LINKER_SCRIPT: &str = r#"
SECTIONS {
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
}"#;
const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
const BUILTINS_LIB_NAME: &str = "clang_rt.builtins-riscv64";
/// The setup routine prepares a temporary working directory.
pub fn setup() -> anyhow::Result<Self> {
let temporary_directory = TempDir::new()?;
let object_path = temporary_directory.path().join("obj.o");
let output_path = temporary_directory.path().join("out.o");
let symbols_path = temporary_directory.path().join("sym.o");
let linker_script_path = temporary_directory.path().join("linker.ld");
fs::write(&linker_script_path, Self::LINKER_SCRIPT)
.map_err(|message| anyhow::anyhow!("{message} {linker_script_path:?}",))?;
let compiler_rt_path = temporary_directory.path().join(Self::BUILTINS_ARCHIVE_FILE);
fs::write(&compiler_rt_path, COMPILER_RT)
.map_err(|message| anyhow::anyhow!("{message} {compiler_rt_path:?}"))?;
Ok(Self {
temporary_directory,
output_path,
object_path,
symbols_path,
linker_script_path,
})
}
/// Link `input` with `symbols` and the `compiler_rt` via `LLD`.
pub fn link<T: AsRef<[u8]>>(self, input: T, symbols: T) -> anyhow::Result<Vec<u8>> {
fs::write(&self.object_path, input)
.map_err(|message| anyhow::anyhow!("{message} {:?}", self.object_path))?;
fs::write(&self.symbols_path, symbols)
.map_err(|message| anyhow::anyhow!("{message} {:?}", self.symbols_path))?;
if lld(self
.create_arguments()
.into_iter()
.map(|v| v.to_string())
.collect())
{
return Err(anyhow::anyhow!("ld.lld failed"));
}
Ok(fs::read(&self.output_path)?)
}
/// The argument creation helper function.
fn create_arguments(&self) -> Vec<String> {
[
"ld.lld",
"--error-limit=0",
"--relocatable",
"--emit-relocs",
"--no-relax",
"--unique",
"--gc-sections",
self.linker_script_path.to_str().expect("should be utf8"),
"-o",
self.output_path.to_str().expect("should be utf8"),
self.object_path.to_str().expect("should be utf8"),
self.symbols_path.to_str().expect("should be utf8"),
"--library-path",
self.temporary_directory
.path()
.to_str()
.expect("should be utf8"),
"--library",
Self::BUILTINS_LIB_NAME,
]
.iter()
.map(ToString::to_string)
.collect()
}
}
/// The thread-safe LLD helper function.
fn lld(arguments: Vec<String>) -> bool {
let c_strings = arguments
.into_iter()
.map(|arg| CString::new(arg).expect("ld.lld args should not contain null bytes"))
.collect::<Vec<_>>();
let args: Vec<*const libc::c_char> = c_strings.iter().map(|arg| arg.as_ptr()).collect();
let _lock = GUARD.lock().expect("ICE: linker mutex should not poison");
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
}
+3 -75
View File
@@ -1,76 +1,4 @@
use std::{env, ffi::CString, fs};
//! The revive ELF object to PVM blob linker library.
use lld_sys::LLDELFLink;
use revive_builtins::COMPILER_RT;
const LINKER_SCRIPT: &str = r#"
SECTIONS {
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
}"#;
const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
const BUILTINS_LIB_NAME: &str = "clang_rt.builtins-riscv64";
fn invoke_lld(cmd_args: &[&str]) -> bool {
let c_strings = cmd_args
.iter()
.map(|arg| CString::new(*arg).expect("ld.lld args should not contain null bytes"))
.collect::<Vec<_>>();
let args: Vec<*const libc::c_char> = c_strings.iter().map(|arg| arg.as_ptr()).collect();
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
}
pub fn polkavm_linker<T: AsRef<[u8]>>(code: T, strip_binary: bool) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default();
config.set_strip(strip_binary);
config.set_optimize(true);
polkavm_linker::program_from_elf(config, code.as_ref())
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
}
pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
let dir = tempfile::tempdir().expect("failed to create temp directory for linking");
let output_path = dir.path().join("out.so");
let object_path = dir.path().join("out.o");
let linker_script_path = dir.path().join("linker.ld");
let compiler_rt_path = dir.path().join(BUILTINS_ARCHIVE_FILE);
fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?;
if env::var("PVM_LINKER_DUMP_OBJ").is_ok() {
fs::copy(&object_path, "/tmp/out.o")?;
}
fs::write(&linker_script_path, LINKER_SCRIPT)
.map_err(|msg| anyhow::anyhow!("{msg} {linker_script_path:?}"))?;
fs::write(&compiler_rt_path, COMPILER_RT)
.map_err(|msg| anyhow::anyhow!("{msg} {compiler_rt_path:?}"))?;
let ld_args = [
"ld.lld",
"--error-limit=0",
"--relocatable",
"--emit-relocs",
"--no-relax",
"--unique",
"--gc-sections",
"--library-path",
dir.path().to_str().expect("should be utf8"),
"--library",
BUILTINS_LIB_NAME,
linker_script_path.to_str().expect("should be utf8"),
object_path.to_str().expect("should be utf8"),
"-o",
output_path.to_str().expect("should be utf8"),
];
if invoke_lld(&ld_args) {
return Err(anyhow::anyhow!("ld.lld failed"));
}
Ok(fs::read(&output_path)?)
}
pub mod elf;
pub mod pvm;
+10
View File
@@ -0,0 +1,10 @@
//! The revive PVM blob linker library.
pub fn polkavm_linker<T: AsRef<[u8]>>(code: T, strip_binary: bool) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default();
config.set_strip(strip_binary);
config.set_optimize(true);
polkavm_linker::program_from_elf(config, code.as_ref())
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
}