Skip to content

Commit a54cb5f

Browse files
[feat] Add the functionality for custom RISC-V intrinsics, support hints in RISC-V (#726)
* Add custom insn macros * Wip * Wip * Add "read-vec" function * Add the new HintInputRv32 phantom instruction, add the corresponding intrinsic, support transpiling it * Add a test * Optimize reading a vector * Address comments, add a test, fix transpiler * Fix rust-v hint program * chore: mv exit,panic to process * feat: make `read_vec` more optimal * fix: () vs ! * fix import * fix: ptr was not ptr_start --------- Co-authored-by: Jonathan Wang <[email protected]>
1 parent 97953e8 commit a54cb5f

File tree

17 files changed

+234
-189
lines changed

17 files changed

+234
-189
lines changed

toolchain/build/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,11 @@ pub fn build_guest_package<P>(
257257
"--target-dir",
258258
target_dir.as_ref().to_str().unwrap(),
259259
]);
260-
tty_println(&format!("cargo command: {:?}", cmd));
261260

262261
if !is_debug() {
263262
cmd.args(["--release"]);
264263
}
264+
tty_println(&format!("cargo command: {:?}", cmd));
265265

266266
let mut child = cmd
267267
.stderr(Stdio::piped())

toolchain/instructions/src/phantom.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub enum PhantomInstruction {
1313
PrintF,
1414
/// Prepare the next input vector for hinting.
1515
HintInput,
16+
/// Prepare the next input vector for hinting, but prepend it with a 4-byte decomposition of its length instead of one field element.
17+
HintInputRv32,
1618
/// Prepare the little-endian bit decomposition of a variable for hinting.
1719
HintBits,
1820
/// Start tracing

toolchain/riscv/axvm/src/env/mod.rs

Lines changed: 0 additions & 83 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// use crate::custom_insn_i;
2+
3+
use axvm_platform::{custom_insn_i, intrinsics::CUSTOM_0};
4+
5+
/// Store the next 4 bytes from the hint stream to [[rd] + imm]_2.
6+
#[macro_export]
7+
macro_rules! hint_store_u32 {
8+
($x:ident, $imm:expr) => {
9+
axvm_platform::custom_insn_i!(axvm_platform::intrinsics::CUSTOM_0, 0b001, $x, "x0", $imm)
10+
};
11+
}
12+
13+
/// Reset the hint stream with the next hint.
14+
#[inline(always)]
15+
pub fn hint_input() {
16+
custom_insn_i!(CUSTOM_0, 0b011, "x0", "x0", 0);
17+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Functions that call custom instructions that use axVM intrinsic instructions.
22
33
mod hash;
4+
/// Library functions for user input/output.
5+
pub mod io;
46

57
pub use hash::*;
8+
pub use io::*;

toolchain/riscv/axvm/src/io.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//! User IO functions
2+
3+
use alloc::vec::Vec;
4+
use core::alloc::Layout;
5+
6+
use crate::{hint_store_u32, intrinsics::hint_input};
7+
8+
/// Read `size: u32` and then `size` bytes from the hint stream into a vector.
9+
pub fn read_vec() -> Vec<u8> {
10+
hint_input();
11+
read_vec_by_len(read_u32() as usize)
12+
}
13+
14+
/// Read the next 4 bytes from the hint stream into a register.
15+
/// Because [hint_store_u32] stores a word to memory, this function first reads to memory and then
16+
/// loads from memory to register.
17+
#[inline(always)]
18+
#[allow(asm_sub_register)]
19+
pub fn read_u32() -> u32 {
20+
let ptr = unsafe { alloc::alloc::alloc(Layout::from_size_align(4, 4).unwrap()) };
21+
let addr = ptr as u32;
22+
hint_store_u32!(addr, 0);
23+
let result: u32;
24+
unsafe {
25+
core::arch::asm!("lw {rd}, ({rs1})", rd = out(reg) result, rs1 = in(reg) addr);
26+
}
27+
result
28+
}
29+
30+
/// Read the next `len` bytes from the hint stream into a vector.
31+
fn read_vec_by_len(len: usize) -> Vec<u8> {
32+
let num_words = (len + 3) / 4;
33+
let capacity = num_words * 4;
34+
// Allocate a buffer of the required length that is 4 byte aligned
35+
// Note: this expect message doesn't matter until our panic handler actually cares about it
36+
let layout = Layout::from_size_align(capacity, 4).expect("vec is too large");
37+
// SAFETY: We populate a `Vec<u8>` by hintstore-ing `num_words` 4 byte words. We set the length to `len` and don't care about the extra `capacity - len` bytes stored.
38+
let ptr_start = unsafe { alloc::alloc::alloc(layout) };
39+
let mut ptr = ptr_start;
40+
41+
// Note: if len % 4 != 0, this will discard some last bytes
42+
for _ in 0..num_words {
43+
hint_store_u32!(ptr, 0);
44+
ptr = unsafe { ptr.add(4) };
45+
}
46+
unsafe { Vec::from_raw_parts(ptr_start, len, capacity) }
47+
}

toolchain/riscv/axvm/src/lib.rs

Lines changed: 6 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,16 @@
1-
// Copyright 2024 RISC Zero, Inc.
2-
//
3-
// Licensed under the Apache License, Version 2.0 (the "License");
4-
// you may not use this file except in compliance with the License.
5-
// You may obtain a copy of the License at
6-
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
9-
// Unless required by applicable law or agreed to in writing, software
10-
// distributed under the License is distributed on an "AS IS" BASIS,
11-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
// See the License for the specific language governing permissions and
13-
// limitations under the License.
14-
15-
//! The RISC Zero zkVM's guest-side RISC-V API.
16-
//!
17-
//! Code that is validated by the [RISC Zero zkVM](crate) is run inside the guest. In almost all
18-
//! practical cases, the guest will want to read private input data from the host and write public
19-
//! data to the journal. This can be done with [env::read] and [env::commit], respectively;
20-
//! additional I/O functionality is also available in [mod@env].
21-
//!
22-
//! ## Installation
23-
//!
24-
//! To build and run RISC Zero zkVM code, you will need to install the RISC Zero
25-
//! toolchain, which can be done using the rzup utility:
26-
//!
27-
//! ```sh
28-
//! curl -L https://risczero.com/install | bash
29-
//! rzup install
30-
//! ```
31-
//!
32-
//! ## Example
33-
//!
34-
//! The following guest code[^starter-ex] proves a number is
35-
//! composite by multiplying two unsigned integers, and panicking if either is
36-
//! `1` or if the multiplication overflows:
37-
//!
38-
//! ```ignore
39-
//! #![no_main]
40-
//! #![no_std]
41-
//!
42-
//! use risc0_zkvm::guest::env;
43-
//!
44-
//! risc0_zkvm::guest::entry!(main);
45-
//!
46-
//! fn main() {
47-
//! // Load the first number from the host
48-
//! let a: u64 = env::read();
49-
//! // Load the second number from the host
50-
//! let b: u64 = env::read();
51-
//! // Verify that neither of them are 1 (i.e. nontrivial factors)
52-
//! if a == 1 || b == 1 {
53-
//! panic!("Trivial factors")
54-
//! }
55-
//! // Compute the product while being careful with integer overflow
56-
//! let product = a.checked_mul(b).expect("Integer overflow");
57-
//! env::commit(&product);
58-
//! }
59-
//! ```
60-
//!
61-
//! Notice how [env::read] is used to load the two factors, and [env::commit] is used to make their
62-
//! composite product publicly available. All input an output of your guest is private except for
63-
//! what is written to the journal with [env::commit].
64-
//!
65-
//! By default, the guest only has the Rust `core` libraries and not `std`. A partial
66-
//! implementation of the Rust standard libraries can be enabled with the `std` feature on this [crate].
67-
//! When this feature is not enabled, the lines including `#![no_std]` and `#![no_main]` are
68-
//! required, as well as the use of the [crate::guest::entry] macro. When `std` is enabled, these
69-
//! three lines can be omitted and many features of `std` can be used.
70-
//!
71-
//! If you encounter problems building zkVM guest code, you can see if we have a
72-
//! known workaround for your issue by looking in our
73-
//! [rust guest workarounds](https://github.com/risc0/risc0/issues?q=is%3Aissue+is%3Aopen+label%3A%22rust+guest+workarounds%22)
74-
//! tag on GitHub.
75-
//!
76-
//! [^starter-ex]: The example is based on the [Factors example](https://github.com/risc0/risc0/tree/main/examples/factors).
1+
//! # axVM
772
783
#![cfg_attr(not(feature = "std"), no_std)]
794
#![deny(rustdoc::broken_intra_doc_links)]
805
#![deny(missing_docs)]
816
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
7+
#![feature(asm_const)]
828

839
extern crate alloc;
8410

85-
pub mod env;
8611
pub mod intrinsics;
12+
pub mod io;
13+
pub mod process;
8714
pub mod serde;
8815

8916
#[cfg(target_os = "zkvm")]
@@ -165,7 +92,8 @@ unsafe extern "C" fn __start() -> ! {
16592
main()
16693
}
16794

168-
env::exit::<0>();
95+
process::exit();
96+
unreachable!()
16997
}
17098

17199
#[cfg(target_os = "zkvm")]

toolchain/riscv/axvm/src/process.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//! System exit and panic functions.
2+
3+
/// Exit the program with exit code 0.
4+
pub fn exit() {
5+
axvm_platform::rust_rt::terminate::<0>();
6+
}
7+
8+
/// Exit the program with exit code 1.
9+
pub fn panic() {
10+
axvm_platform::rust_rt::terminate::<1>();
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[build]
2+
target = "riscv32im-risc0-zkvm-elf"
3+
4+
[unstable]
5+
build-std = ["core", "alloc", "proc_macro", "panic_abort"]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[workspace]
2+
[package]
3+
version = "0.1.0"
4+
name = "axvm-hint-program"
5+
edition = "2021"
6+
7+
[dependencies]
8+
axvm = { path = "../../../axvm" }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![no_main]
2+
#![no_std]
3+
use axvm::io::read_vec;
4+
5+
axvm::entry!(main);
6+
7+
pub fn main() {
8+
let vec = read_vec();
9+
assert_eq!(vec.len(), 4);
10+
for i in 0..4 {
11+
assert_eq!(vec[i], i as u8);
12+
}
13+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
pub const CUSTOM_0: u8 = 0x0b;
2+
pub const CUSTOM_1: u8 = 0x2b;
3+
4+
#[macro_export]
5+
macro_rules! custom_insn_i {
6+
($opcode:expr, $funct3:expr, $rd:literal, $rs1:literal, $imm:expr) => {
7+
unsafe {
8+
core::arch::asm!(concat!(
9+
".insn i {opcode}, {funct3}, ",
10+
$rd,
11+
", ",
12+
$rs1,
13+
", {imm}",
14+
), opcode = const $opcode, funct3 = const $funct3, imm = const $imm)
15+
}
16+
};
17+
($opcode:expr, $funct3:expr, $x:ident, $rs1:literal, $imm:expr) => {
18+
unsafe {
19+
core::arch::asm!(concat!(
20+
".insn i {opcode}, {funct3}, {rd}, ",
21+
$rs1,
22+
", {imm}",
23+
), opcode = const $opcode, funct3 = const $funct3, rd = in(reg) $x, imm = const $imm)
24+
}
25+
};
26+
}
27+
28+
#[macro_export]
29+
macro_rules! custom_insn_r {
30+
($opcode:expr, $funct3:expr, $rd:literal, $rs1:literal, $rs2:literal) => {
31+
unsafe {
32+
core::arch::asm!(concat!(
33+
".insn r {opcode}, {funct3}, ",
34+
$rd,
35+
", ",
36+
$rs1,
37+
", ",
38+
$rs2,
39+
), opcode = const $opcode, funct3 = const $funct3)
40+
}
41+
};
42+
($opcode:expr, $funct3:expr, $x:ident, $rs1:literal, $rs2:literal) => {
43+
unsafe {
44+
core::arch::asm!(concat!(
45+
".insn r {opcode}, {funct3}, {rd}, ",
46+
$rs1,
47+
", ",
48+
$rs2,
49+
), opcode = const $opcode, funct3 = const $funct3, rd = out(reg) $x)
50+
}
51+
};
52+
// TODO: implement more variants with like rs1 = in(reg) $y etc
53+
}

toolchain/riscv/platform/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod memory;
2626
// mod getrandom;
2727
#[cfg(all(feature = "rust-runtime", target_os = "zkvm"))]
2828
pub mod heap;
29+
pub mod intrinsics;
2930
#[cfg(all(feature = "export-libm", target_os = "zkvm"))]
3031
mod libm_extern;
3132
#[cfg(feature = "rust-runtime")]

0 commit comments

Comments
 (0)