Contracts build risc-v fixtures (#2554)

Follow up from #2347 this time to verify that fixtures build to RISC-V

---------

Co-authored-by: alvicsam <alvicsam@gmail.com>
Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
This commit is contained in:
PG Herveou
2024-01-04 15:49:42 +01:00
committed by GitHub
parent 6f9b1f61ec
commit f82c297728
14 changed files with 647 additions and 150 deletions
@@ -21,5 +21,5 @@ parity-wasm = "0.45.0"
tempfile = "3.8.1"
toml = "0.8.2"
twox-hash = "1.6.3"
polkavm-linker = "0.3.0"
anyhow = "1.0.0"
cfg-if = { version = "1.0", default-features = false }
+110 -45
View File
@@ -16,7 +16,7 @@
// limitations under the License.
//! Compile contracts to wasm and RISC-V binaries.
use anyhow::Result;
use anyhow::{bail, format_err, Context, Result};
use parity_wasm::elements::{deserialize_file, serialize_to_file, Internal};
use std::{
env, fs,
@@ -65,16 +65,41 @@ impl Entry {
.expect("name is valid unicode; qed")
}
/// Return whether the contract has already been compiled.
fn is_cached(&self, out_dir: &Path) -> bool {
out_dir.join(self.name()).join(&self.hash).exists()
}
/// Update the cache file for the contract.
fn update_cache(&self, out_dir: &Path) -> Result<()> {
let cache_dir = out_dir.join(self.name());
// clear the cache dir if it exists
if cache_dir.exists() {
fs::remove_dir_all(&cache_dir)?;
}
// re-populate the cache dir with the new hash
fs::create_dir_all(&cache_dir)?;
fs::write(out_dir.join(&self.hash), "")?;
Ok(())
}
/// Return the name of the output wasm file.
fn out_wasm_filename(&self) -> String {
format!("{}.wasm", self.name())
}
/// Return the name of the RISC-V polkavm file.
fn out_riscv_filename(&self) -> String {
format!("{}.polkavm", self.name())
}
}
/// Collect all contract entries from the given source directory.
/// Contracts that have already been compiled are filtered out.
fn collect_entries(contracts_dir: &Path, out_dir: &Path) -> Vec<Entry> {
fs::read_dir(&contracts_dir)
fs::read_dir(contracts_dir)
.expect("src dir exists; qed")
.filter_map(|file| {
let path = file.expect("file exists; qed").path();
@@ -83,7 +108,7 @@ fn collect_entries(contracts_dir: &Path, out_dir: &Path) -> Vec<Entry> {
}
let entry = Entry::new(path);
if out_dir.join(&entry.hash).exists() {
if entry.is_cached(out_dir) {
None
} else {
Some(entry)
@@ -98,41 +123,29 @@ fn create_cargo_toml<'a>(
entries: impl Iterator<Item = &'a Entry>,
output_dir: &Path,
) -> Result<()> {
let uapi_path = fixtures_dir.join("../uapi").canonicalize()?;
let common_path = fixtures_dir.join("./contracts/common").canonicalize()?;
let mut cargo_toml: toml::Value = toml::from_str(&format!(
"
[package]
name = 'contracts'
version = '0.1.0'
edition = '2021'
let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?;
let mut set_dep = |name, path| -> Result<()> {
cargo_toml["dependencies"][name]["path"] = toml::Value::String(
fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(),
);
Ok(())
};
set_dep("uapi", "../uapi")?;
set_dep("common", "./contracts/common")?;
# Binary targets are injected below.
[[bin]]
[dependencies]
uapi = {{ package = 'pallet-contracts-uapi', default-features = false, path = {uapi_path:?}}}
common = {{ package = 'pallet-contracts-fixtures-common', path = {common_path:?}}}
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
"
))?;
let binaries = entries
.map(|entry| {
let name = entry.name();
let path = entry.path();
toml::Value::Table(toml::toml! {
name = name
path = path
cargo_toml["bin"] = toml::Value::Array(
entries
.map(|entry| {
let name = entry.name();
let path = entry.path();
toml::Value::Table(toml::toml! {
name = name
path = path
})
})
})
.collect::<Vec<_>>();
.collect::<Vec<_>>(),
);
cargo_toml["bin"] = toml::Value::Array(binaries);
let cargo_toml = toml::to_string_pretty(&cargo_toml)?;
fs::write(output_dir.join("Cargo.toml"), cargo_toml).map_err(Into::into)
}
@@ -145,7 +158,7 @@ fn invoke_cargo_fmt<'a>(
) -> Result<()> {
// If rustfmt is not installed, skip the check.
if !Command::new("rustup")
.args(&["run", "nightly", "rustfmt", "--version"])
.args(["run", "nightly", "rustfmt", "--version"])
.output()
.map_or(false, |o| o.status.success())
{
@@ -153,7 +166,7 @@ fn invoke_cargo_fmt<'a>(
}
let fmt_res = Command::new("rustup")
.args(&["run", "nightly", "rustfmt", "--check", "--config-path"])
.args(["run", "nightly", "rustfmt", "--check", "--config-path"])
.arg(config_path)
.args(files)
.output()
@@ -176,8 +189,8 @@ fn invoke_cargo_fmt<'a>(
anyhow::bail!("Fixtures files are not formatted")
}
/// Invoke `cargo build` to compile the contracts.
fn invoke_build(current_dir: &Path) -> Result<()> {
/// Build contracts for wasm.
fn invoke_wasm_build(current_dir: &Path) -> Result<()> {
let encoded_rustflags = [
"-Clink-arg=-zstack-size=65536",
"-Clink-arg=--import-memory",
@@ -190,7 +203,7 @@ fn invoke_build(current_dir: &Path) -> Result<()> {
let build_res = Command::new(env::var("CARGO")?)
.current_dir(current_dir)
.env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags)
.args(&["build", "--release", "--target=wasm32-unknown-unknown"])
.args(["build", "--release", "--target=wasm32-unknown-unknown"])
.output()
.expect("failed to execute process");
@@ -200,12 +213,13 @@ fn invoke_build(current_dir: &Path) -> Result<()> {
let stderr = String::from_utf8_lossy(&build_res.stderr);
eprintln!("{}", stderr);
anyhow::bail!("Failed to build contracts");
bail!("Failed to build wasm contracts");
}
/// Post-process the compiled wasm contracts.
fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> {
let mut module = deserialize_file(input_path)?;
let mut module =
deserialize_file(input_path).with_context(|| format!("Failed to read {:?}", input_path))?;
if let Some(section) = module.export_section_mut() {
section.entries_mut().retain(|entry| {
matches!(entry.internal(), Internal::Function(_)) &&
@@ -216,6 +230,50 @@ fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> {
serialize_to_file(output_path, module).map_err(Into::into)
}
/// Build contracts for RISC-V.
fn invoke_riscv_build(current_dir: &Path) -> Result<()> {
let encoded_rustflags =
["-Crelocation-model=pie", "-Clink-arg=--emit-relocs", "-Clink-arg=-Tmemory.ld"]
.join("\x1f");
fs::write(current_dir.join("memory.ld"), include_bytes!("./build/riscv_memory_layout.ld"))?;
let build_res = Command::new(env::var("CARGO")?)
.current_dir(current_dir)
.env_clear()
.env("PATH", env::var("PATH").unwrap())
.env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags)
.env("RUSTUP_TOOLCHAIN", "rve-nightly")
.env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap())
.args(["build", "--release", "--target=riscv32em-unknown-none-elf"])
.output()
.expect("failed to execute process");
if build_res.status.success() {
return Ok(())
}
let stderr = String::from_utf8_lossy(&build_res.stderr);
if stderr.contains("'rve-nightly' is not installed") {
eprintln!("RISC-V toolchain is not installed.\nDownload and install toolchain from https://github.com/paritytech/rustc-rv32e-toolchain.");
eprintln!("{}", stderr);
} else {
eprintln!("{}", stderr);
}
bail!("Failed to build contracts");
}
/// Post-process the compiled wasm contracts.
fn post_process_riscv(input_path: &Path, output_path: &Path) -> Result<()> {
let mut config = polkavm_linker::Config::default();
config.set_strip(true);
let orig = fs::read(input_path).with_context(|| format!("Failed to read {:?}", input_path))?;
let linked = polkavm_linker::program_from_elf(config, orig.as_ref())
.map_err(|err| format_err!("Failed to link polkavm program: {}", err))?;
fs::write(output_path, linked.as_bytes()).map_err(Into::into)
}
/// Write the compiled contracts to the given output directory.
fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec<Entry>) -> Result<()> {
for entry in entries {
@@ -224,7 +282,13 @@ fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec<Entry>) -> Result
&build_dir.join("target/wasm32-unknown-unknown/release").join(&wasm_output),
&out_dir.join(&wasm_output),
)?;
fs::write(out_dir.join(&entry.hash), "")?;
post_process_riscv(
&build_dir.join("target/riscv32em-unknown-none-elf/release").join(entry.name()),
&out_dir.join(entry.out_riscv_filename()),
)?;
entry.update_cache(out_dir)?;
}
Ok(())
@@ -270,8 +334,9 @@ fn main() -> Result<()> {
&contracts_dir,
)?;
invoke_build(tmp_dir_path)?;
write_output(tmp_dir_path, &out_dir, entries)?;
invoke_wasm_build(tmp_dir_path)?;
invoke_riscv_build(tmp_dir_path)?;
write_output(tmp_dir_path, &out_dir, entries)?;
Ok(())
}
@@ -0,0 +1,18 @@
[package]
name = "contracts"
version = "0.1.0"
edition = "2021"
# Binary targets are injected dynamically by the build script.
[[bin]]
# local path are injected dynamically by the build script.
[dependencies]
uapi = { package = 'pallet-contracts-uapi', path = "", default-features = false }
common = { package = 'pallet-contracts-fixtures-common', path = "" }
polkavm-derive = '0.2.0'
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
@@ -0,0 +1,4 @@
SECTIONS {
.text : { KEEP(*(.text.polkavm_export)) }
}
@@ -23,9 +23,11 @@ extern crate common;
use uapi::{CallFlags, HostFn, HostFnImpl as api};
#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}
#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
let mut buffer = [0u8; 40];
let callee_input = 0..4;
@@ -6,6 +6,3 @@ authors.workspace = true
edition.workspace = true
license.workspace = true
description = "Common utilities for pallet-contracts-fixtures."
[lints]
workspace = true
@@ -20,7 +20,9 @@
extern crate common;
#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}
#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {}