nub_rt/lib.rs
1//! Guest-side runtime support for PVM2 programs.
2//!
3//! Provides compiler builtins (memset, memcpy, memcmp), a panic
4//! handler, and a default trap `_start` (so guests link without
5//! defining one themselves).
6//!
7//! Programs that need `alloc` get it from
8//! [`bump_allocator!`](crate::bump_allocator), which installs a
9//! resettable bump arena.
10//!
11//! Personality-free: a program built against this crate is a plain
12//! PVM2 program. Whether the engine running it has a capability system
13//! is not its concern.
14//!
15//! Guests declare their entry points via the
16//! `#[nub_rt::endpoint(N)]` attribute (from `nub-rt-macro`).
17//! Each annotation emits a per-endpoint trampoline that calls the
18//! user fn and halts; the kernel enters trampolines via
19//! `endpoints[N].entry_pc`. `_start` (PC=0) is never an intended
20//! entry — it traps via `unimp` if ever reached.
21//!
22//! All freestanding-only symbols are gated behind `cfg(target_os =
23//! "none")` — on host this crate is empty. Services force-link it via
24//! `use nub_rt as _;`.
25//!
26//! `target_os = "none"` is the freestanding-target test throughout;
27//! the guest target JSON sets it. The RISC-V-specific bits (panic
28//! handler, `_start`) additionally require `target_arch = "riscv64"`,
29//! because `x86_64-unknown-none` is also `target_os = "none"` and
30//! must never see this crate's `unimp`.
31
32#![no_std]
33
34pub mod alloc;
35
36pub use nub_rt_macro::endpoint;
37
38/// Descriptor written into the `.nub.endpoints` ELF section by the
39/// [`endpoint`] attribute macro. `nub-linker` reads this section at
40/// link time and turns each record into a
41/// `nub_program::ProgramBlob::endpoints` entry.
42///
43/// Layout is `#[repr(C)]` so the linker can decode the section as a
44/// flat array of fixed-size records. On RISC-V64 the function pointer
45/// occupies 8 bytes, followed by 8 bytes of metadata, for a total
46/// stride of 16 bytes.
47#[repr(C)]
48pub struct EndpointDescriptor {
49 /// RISC-V address of the endpoint function. The linker maps this
50 /// to a PVM PC via its instruction-mapping table.
51 pub fn_ptr: fn(u64) -> u64,
52 /// Endpoint index (key in the program's `endpoints` map).
53 pub index: u8,
54 /// Number of register args the caller supplies.
55 pub arg_registers: u8,
56 /// Opaque metadata byte, passed through to
57 /// `nub_program::Endpoint::arg_meta`. nub does not interpret it; a
58 /// personality may (JAVM reads it as the arg-cnode size).
59 pub arg_meta: u8,
60 /// Reserved for alignment / future expansion.
61 pub _pad: [u8; 5],
62}
63
64// -- Compiler builtins (freestanding targets only) ----------------------------
65
66#[cfg(target_os = "none")]
67mod builtins {
68 #[unsafe(no_mangle)]
69 pub unsafe extern "C" fn memset(dst: *mut u8, val: i32, n: usize) -> *mut u8 {
70 let mut i = 0;
71 while i < n {
72 unsafe { *dst.add(i) = val as u8 };
73 i += 1;
74 }
75 dst
76 }
77
78 #[unsafe(no_mangle)]
79 pub unsafe extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 {
80 let mut i = 0;
81 while i < n {
82 unsafe { *dst.add(i) = *src.add(i) };
83 i += 1;
84 }
85 dst
86 }
87
88 #[unsafe(no_mangle)]
89 pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 {
90 let mut i = 0;
91 while i < n {
92 let a = unsafe { *s1.add(i) };
93 let b = unsafe { *s2.add(i) };
94 if a != b {
95 return a as i32 - b as i32;
96 }
97 i += 1;
98 }
99 0
100 }
101}
102
103// -- Panic handler (freestanding targets only) --------------------------------
104
105// This crate is RISC-V-only by construction: both the panic handler
106// and `_start` below emit `unimp`. Fail loudly rather than silently
107// dropping a panic handler if it is ever built for another
108// freestanding target.
109#[cfg(all(target_os = "none", not(target_arch = "riscv64")))]
110compile_error!("nub_rt supports the freestanding riscv64 (PVM2) guest target only");
111
112#[cfg(all(target_os = "none", target_arch = "riscv64"))]
113#[panic_handler]
114fn panic(_: &core::panic::PanicInfo) -> ! {
115 unsafe {
116 core::arch::asm!("li a0, 0xDEAD", "unimp", options(noreturn));
117 }
118}
119
120// -- Default `_start` ---------------------------------------------------------
121//
122// The linker picks `_start` as the default ELF entry symbol. Guests
123// never expect PC=0 to be entered at runtime — the kernel always
124// enters via `endpoints[N].entry_pc` (a trampoline emitted by
125// `#[nub_rt::endpoint(N)]`). This default `_start` exists only to
126// satisfy the linker and traps loudly if ever reached.
127#[cfg(all(target_os = "none", target_arch = "riscv64"))]
128core::arch::global_asm!(
129 ".section .text._start, \"ax\", @progbits",
130 ".global _start",
131 "_start:",
132 " unimp",
133);