From e83e4f04e6617d19f751b04f56f2e0d61ada9d38 Mon Sep 17 00:00:00 2001 From: xermicus Date: Wed, 20 Mar 2024 10:59:02 +0100 Subject: [PATCH] add crate for custom isa extensions Signed-off-by: xermicus --- Cargo.lock | 7 ++++++ crates/extensions/Cargo.toml | 9 ++++++++ crates/extensions/bswap.ll | 13 +++++++++++ crates/extensions/build.rs | 40 +++++++++++++++++++++++++++++++++ crates/extensions/src/lib.rs | 40 +++++++++++++++++++++++++++++++++ crates/linker/polkavm_guest.bc | Bin 36020 -> 0 bytes 6 files changed, 109 insertions(+) create mode 100644 crates/extensions/Cargo.toml create mode 100644 crates/extensions/bswap.ll create mode 100644 crates/extensions/build.rs create mode 100644 crates/extensions/src/lib.rs delete mode 100644 crates/linker/polkavm_guest.bc diff --git a/Cargo.lock b/Cargo.lock index 4e695c7..3b5f9b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1415,6 +1415,13 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "revive-builtins" version = "0.1.0" +[[package]] +name = "revive-extensions" +version = "0.1.0" +dependencies = [ + "inkwell", +] + [[package]] name = "revive-integration" version = "0.1.0" diff --git a/crates/extensions/Cargo.toml b/crates/extensions/Cargo.toml new file mode 100644 index 0000000..06fc411 --- /dev/null +++ b/crates/extensions/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "revive-extensions" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +inkwell = { workspace = true, no-default-features = true, features = ["target-riscv", "no-libffi-linking", "llvm16-0"] } diff --git a/crates/extensions/bswap.ll b/crates/extensions/bswap.ll new file mode 100644 index 0000000..36a4b4a --- /dev/null +++ b/crates/extensions/bswap.ll @@ -0,0 +1,13 @@ +target datalayout = "e-m:e-p:32:32-i64:64-n32-S128" +target triple = "riscv32-unknown-unknown-elf" + +define dso_local noundef i256 @__bswap(i256 noundef %0) local_unnamed_addr #0 { + %2 = tail call i256 @llvm.bswap.i256(i256 %0) + ret i256 %2 +} + +; Function Attrs: mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare i256 @llvm.bswap.i256(i256) #1 + +attributes #0 = { alwaysinline mustprogress nofree norecurse nosync nounwind willreturn memory(none) } +attributes #1 = { mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) } diff --git a/crates/extensions/build.rs b/crates/extensions/build.rs new file mode 100644 index 0000000..c7b657a --- /dev/null +++ b/crates/extensions/build.rs @@ -0,0 +1,40 @@ +use std::{env, fs, path::Path, process::Command}; + +fn compile(source_path: &str, output_path: &str) { + let output = Command::new("llc") + .args([ + "-O3", + "-filetype=asm", + "-mattr=+zbb,+e", + source_path, + "-o", + output_path, + ]) + .output() + .expect("should be able to invoke llc"); + + assert!( + output.status.success(), + "failed to compile {}: {:?}", + source_path, + output + ); +} + +fn main() { + let in_file = "bswap.ll"; + let out_file = "bswap.s"; + let out_dir = env::var_os("OUT_DIR").expect("env should have $OUT_DIR"); + let out_path = Path::new(&out_dir).join(out_file); + compile( + in_file, + out_path.to_str().expect("$OUT_DIR should be UTF-8"), + ); + + let src_path = Path::new(&out_dir).join("bswap.rs"); + let src = format!("pub static ASSEMBLY: &str = include_str!(\"{out_file}\");"); + fs::write(src_path, src).expect("should be able to write in $OUT_DIR"); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=bswap.ll"); +} diff --git a/crates/extensions/src/lib.rs b/crates/extensions/src/lib.rs new file mode 100644 index 0000000..171705b --- /dev/null +++ b/crates/extensions/src/lib.rs @@ -0,0 +1,40 @@ +//! Custom RISC-V extension in PolkaVM that are partially supported. +//! We use inline assembly to emit partially supported instructions. + +use inkwell::{context::Context, module::Module, support::LLVMString}; + +include!(concat!(env!("OUT_DIR"), "/bswap.rs")); + +/// Returns a LLVM module containing a `__bswap` function, which +/// - Takes a `i256` value argument +/// - Byte swaps it using `rev8` from the `zbb` extension +/// - Returns the `i256` value +/// +/// Returns `Error` if the module fails to validate, which should never happen. +pub fn module<'context>( + context: &'context Context, + module_name: &str, +) -> Result, LLVMString> { + let module = context.create_module(module_name); + + module.set_inline_assembly(ASSEMBLY); + module.verify()?; + + Ok(module) +} + +#[cfg(test)] +mod tests { + #[test] + fn assembly_contains_rev8_instruction() { + assert!(crate::ASSEMBLY.contains("rev8")); + } + + #[test] + fn module_is_valid() { + inkwell::targets::Target::initialize_riscv(&Default::default()); + let context = inkwell::context::Context::create(); + + assert!(crate::module(&context, "polkavm_bswap").is_ok()); + } +} diff --git a/crates/linker/polkavm_guest.bc b/crates/linker/polkavm_guest.bc deleted file mode 100644 index fa532df633916d34b72cbeff3e06c7f23c3454f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36020 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 Aod5s;