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
//! 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 given project for `riscv`.
///
/// A file `file_name` will be created in `OUT_DIR` that contains a constant named `RISCV_BLOB:
/// Option<&[u8]>` that represents the build riscv binary.
///
/// When setting the `SKIP_RUNTIME_BUILD` env variable at build time, the `RISCV_BLOB` will be set
/// to `None`.
pub fn build_jam_pvm_blob(path_name: &str, crate_name: &str, file_name: &str, blob_type: BlobType) {
	let out_dir: PathBuf = std::env::var("OUT_DIR").expect("No OUT_DIR").into();

	if std::env::var("SKIP_RUNTIME_BUILD").is_ok() {
		fs::write(out_dir.join(file_name), "pub const RISCV_BLOB: Option<&[u8]> = None;")
			.expect("Writes the output file.");
		return;
	}

	println!("cargo:rerun-if-env-changed=SKIP_RUNTIME_BUILD");

	let mut root_dir: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap().into();
	root_dir.pop();
	root_dir.pop();
	let service_dir = root_dir.join("services").join(path_name);
	let jam_common_dir = root_dir.join("crates").join("jam-pvm-common");

	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())
	};

	let build_res = Command::new("cargo")
		.current_dir(&service_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)
		.output()
		.expect("Failed to execute rustup process");

	if !build_res.status.success() {
		let stderr = String::from_utf8_lossy(&build_res.stderr);
		eprintln!("{}", stderr);
		std::process::exit(1);
	}

	// Post processing
	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_path_jam = &out_dir.join(format!("{}.jam", crate_name));
	fs::write(output_path_jam, blob_jam.to_vec().expect("error serializing the .jam blob"))
		.expect("error writing the .jam blob");

	fs::write(
		out_dir.join(file_name),
		format!(
			"pub const RISCV_BLOB: Option<&[u8]> = Some(include_bytes!(\"{}\"));",
			output_path_jam.display()
		),
	)
	.expect("Writes the output file.");

	println!("cargo:rerun-if-changed={}", service_dir.to_str().unwrap());
	println!("cargo:rerun-if-changed={}", jam_common_dir.to_str().unwrap());
}