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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! 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()],
		}
	}
}

pub enum ProfileType {
	Debug,
	Release,
	Other(&'static str),
}
impl ProfileType {
	fn as_str(&self) -> &'static str {
		match self {
			ProfileType::Debug => "debug",
			ProfileType::Release => "release",
			ProfileType::Other(s) => s,
		}
	}
	fn to_arg(&self) -> String {
		match self {
			ProfileType::Debug => "--debug".into(),
			ProfileType::Release => "--release".into(),
			ProfileType::Other(s) => format!("--profile={s}"),
		}
	}
}

/// 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, ProfileType::Release);
}

/// 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, ProfileType::Release);
}

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,
	profile: ProfileType,
) -> (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 module 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);
		}
	}
	let crate_name = get_crate_name(crate_dir);
	println!("📦Crate name: {}", crate_name);
	println!("🏷️ Build profile: {}", profile.as_str());

	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"])
		.arg(profile.to_arg())
		.arg("--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(profile.as_str()).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"));
	};
}