Skip to main content

subsoil_derive/
lib.rs

1//! Procedural macros for declaring subsoil guest endpoints.
2//!
3//! The `#[subsoil::endpoint(N)]` attribute marks a function as
4//! endpoint `N` of a JAR chain Image. The macro emits three items
5//! into the guest crate:
6//!
7//! 1. The function definition itself, unchanged.
8//! 2. A per-endpoint trampoline `__subsoil_ep_N_trampoline` in
9//!    `.text` that calls the user function, then halts the VM via
10//!    a REPLY ecall (`li t0, 0; ecall`). The trampoline lives in
11//!    regular code; the kernel enters it at `endpoints[N].entry_pc`.
12//! 3. A `subsoil::EndpointDescriptor` static in the
13//!    `.subsoil.endpoints` ELF section whose `fn_ptr` points at
14//!    the trampoline (not the user fn). The transpiler reads the
15//!    section at link time and resolves each `fn_ptr` to a PVM PC.
16//!
17//! ```ignore
18//! #[subsoil::endpoint(0)]
19//! fn process(args_len: u64) -> u64 { ... }
20//! ```
21//!
22//! On host targets the macro emits only the function definition;
23//! the trampoline and descriptor are gated behind
24//! `cfg(all(target_env = "javm", target_os = "none"))`.
25
26use proc_macro::TokenStream;
27use quote::{format_ident, quote};
28use syn::{ItemFn, LitInt, parse_macro_input};
29
30/// Mark a function as endpoint `N` of a JAR chain Image.
31///
32/// `N` must be a `u8` literal (0..=255). Validates the function
33/// signature loosely; the transpiler does the strict check when it
34/// resolves the descriptor against the ELF symbol table.
35#[proc_macro_attribute]
36pub fn endpoint(attr: TokenStream, item: TokenStream) -> TokenStream {
37    let idx = parse_macro_input!(attr as LitInt);
38    let idx_value: u8 = match idx.base10_parse::<u8>() {
39        Ok(v) => v,
40        Err(e) => return e.to_compile_error().into(),
41    };
42
43    let func = parse_macro_input!(item as ItemFn);
44    let fn_name = &func.sig.ident;
45    let descriptor_name = format_ident!("__SUBSOIL_ENDPOINT_{}", idx_value);
46    let trampoline_ident = format_ident!("__subsoil_ep_{}_trampoline", idx_value);
47    let trampoline_label = format!("__subsoil_ep_{}_trampoline", idx_value);
48    let global_directive = format!(".global {trampoline_label}");
49    let trampoline_label_colon = format!("{trampoline_label}:");
50
51    let expanded = quote! {
52        #func
53
54        #[cfg(all(target_env = "javm", target_os = "none"))]
55        core::arch::global_asm!(
56            ".text",
57            #global_directive,
58            #trampoline_label_colon,
59            "call {user_fn}",
60            // REPLY to kernel via IPC slot 0 (HALT).
61            "li t0, 0",
62            "ecall",
63            "unimp", // trap if somehow resumed after REPLY
64            user_fn = sym #fn_name,
65        );
66
67        #[cfg(all(target_env = "javm", target_os = "none"))]
68        unsafe extern "Rust" {
69            safe fn #trampoline_ident(args_len: u64) -> u64;
70        }
71
72        #[cfg(all(target_env = "javm", target_os = "none"))]
73        #[doc(hidden)]
74        #[unsafe(link_section = ".subsoil.endpoints")]
75        #[used]
76        static #descriptor_name: ::subsoil::EndpointDescriptor =
77            ::subsoil::EndpointDescriptor {
78                fn_ptr: #trampoline_ident,
79                index: #idx_value,
80                arg_registers: 0,
81                arg_cnode_size: 0,
82                _pad: [0; 5],
83            };
84    };
85
86    expanded.into()
87}