1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
//! This module defines the low-level syscall API.
//!
//! # Wasm Syscall ABI
//!
//! Here we specify how the syscalls specified in this module map to Wasm. For more information on
//! the FVM syscall ABI, read the [syscall ABI spec][abi].
//!
//! By return type, there are three "kinds" of syscalls:
//!
//! 1. Syscalls that do not return (return `!`).
//! 2. Syscalls that do not return a value (return `Result<()>`).
//! 3. Syscalls that return a value (return `Result<T>`).
//!
//! Syscalls may also "return" values by writing them to out-pointers passed into the syscall by the
//! caller, but this is documented on a syscall-by-syscall basis.
//!
//! [abi]: https://github.com/filecoin-project/fvm-specs/blob/main/08-syscalls.md
//!
//! ## Kind 1: Divergent
//!
//! Syscalls that return `!` (e.g. [`vm::abort`]) have the signature:
//!
//! ```wat
//! (func $name (param ...) ... (result i32))
//! ```
//!
//! `result` is unused because the syscall never returns.
//!
//! ## Kind 2: Empty Return
//!
//! Syscalls that return `Result<()>` (nothing) have the signature:
//!
//! ```wat
//! (func $name (param ...) ... (result i32))
//! ```
//!
//! Here, `result` is an [`ErrorNumber`] or `0` on success.
//!
//! ## Kind 3: Non-Empty Return
//!
//! Syscalls that return `Result<T>` where `T` is a non-empty sized value have the signature:
//!
//! ```wat
//! (func $name (param $ret_ptr) (param ...) ... (result i32))
//! ```
//!
//! Here:
//!
//! - `result` is an [`ErrorNumber`] or `0` on success.
//! - `ret_ptr` is the offset (specified by the _caller_) where the FVM will write the return value
//! if, and only if the result is `0` (success).
#[doc(inline)]
pub use fvm_shared::error::ErrorNumber;
#[doc(inline)]
pub use fvm_shared::sys::TokenAmount;
pub mod actor;
pub mod crypto;
pub mod debug;
pub mod event;
pub mod gas;
pub mod ipld;
pub mod network;
pub mod rand;
pub mod send;
pub mod sself;
pub mod vm;
/// Generate a set of FVM syscall shims.
///
/// ```ignore
/// fvm_sdk::sys::fvm_syscalls! {
/// module = "my_wasm_module";
///
/// /// This method will translate to a syscall with the signature:
/// ///
/// /// fn(arg: u64) -> u32;
/// ///
/// /// Where the returned u32 is the status code.
/// pub fn returns_nothing(arg: u64) -> Result<()>;
///
/// /// This method will translate to a syscall with the signature:
/// ///
/// /// fn(out: u32, arg: u32) -> u32;
/// ///
/// /// Where `out` is a pointer to where the return value will be written and the returned u32
/// /// is the status code.
/// pub fn returns_value(arg: u64) -> Result<u64>;
///
/// /// This method will translate to a syscall with the signature:
/// ///
/// /// fn(arg: u32) -> u32;
/// ///
/// /// But it will panic if this function returns.
/// pub fn aborts(arg: u32) -> !;
/// }
/// ```
#[macro_export]
macro_rules! fvm_syscalls {
// Returns no values.
(module = $module:literal; $(#[$attrs:meta])* $v:vis fn $name:ident($($args:ident : $args_ty:ty),*$(,)?) -> Result<()>; $($rest:tt)*) => {
$(#[$attrs])*
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::too_many_arguments)]
$v unsafe fn $name($($args:$args_ty),*) -> Result<(), $crate::sys::ErrorNumber> {
#[link(wasm_import_module = $module)]
extern "C" {
#[link_name = stringify!($name)]
fn syscall($($args:$args_ty),*) -> u32;
}
let code = syscall($($args),*);
if code == 0 {
Ok(())
} else {
Err(num_traits::FromPrimitive::from_u32(code)
.expect("syscall returned unrecognized exit code"))
}
}
$crate::sys::fvm_syscalls! {
module = $module; $($rest)*
}
};
// Returns a value.
(module = $module:literal; $(#[$attrs:meta])* $v:vis fn $name:ident($($args:ident : $args_ty:ty),*$(,)?) -> Result<$ret:ty>; $($rest:tt)*) => {
$(#[$attrs])*
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::too_many_arguments)]
$v unsafe fn $name($($args:$args_ty),*) -> Result<$ret, $crate::sys::ErrorNumber> {
#[link(wasm_import_module = $module)]
extern "C" {
#[link_name = stringify!($name)]
fn syscall(ret: *mut $ret $(, $args : $args_ty)*) -> u32;
}
let mut ret = std::mem::MaybeUninit::<$ret>::uninit();
let code = syscall(ret.as_mut_ptr(), $($args),*);
if code == 0 {
Ok(ret.assume_init())
} else {
Err(num_traits::FromPrimitive::from_u32(code)
.expect("syscall returned unrecognized exit code"))
}
}
$crate::sys::fvm_syscalls! {
module = $module;
$($rest)*
}
};
// Does not return.
(module = $module:literal; $(#[$attrs:meta])* $v:vis fn $name:ident($($args:ident : $args_ty:ty),*$(,)?) -> !; $($rest:tt)*) => {
$(#[$attrs])*
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::too_many_arguments)]
$v unsafe fn $name($($args:$args_ty),*) -> ! {
#[link(wasm_import_module = $module)]
extern "C" {
#[link_name = stringify!($name)]
fn syscall($($args : $args_ty),*) -> u32;
}
syscall($($args),*);
// This should be unreachable unless the syscall has a bug. We abort instead of panicing
// to help the compiler optimize. It has no way of _proving_ that the syscall doesn't
// return, so this gives it a way to prove that even if the syscall does return, this
// function won't.
std::process::abort()
}
$crate::sys::fvm_syscalls! {
module = $module;
$($rest)*
}
};
// Base case.
(module = $module:literal;) => {};
}
pub use fvm_syscalls;