subsoil/lib.rs
1//! Guest-side runtime support library for JAVM chain Images.
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//! Guests declare their entry points via the
8//! `#[subsoil::endpoint(N)]` attribute (from `subsoil-derive`).
9//! Each annotation emits a per-endpoint trampoline that calls the
10//! user fn and halts; the kernel enters trampolines via
11//! `endpoints[N].entry_pc`. `_start` (PC=0) is never an intended
12//! entry — it traps via `unimp` if ever reached.
13//!
14//! All freestanding-only symbols are gated behind `cfg(target_os =
15//! "none")` — on host this crate is empty. Services force-link it via
16//! `use subsoil as _;`.
17
18#![no_std]
19
20pub use subsoil_derive::endpoint;
21
22/// Descriptor written into the `.subsoil.endpoints` ELF section by
23/// the [`endpoint`] attribute macro. The JAVM transpiler reads this
24/// section at link time and uses each entry to populate the chain
25/// Image's `endpoints: BTreeMap<u8, EndpointDef>` field.
26///
27/// Layout is `#[repr(C)]` so the transpiler can decode the section
28/// as a flat array of fixed-size records. On RISC-V64 the function
29/// pointer occupies 8 bytes, followed by 8 bytes of metadata, for a
30/// total stride of 16 bytes.
31#[repr(C)]
32pub struct EndpointDescriptor {
33 /// RISC-V address of the endpoint function. The transpiler maps
34 /// this to a PVM PC via its instruction-mapping table.
35 pub fn_ptr: fn(u64) -> u64,
36 /// Endpoint index (key in the chain Image's `endpoints` map).
37 pub index: u8,
38 /// Caller-supplied register-arg count (per Image::EndpointDef).
39 pub arg_registers: u8,
40 /// Caller-supplied arg-cnode size (per Image::EndpointDef).
41 pub arg_cnode_size: u8,
42 /// Reserved for alignment / future expansion.
43 pub _pad: [u8; 5],
44}
45
46// -- Compiler builtins (freestanding targets only) ----------------------------
47
48#[cfg(target_os = "none")]
49mod builtins {
50 #[unsafe(no_mangle)]
51 pub unsafe extern "C" fn memset(dst: *mut u8, val: i32, n: usize) -> *mut u8 {
52 let mut i = 0;
53 while i < n {
54 unsafe { *dst.add(i) = val as u8 };
55 i += 1;
56 }
57 dst
58 }
59
60 #[unsafe(no_mangle)]
61 pub unsafe extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 {
62 let mut i = 0;
63 while i < n {
64 unsafe { *dst.add(i) = *src.add(i) };
65 i += 1;
66 }
67 dst
68 }
69
70 #[unsafe(no_mangle)]
71 pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 {
72 let mut i = 0;
73 while i < n {
74 let a = unsafe { *s1.add(i) };
75 let b = unsafe { *s2.add(i) };
76 if a != b {
77 return a as i32 - b as i32;
78 }
79 i += 1;
80 }
81 0
82 }
83}
84
85// -- Panic handler (freestanding targets only) --------------------------------
86
87#[cfg(target_os = "none")]
88#[panic_handler]
89fn panic(_: &core::panic::PanicInfo) -> ! {
90 unsafe {
91 core::arch::asm!("li a0, 0xDEAD", "unimp", options(noreturn));
92 }
93}
94
95// -- Default `_start` ---------------------------------------------------------
96//
97// The linker picks `_start` as the default ELF entry symbol. Guests
98// never expect PC=0 to be entered at runtime — the kernel always
99// enters via `endpoints[N].entry_pc` (a trampoline emitted by
100// `#[subsoil::endpoint(N)]`). This default `_start` exists only to
101// satisfy the linker and traps loudly if ever reached.
102#[cfg(target_env = "javm")]
103core::arch::global_asm!(
104 ".section .text._start, \"ax\", @progbits",
105 ".global _start",
106 "_start:",
107 " unimp",
108);