Skip to main content

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