jam_pvm_builder/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Builder logic for creating PVM code blobs for execution on the JAM PVM instances (service code
//! and authorizer code).

#![allow(clippy::unwrap_used)]

use std::{fs, path::PathBuf, process::Command};

pub enum BlobType {
	Service,
	Authorizer,
}

impl BlobType {
	pub fn dispatch_table(&self) -> Vec<Vec<u8>> {
		match self {
			Self::Service =>
				vec![b"refine_ext".into(), b"accumulate_ext".into(), b"on_transfer_ext".into()],
			Self::Authorizer => vec![b"is_authorized_ext".into()],
		}
	}
}

/// Build the service crate in the parent path for the RISCV target, convert to PVM code and finish
/// by creating a `.pvm` blob file for inclusion into the `lib.rs` of the present crate using the
/// `pvm_binary` macro.
pub fn build_service(crate_dir: PathBuf, crate_name: &str) {
	let out_dir: PathBuf = std::env::var("OUT_DIR").expect("No OUT_DIR").into();
	let output_file = out_dir.join(format!("{}.jam", &crate_name));

	println!("cargo:rerun-if-changed={}", crate_dir.to_str().unwrap());
	build_pvm_blob(crate_dir, crate_name, BlobType::Service, &output_file, &out_dir);
}

/// Build the authorizer crate in the parent path for the RISCV target, convert to PVM code and
/// finish by creating a `.pvm` blob file for inclusion into the `lib.rs` of the present crate using
/// the `pvm_binary` macro.
pub fn build_authorizer(crate_dir: PathBuf, crate_name: &str) {
	let out_dir: PathBuf = std::env::var("OUT_DIR").expect("No OUT_DIR").into();
	let output_file = out_dir.join(format!("{}.jam", &crate_name));

	println!("cargo:rerun-if-changed={}", crate_dir.to_str().unwrap());
	build_pvm_blob(crate_dir, crate_name, BlobType::Authorizer, &output_file, &out_dir);
}

struct DeleteFileOnDrop(std::path::PathBuf);
impl Drop for DeleteFileOnDrop {
	fn drop(&mut self) {
		// Best effort.
		if fs::remove_file(&self.0).is_ok() {
			println!("Cleaned up `rust-toolchain.toml`");
		} else {
			println!("Warning: Unable to clean up `rust-toolchain.toml`");
		}
	}
}

/// Build the PVM crate in `crate_dir` called `crate_name` for the RISCV target, convert to PVM
/// code and finish by creating a `.pvm` blob file for inclusion into the `lib.rs` of the present
/// crate (called `this_crate_name`) using the `pvm_binary` macro. The crate is either a service
/// or an authorizer, defined by `blob_type`.
pub fn build_pvm_blob(
	crate_dir: PathBuf,
	crate_name: &str,
	blob_type: BlobType,
	output_file: &PathBuf,
	out_dir: &PathBuf,
) {
	let is_64_bit = cfg!(feature = "use-64-bit-pvm");
	let (target_name, target_json_path) = if is_64_bit {
		("riscv64emac-unknown-none-polkavm", polkavm_linker::target_json_64_path().unwrap())
	} else {
		("riscv32emac-unknown-none-polkavm", polkavm_linker::target_json_32_path().unwrap())
	};

	const RUST_TOOLCHAIN_TOML: &[u8; 68] = b"[toolchain]\nchannel = \"nightly-2024-11-01\"\ncomponents = [\"rust-src\"]";
	let toolchain_toml_path = crate_dir.join("rust-toolchain.toml");
	let toolchain_toml = if !fs::exists(&toolchain_toml_path).expect("Unable to inspect filesystem") {
		fs::write(&toolchain_toml_path, RUST_TOOLCHAIN_TOML).expect(&format!("Unable to write to {}", toolchain_toml_path.display()));
		println!("Wrote rust-toolchain.toml");
		Some(DeleteFileOnDrop(toolchain_toml_path.to_path_buf()))
	} else {
		None
	};

	println!("Building PVM blob for: {}", crate_name);
	println!("Target name: {}", target_name);

	println!("Building RISC-V ELF...");
	let mut child = Command::new("cargo")
		.current_dir(&crate_dir)
		.env_clear()
		.env("PATH", std::env::var("PATH").unwrap())
		.env("RUSTFLAGS", "-C panic=abort")
		.env("CARGO_TARGET_DIR", &out_dir)
		.args(["build", "-Z", "build-std=core,alloc", "--release", "--target"])
		.arg(target_json_path)
		.stdout(std::process::Stdio::inherit())
		.stderr(std::process::Stdio::inherit())
		.spawn()
		.expect("Failed to execute cargo process");
	let status = child.wait().expect("Failed to execute cargo process");

	if !status.success() {
		eprintln!("Failed to build RISC-V ELF due to cargo execution error");
		std::process::exit(1);
	}

	// Post processing
	println!("Converting RISC-V ELF to PVM blob...");
	let mut config = polkavm_linker::Config::default();
	config.set_strip(true);
	config.set_dispatch_table(blob_type.dispatch_table());
	let input_path = &out_dir.join(target_name).join("release").join(crate_name);
	let orig =
		fs::read(input_path).unwrap_or_else(|e| panic!("Failed to read {:?} :{:?}", input_path, e));
	let linked = polkavm_linker::program_from_elf(config, orig.as_ref())
		.expect("Failed to link polkavm program:");

	// Write out a full `.polkavm` blob for debugging/inspection.
	let output_path_polkavm = &out_dir.join(format!("{}.polkavm", &crate_name));
	fs::write(output_path_polkavm, &linked).expect("Error writing resulting binary");

	let parts = polkavm_linker::ProgramParts::from_bytes(linked.into())
		.expect("failed to deserialize linked PolkaVM program");

	let mut ro_data = parts.ro_data.to_vec();
	ro_data.resize(parts.ro_data_size as usize, 0);

	let blob_jam = jam_types::ProgramBlob {
		ro_data: ro_data.into(),
		rw_data: (&parts.rw_data[..]).into(),
		code_blob: (&parts.code_and_jump_table[..]).into(),
		rw_data_padding: (parts.rw_data_size as usize - parts.rw_data.len()) as u32,
		stack_size: parts.stack_size,
	};

	println!("Writing JAM-PVM blob to {}...", output_file.display());
	fs::write(output_file, blob_jam.to_vec().expect("error serializing the .jam blob"))
		.expect("error writing the .jam blob");

	drop(toolchain_toml);
}

#[macro_export]
macro_rules! pvm_binary {
	($name:literal) => {
		include_bytes!(concat!(env!("OUT_DIR"), "/", $name, ".jam"));
	};
}