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}