Skip to main content

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}