nub_build/pvm2.rs
1//! build.rs helper: cross-compile a guest crate for the PVM2 target and
2//! link it into a runnable program.
3//!
4//! Two entry points, deliberately split:
5//!
6//! - [`build_elf`] stops at the RV64EMC ELF. A personality that wants
7//! its own container (JAVM wraps the blob in a cap `Image`) calls
8//! this and links the ELF itself.
9//! - [`build`] goes all the way to a `.nubp` [`ProgramBlob`] file.
10//!
11//! The target is `riscv64emc-pvm2`: RV64 embedded (16 registers) with
12//! compressed, M, and the Zbb/Zba/Zbs/Zicond/Zicclsm extensions, no
13//! atomics, `panic=abort`, PIE. It is a custom target JSON, so the
14//! guest build needs `-Zbuild-std` and therefore `RUSTC_BOOTSTRAP=1` —
15//! which `build_crate::GuestBuild` sets.
16//!
17//! [`ProgramBlob`]: nub_program::ProgramBlob
18
19use std::path::PathBuf;
20
21use build_crate::{BuildKind, GuestBuild};
22
23/// PVM2 target spec: RV64EMC + Zbb/Zba/Zbs/Zicond/Zicclsm.
24const TARGET_JSON: &str = include_str!("riscv64emc-pvm2.json");
25const TARGET_NAME: &str = "riscv64emc-pvm2";
26/// File extension for a serialized [`nub_program::ProgramBlob`].
27const BLOB_EXT: &str = "nubp";
28
29/// Emit `cargo:rerun-if-changed` for the linker and program-format
30/// sources.
31///
32/// Cargo does re-run a build script when its own executable changes,
33/// which covers these transitively — but the coupling is load-bearing
34/// enough (a linker change rewrites every blob, and blobs feed pinned
35/// gas vectors) to state explicitly.
36fn watch_linker_sources() {
37 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
38 let nub_dir = PathBuf::from(&manifest_dir)
39 .parent()
40 .expect("nub-build must live inside nub/")
41 .to_path_buf();
42 build_crate::emit_rerun_for_dir(&nub_dir.join("nub-linker/src"));
43 build_crate::emit_rerun_for_dir(&nub_dir.join("nub-program/src"));
44}
45
46/// Cross-compile `bin_name` in `manifest_dir` for `riscv64emc-pvm2` and
47/// return the path to the resulting ELF.
48///
49/// `manifest_dir` is relative to the calling `build.rs`'s
50/// `CARGO_MANIFEST_DIR`.
51pub fn build_elf(manifest_dir: &str, bin_name: &str) -> PathBuf {
52 watch_linker_sources();
53
54 let resolved = build_crate::resolve_manifest_dir(manifest_dir);
55 let target_json_path = build_crate::write_target_json("riscv64emc-pvm2.json", TARGET_JSON);
56
57 let guest = GuestBuild {
58 manifest_dir: resolved,
59 target_json_path,
60 target_dir_name: TARGET_NAME.to_string(),
61 build_kind: BuildKind::Bin(bin_name.to_string()),
62 // Guests are small and hot; a raised inline threshold buys real
63 // PVM2 instruction-count reductions. Changing it changes every
64 // blob, hence every pinned gas vector.
65 extra_rustflags: vec!["-Cllvm-args=--inline-threshold=265".to_string()],
66 extra_rustc_args: vec![],
67 env_overrides: vec![
68 (
69 "CARGO_PROFILE_RELEASE_OPT_LEVEL".to_string(),
70 "3".to_string(),
71 ),
72 ("CARGO_PROFILE_RELEASE_LTO".to_string(), "true".to_string()),
73 (
74 "CARGO_PROFILE_RELEASE_CODEGEN_UNITS".to_string(),
75 "1".to_string(),
76 ),
77 ],
78 rustc_bootstrap: true,
79 };
80
81 guest.build()
82}
83
84/// Cross-compile and link `bin_name` into `$OUT_DIR/<bin_name>.nubp`,
85/// returning the blob path.
86///
87/// Honours `SKIP_GUEST_BUILD` by writing an empty placeholder — CI uses
88/// it for jobs that only need the workspace to compile.
89pub fn build(manifest_dir: &str, bin_name: &str) -> PathBuf {
90 let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
91 let blob_path = PathBuf::from(&out_dir).join(format!("{bin_name}.{BLOB_EXT}"));
92
93 if std::env::var("SKIP_GUEST_BUILD").is_ok() {
94 if !blob_path.exists() {
95 std::fs::write(&blob_path, b"").ok();
96 }
97 return blob_path;
98 }
99
100 let elf_path = build_elf(manifest_dir, bin_name);
101 let elf_data = std::fs::read(&elf_path).expect("failed to read guest ELF");
102 let program = nub_linker::link_elf(&elf_data).expect("failed to link guest ELF");
103 std::fs::write(&blob_path, program.to_bytes()).expect("failed to write program blob");
104 blob_path
105}