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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! Builder logic for creating PVM code blobs for execution on the JAM PVM instances (service code
//! and authorizer code).

#![allow(clippy::unwrap_used)]

const NIGHTLY: &str = "nightly-2024-11-01";

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

pub enum BlobType {
	Service,
	Authorizer,
}
impl Display for BlobType {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::Service => write!(f, "Service"),
			Self::Authorizer => write!(f, "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 `crate_dir` for the RISCV target, convert to PVM code and finish
/// by creating a `<crate_name>.pvm` blob file.
///
/// If used in `build.rs`, then this may be included in the relevant crate by using the `pvm_binary`
/// macro.
pub fn build_service(crate_dir: &Path) {
	let out_dir: PathBuf = std::env::var("OUT_DIR").expect("No OUT_DIR").into();
	println!("cargo:rerun-if-changed={}", crate_dir.to_str().unwrap());
	build_pvm_blob(crate_dir, BlobType::Service, &out_dir, true);
}

/// Build the authorizer crate in `crate_dir` for the RISCV target, convert to PVM code and finish
/// by creating a `<crate_name>.pvm` blob file.
///
/// If used in `build.rs`, then this may be included in the relevant crate by using the `pvm_binary`
/// macro.
pub fn build_authorizer(crate_dir: &Path) {
	let out_dir: PathBuf = std::env::var("OUT_DIR").expect("No OUT_DIR").into();
	println!("cargo:rerun-if-changed={}", crate_dir.to_str().unwrap());
	build_pvm_blob(crate_dir, BlobType::Authorizer, &out_dir, true);
}

fn get_crate_name(crate_dir: &Path) -> String {
	let manifest = Command::new("cargo")
		.current_dir(crate_dir)
		.arg("read-manifest")
		.output()
		.unwrap()
		.stdout;
	serde_json::from_slice::<serde_json::Value>(&manifest)
		.unwrap()
		.get("name")
		.unwrap()
		.as_str()
		.unwrap()
		.to_string()
}

/// 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 of path `output_file`. `out_dir` is used to store
/// any intermediate build files.
pub fn build_pvm_blob(
	crate_dir: &Path,
	blob_type: BlobType,
	out_dir: &Path,
	install_rustc: bool,
) -> (String, 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())
	};

	println!("PVM binary type: {}", blob_type);
	println!("Target name: {}", target_name);

	let output = Command::new("rustup")
		.args(["component", "list", "--toolchain", NIGHTLY, "--installed"])
		.output()
		.unwrap_or_else(|_| {
			panic!(
				"Failed to execute `rustup component list --toolchain {NIGHTLY} --installed`.\n\
		Please install `rustup` to continue.",
			)
		});
	if !output.status.success() ||
		!output.stdout.split(|x| *x == b'\n').any(|x| x[..] == b"rust-src"[..])
	{
		if install_rustc {
			println!("Installing rustc dependencies...");
			let mut child = Command::new("rustup")
				.args(["toolchain", "install", NIGHTLY, "-c", "rust-src"])
				.stdout(std::process::Stdio::inherit())
				.stderr(std::process::Stdio::inherit())
				.spawn()
				.unwrap_or_else(|_| {
					panic!(
						"Failed to execute `rustup toolchain install {NIGHTLY} -c rust-src`.\n\
				Please install `rustup` to continue."
					)
				});
			if !child.wait().expect("Failed to execute rustup process").success() {
				panic!("Failed to install `rust-src` component of {}.", NIGHTLY);
			}
		} else {
			panic!("`rust-src` component of {} is required to build the PVM binary.", NIGHTLY);
		}
	}

	println!("Found good cargo environment. 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)
		.arg(format!("+{}", NIGHTLY))
		.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);
	}

	let crate_name = get_crate_name(crate_dir);
	println!("Building PVM binary for: {}", crate_name);

	// 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,
	};

	let output_file = out_dir.join(format!("{}.jam", &crate_name));
	fs::write(&output_file, blob_jam.to_vec().expect("error serializing the .jam blob"))
		.expect("error writing the .jam blob");

	(crate_name, output_file)
}

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