This PR adds initial support for building RISC-V runtimes targeting PolkaVM. - Setting the `SUBSTRATE_RUNTIME_TARGET=riscv` environment variable will now build a RISC-V runtime instead of a WASM runtime. - This only adds support for *building* runtimes; running them will need a PolkaVM-based executor, which I will add in a future PR. - Only building the minimal runtime is supported (building the Polkadot runtime doesn't work *yet* due to one of the dependencies). - The builder now sets a `substrate_runtime` cfg flag when building the runtimes, with the idea being that instead of doing `#[cfg(not(feature = "std"))]` or `#[cfg(target_arch = "wasm32")]` to detect that we're building a runtime you'll do `#[cfg(substrate_runtime)]`. (Switching the whole codebase to use this will be done in a future PR; I deliberately didn't do this here to keep this PR minimal and reviewable.) - Further renaming of things (e.g. types, environment variables and proc macro attributes having "wasm" in their name) to be target-agnostic will also be done in a future refactoring PR (while keeping backwards compatibility where it makes sense; I don't intend to break anyone's workflow or create unnecessary churn). - This PR also fixes two bugs in the `wasm-builder` crate: * The `RUSTC` environment variable is now removed when invoking the compiler. This prevents the toolchain version from being overridden when called from a `build.rs` script. * When parsing the `rustup toolchain list` output the `(default)` is now properly stripped and not treated as part of the version. - I've also added a minimal CI job that makes sure this doesn't break in the future. (cc @paritytech/ci) cc @athei ------ Also, just a fun little tidbit: quickly comparing the size of the built runtimes it seems that the PolkaVM runtime is slightly smaller than the WASM one. (`production` build, with the `names` section substracted from the WASM's size to keep things fair, since for the PolkaVM runtime we're currently stripping out everything) - `.wasm`: 625505 bytes - `.wasm` (after wasm-opt -O3): 563205 bytes - `.wasm` (after wasm-opt -Os): 562987 bytes - `.wasm` (after wasm-opt -Oz): 536852 bytes - `.polkavm`: ~~580338 bytes~~ 550476 bytes (after enabling extra target features; I'll add those in another PR once we have an executor working) --------- Co-authored-by: Bastian Köcher <git@kchr.de>
Substrate runtime interface
This crate provides types, traits and macros around runtime interfaces. A runtime interface is a fixed interface between a Substrate runtime and a Substrate node. For a native runtime the interface maps to a direct function call of the implementation. For a wasm runtime the interface maps to an external function call. These external functions are exported by the wasm executor and they map to the same implementation as the native calls.
Using a type in a runtime interface
Any type that should be used in a runtime interface as argument or return value needs to implement [RIType]. The
associated type
FFIType is
the type that is used in the FFI function to represent the actual type. For example [T] is represented by an u64.
The slice pointer and the length will be mapped to an u64 value. For more information see this
table. The FFI function
definition is used when calling from the wasm runtime into the node.
Traits are used to convert from a type to the corresponding
RIType::FFIType.
Depending on where and how a type should be used in a function signature, a combination of the following traits need to
be implemented:
- Pass as function argument: [
wasm::IntoFFIValue] and [host::FromFFIValue] - As function return value: [
wasm::FromFFIValue] and [host::IntoFFIValue] - Pass as mutable function argument: [
host::IntoPreallocatedFFIValue]
The traits are implemented for most of the common types like [T], Vec<T>, arrays and primitive types.
For custom types, we provide the
PassBy trait and strategies that
define how a type is passed between the wasm runtime and the node. Each strategy also provides a derive macro to
simplify the implementation.
Performance
To not waste any more performance when calling into the node, not all types are SCALE encoded when being passed as
arguments between the wasm runtime and the node. For most types that are raw bytes like Vec<u8>, [u8] or [u8; N]
we pass them directly, without SCALE encoding them in front of. The implementation of [RIType] each type provides more
information on how the data is passed.
Declaring a runtime interface
Declaring a runtime interface is similar to declaring a trait in Rust:
#[sp_runtime_interface::runtime_interface]
trait RuntimeInterface {
fn some_function(value: &[u8]) -> bool {
value.iter().all(|v| *v > 125)
}
}
For more information on declaring a runtime interface, see
#[runtime_interface].
FFI type and conversion
The following table documents how values of types are passed between the wasm and the host side and how they are converted into the corresponding type.
| Type | FFI type | Conversion |
|---|---|---|
u8 |
u8 |
Identity |
u16 |
u16 |
Identity |
u32 |
u32 |
Identity |
u64 |
u64 |
Identity |
i128 |
u32 |
v.as_ptr() (pointer to a 16 byte array) |
i8 |
i8 |
Identity |
i16 |
i16 |
Identity |
i32 |
i32 |
Identity |
i64 |
i64 |
Identity |
u128 |
u32 |
v.as_ptr() (pointer to a 16 byte array) |
bool |
u8 |
if v { 1 } else { 0 } |
&str |
u64 |
v.len() 32bit << 32 | v.as_ptr() 32bit |
&[u8] |
u64 |
v.len() 32bit << 32 | v.as_ptr() 32bit |
Vec<u8> |
u64 |
v.len() 32bit << 32 | v.as_ptr() 32bit |
Vec<T> where T: Encode |
u64 |
let e = v.encode();e.len() 32bit << 32 | e.as_ptr() 32bit |
&[T] where T: Encode |
u64 |
let e = v.encode();e.len() 32bit << 32 | e.as_ptr() 32bit |
[u8; N] |
u32 |
v.as_ptr() |
*const T |
u32 |
Identity |
Option<T> |
u64 |
let e = v.encode();e.len() 32bit << 32 | e.as_ptr() 32bit |
T where T: PassBy<PassBy=Inner> |
Depends on inner | Depends on inner |
T where T: PassBy<PassBy=Codec> |
u64 |
v.len() 32bit << 32 | v.as_ptr() 32bit |
Identity means that the value is converted directly into the corresponding FFI type.
License: Apache-2.0