Skip to content

Commit

Permalink
Initial support for building RISC-V runtimes targeting PolkaVM (parit…
Browse files Browse the repository at this point in the history
…ytech#3179)

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>
  • Loading branch information
koute and bkchr authored Feb 3, 2024
1 parent a262da4 commit 2b92108
Show file tree
Hide file tree
Showing 14 changed files with 506 additions and 165 deletions.
6 changes: 5 additions & 1 deletion substrate/primitives/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ sp-api-proc-macro = { path = "proc-macro", default-features = false }
sp-core = { path = "../core", default-features = false }
sp-std = { path = "../std", default-features = false }
sp-runtime = { path = "../runtime", default-features = false }
sp-runtime-interface = { path = "../runtime-interface", default-features = false }
sp-externalities = { path = "../externalities", default-features = false, optional = true }
sp-version = { path = "../version", default-features = false }
sp-state-machine = { path = "../state-machine", default-features = false, optional = true }
sp-trie = { path = "../trie", default-features = false, optional = true }
hash-db = { version = "0.16.0", optional = true }
thiserror = { version = "1.0.48", optional = true }
scale-info = { version = "2.10.0", default-features = false, features = ["derive"] }
scale-info = { version = "2.10.0", default-features = false, features = [
"derive",
] }
sp-metadata-ir = { path = "../metadata-ir", default-features = false, optional = true }
log = { version = "0.4.17", default-features = false }

Expand All @@ -46,6 +49,7 @@ std = [
"sp-externalities",
"sp-externalities?/std",
"sp-metadata-ir?/std",
"sp-runtime-interface/std",
"sp-runtime/std",
"sp-state-machine/std",
"sp-std/std",
Expand Down
3 changes: 2 additions & 1 deletion substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result<TokenStream> {
#c::std_disabled! {
#( #attrs )*
#[no_mangle]
pub unsafe fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 {
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #c::__private::polkavm_export(abi = #c::__private::polkavm_abi))]
pub unsafe extern fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 {
let mut #input = if input_len == 0 {
&[0u8; 0]
} else {
Expand Down
3 changes: 3 additions & 0 deletions substrate/primitives/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ pub mod __private {
};
pub use sp_std::{mem, slice, vec};
pub use sp_version::{create_apis_vec, ApiId, ApisVec, RuntimeVersion};

#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))]
pub use sp_runtime_interface::polkavm::{polkavm_abi, polkavm_export};
}

#[cfg(feature = "std")]
Expand Down
43 changes: 31 additions & 12 deletions substrate/primitives/io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1737,20 +1737,20 @@ mod tracing_setup {

pub use tracing_setup::init_tracing;

/// Allocator used by Substrate when executing the Wasm runtime.
#[cfg(all(target_arch = "wasm32", not(feature = "std")))]
struct WasmAllocator;
/// Allocator used by Substrate from within the runtime.
#[cfg(substrate_runtime)]
struct RuntimeAllocator;

#[cfg(all(target_arch = "wasm32", not(feature = "disable_allocator"), not(feature = "std")))]
#[cfg(all(not(feature = "disable_allocator"), substrate_runtime))]
#[global_allocator]
static ALLOCATOR: WasmAllocator = WasmAllocator;
static ALLOCATOR: RuntimeAllocator = RuntimeAllocator;

#[cfg(all(target_arch = "wasm32", not(feature = "std")))]
#[cfg(substrate_runtime)]
mod allocator_impl {
use super::*;
use core::alloc::{GlobalAlloc, Layout};

unsafe impl GlobalAlloc for WasmAllocator {
unsafe impl GlobalAlloc for RuntimeAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
allocator::malloc(layout.size() as u32)
}
Expand All @@ -1761,8 +1761,27 @@ mod allocator_impl {
}
}

/// A default panic handler for WASM environment.
#[cfg(all(not(feature = "disable_panic_handler"), not(feature = "std")))]
/// Crashes the execution of the program.
///
/// Equivalent to the WASM `unreachable` instruction, RISC-V `unimp` instruction,
/// or just the `unreachable!()` macro everywhere else.
pub fn unreachable() -> ! {
#[cfg(target_family = "wasm")]
{
core::arch::wasm32::unreachable();
}

#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
unsafe {
core::arch::asm!("unimp", options(noreturn));
}

#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_family = "wasm")))]
unreachable!();
}

/// A default panic handler for the runtime environment.
#[cfg(all(not(feature = "disable_panic_handler"), substrate_runtime))]
#[panic_handler]
#[no_mangle]
pub fn panic(info: &core::panic::PanicInfo) -> ! {
Expand All @@ -1774,11 +1793,11 @@ pub fn panic(info: &core::panic::PanicInfo) -> ! {
#[cfg(not(feature = "improved_panic_error_reporting"))]
{
logging::log(LogLevel::Error, "runtime", message.as_bytes());
core::arch::wasm32::unreachable();
unreachable();
}
}

/// A default OOM handler for WASM environment.
/// A default OOM handler for the runtime environment.
#[cfg(all(not(feature = "disable_oom"), enable_alloc_error_handler))]
#[alloc_error_handler]
pub fn oom(_: core::alloc::Layout) -> ! {
Expand All @@ -1789,7 +1808,7 @@ pub fn oom(_: core::alloc::Layout) -> ! {
#[cfg(not(feature = "improved_panic_error_reporting"))]
{
logging::log(LogLevel::Error, "runtime", b"Runtime memory exhausted. Aborting");
core::arch::wasm32::unreachable();
unreachable();
}
}

Expand Down
3 changes: 3 additions & 0 deletions substrate/primitives/runtime-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ primitive-types = { version = "0.12.0", default-features = false }
sp-storage = { path = "../storage", default-features = false }
impl-trait-for-tuples = "0.2.2"

[target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies]
polkavm-derive = { workspace = true }

[dev-dependencies]
sp-runtime-interface-test-wasm = { path = "test-wasm" }
sp-state-machine = { path = "../state-machine" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ fn function_no_std_impl(
};
let maybe_unreachable = if method.should_trap_on_return() {
quote! {
; core::arch::wasm32::unreachable();
;
#[cfg(target_family = "wasm")]
{ core::arch::wasm32::unreachable(); }

#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
unsafe { core::arch::asm!("unimp", options(noreturn)); }
}
} else {
quote! {}
Expand All @@ -118,7 +123,7 @@ fn function_no_std_impl(
let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));

let cfg_wasm_only = if is_wasm_only {
quote! { #[cfg(target_arch = "wasm32")] }
quote! { #[cfg(substrate_runtime)] }
} else {
quote! {}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ fn generate_extern_host_function(
#(#cfg_attrs)*
#[doc = #doc_string]
pub fn #function ( #( #args ),* ) #return_value {
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #crate_::polkavm::polkavm_import(abi = #crate_::polkavm::polkavm_abi))]
extern "C" {
/// The extern function.
pub fn #ext_function (
#( #arg_names: <#arg_types as #crate_::RIType>::FFIType ),*
) #ffi_return_value;
Expand Down
3 changes: 3 additions & 0 deletions substrate/primitives/runtime-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ pub use sp_externalities::{
#[doc(hidden)]
pub use codec;

#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))]
pub mod polkavm;

#[cfg(feature = "std")]
pub mod host;
pub(crate) mod impls;
Expand Down
30 changes: 30 additions & 0 deletions substrate/primitives/runtime-interface/src/polkavm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pub use polkavm_derive::{polkavm_export, polkavm_import};

#[polkavm_derive::polkavm_define_abi(allow_extra_input_registers)]
pub mod polkavm_abi {}

impl self::polkavm_abi::FromHost for *mut u8 {
type Regs = (u32,);

#[inline]
fn from_host((value,): Self::Regs) -> Self {
value as *mut u8
}
}
1 change: 1 addition & 0 deletions substrate/utils/wasm-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ sp-maybe-compressed-blob = { path = "../../primitives/maybe-compressed-blob" }
filetime = "0.2.16"
wasm-opt = "0.116"
parity-wasm = "0.45"
polkavm-linker = { workspace = true }
33 changes: 29 additions & 4 deletions substrate/utils/wasm-builder/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use std::{
process,
};

use crate::RuntimeTarget;

/// Returns the manifest dir from the `CARGO_MANIFEST_DIR` env.
fn get_manifest_dir() -> PathBuf {
env::var("CARGO_MANIFEST_DIR")
Expand Down Expand Up @@ -49,6 +51,8 @@ impl WasmBuilderSelectProject {
project_cargo_toml: get_manifest_dir().join("Cargo.toml"),
features_to_enable: Vec::new(),
disable_runtime_version_section_check: false,
export_heap_base: false,
import_memory: false,
}
}

Expand All @@ -65,6 +69,8 @@ impl WasmBuilderSelectProject {
project_cargo_toml: path,
features_to_enable: Vec::new(),
disable_runtime_version_section_check: false,
export_heap_base: false,
import_memory: false,
})
} else {
Err("Project path must point to the `Cargo.toml` of the project")
Expand Down Expand Up @@ -97,6 +103,11 @@ pub struct WasmBuilder {
features_to_enable: Vec<String>,
/// Should the builder not check that the `runtime_version` section exists in the wasm binary?
disable_runtime_version_section_check: bool,

/// Whether `__heap_base` should be exported (WASM-only).
export_heap_base: bool,
/// Whether `--import-memory` should be added to the link args (WASM-only).
import_memory: bool,
}

impl WasmBuilder {
Expand All @@ -109,7 +120,7 @@ impl WasmBuilder {
///
/// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`.
pub fn export_heap_base(mut self) -> Self {
self.rust_flags.push("-Clink-arg=--export=__heap_base".into());
self.export_heap_base = true;
self
}

Expand All @@ -127,7 +138,7 @@ impl WasmBuilder {
///
/// This adds `-C link-arg=--import-memory` to `RUST_FLAGS`.
pub fn import_memory(mut self) -> Self {
self.rust_flags.push("-C link-arg=--import-memory".into());
self.import_memory = true;
self
}

Expand Down Expand Up @@ -159,7 +170,18 @@ impl WasmBuilder {
}

/// Build the WASM binary.
pub fn build(self) {
pub fn build(mut self) {
let target = crate::runtime_target();
if target == RuntimeTarget::Wasm {
if self.export_heap_base {
self.rust_flags.push("-Clink-arg=--export=__heap_base".into());
}

if self.import_memory {
self.rust_flags.push("-C link-arg=--import-memory".into());
}
}

let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!"));
let file_path =
out_dir.join(self.file_name.clone().unwrap_or_else(|| "wasm_binary.rs".into()));
Expand All @@ -175,6 +197,7 @@ impl WasmBuilder {
}

build_project(
target,
file_path,
self.project_cargo_toml,
self.rust_flags.into_iter().map(|f| format!("{} ", f)).collect(),
Expand Down Expand Up @@ -248,14 +271,15 @@ fn generate_rerun_if_changed_instructions() {
/// `check_for_runtime_version_section` - Should the wasm binary be checked for the
/// `runtime_version` section?
fn build_project(
target: RuntimeTarget,
file_name: PathBuf,
project_cargo_toml: PathBuf,
default_rustflags: String,
features_to_enable: Vec<String>,
wasm_binary_name: Option<String>,
check_for_runtime_version_section: bool,
) {
let cargo_cmd = match crate::prerequisites::check() {
let cargo_cmd = match crate::prerequisites::check(target) {
Ok(cmd) => cmd,
Err(err_msg) => {
eprintln!("{}", err_msg);
Expand All @@ -264,6 +288,7 @@ fn build_project(
};

let (wasm_binary, bloaty) = crate::wasm_project::create_and_compile(
target,
&project_cargo_toml,
&default_rustflags,
cargo_cmd,
Expand Down
Loading

0 comments on commit 2b92108

Please sign in to comment.