add a runtime crate

Update riscv/runtime/src/lib.rs

Co-authored-by: chriseth <chris@ethereum.org>

package runtime lib to compile single files

Remove need for loop{} in main

Remove 'extern "C"' from main declaration

implement mem* funcs in the runtime

Co-Authored-by: chriseth <chris@ethereum.org>

fix a couple endianness and alignment issues

remove unused build entry in compiler/Cargo.toml

more review feedback

fix clippy linter error

update README

Remove all #[no_main]

Add test runner for memfuncs.
This commit is contained in:
Guillaume Ballet
2023-04-27 13:27:51 +02:00
parent ac8bcc3b9c
commit 5d5ab18d7b
16 changed files with 279 additions and 169 deletions

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
riscv/runtime/target

View File

@@ -45,10 +45,10 @@ Note that this is the full and only input file you need for the whole process!
```rust
#![no_std]
use core::arch::asm;
use runtime::get_prover_input;
#[no_mangle]
pub extern "C" fn main() -> ! {
pub fn main() {
let mut buffer = [0u32; 100];
let proposed_sum = get_prover_input(0);
let len = get_prover_input(1) as usize;
@@ -58,16 +58,6 @@ pub extern "C" fn main() -> ! {
}
let sum: u32 = buffer[..len].iter().sum();
assert!(sum == proposed_sum);
loop {}
}
#[inline]
fn get_prover_input(index: u32) -> u32 {
let mut value: u32;
unsafe {
asm!("ecall", lateout("a0") value, in("a0") index);
}
value
}
```

8
riscv/runtime/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "runtime"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

141
riscv/runtime/src/lib.rs Normal file
View File

@@ -0,0 +1,141 @@
#![no_std]
#![feature(start)]
use core::arch::asm;
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
// activate after #176 has been printed
// print(format!("panic at line {} in file {}\n", panic.location().line(), panic.location().file()));
unsafe {
asm!("unimp");
}
loop {}
}
#[inline]
pub fn get_prover_input(index: u32) -> u32 {
let mut value: u32;
unsafe {
asm!("ecall", lateout("a0") value, in("a0") index);
}
value
}
extern "Rust" {
fn main();
}
#[no_mangle]
#[start]
pub unsafe extern "C" fn __runtime_start() -> ! {
unsafe {
main();
}
loop {}
}
#[no_mangle]
pub unsafe extern "C" fn __runtime_memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
if (src as u32) % 4 == 0 && (dest as u32) % 4 == 0 {
memcpy_aligned(dest, src, n);
} else {
for i in 0..n {
*dest.offset(i as isize) = *src.offset(i as isize);
}
}
dest
}
pub unsafe fn memcpy_aligned(dest: *mut u8, src: *const u8, n: usize) {
let mut i: isize = 0;
while i + 3 < n as isize {
*((dest.offset(i)) as *mut u32) = *((src.offset(i)) as *mut u32);
i += 4;
}
if i < n as isize {
let value = *((src.offset(i)) as *mut u32);
let dest_value = (dest.offset(i)) as *mut u32;
let mask = (1 << (((n as isize - i) * 8) as u32)) - 1;
*dest_value = (*dest_value & !mask) | (value & mask);
}
}
#[no_mangle]
pub unsafe extern "C" fn __runtime_memset(dest: *mut u8, val: u8, n: usize) -> *mut u8 {
let mut i: isize = 0;
let pattern = (val as u32) << 24 | (val as u32) << 16 | (val as u32) << 8 | (val as u32);
// Prologue: set bytes in the first non-aligned word
let first_word_offset = (dest as u32) % 4;
if first_word_offset != 0 {
let aligned_dest = dest.offset(-(first_word_offset as isize)) as *mut u32;
let mask = (1 << ((first_word_offset * 8) as u32)) - 1;
*aligned_dest = (*aligned_dest & mask) | (pattern & !mask);
i += 4 - first_word_offset as isize;
}
// Set all full words
while i + 3 < n as isize {
*((dest.offset(i)) as *mut u32) = pattern;
i += 4;
}
// last part: set the final word
if i < n as isize {
let dest_value = (dest.offset(i)) as *mut u32;
let mask = (1 << (((n as isize - i) * 8) as u32)) - 1;
*dest_value = (*dest_value & !mask) | (pattern & mask);
}
dest
}
#[no_mangle]
pub unsafe extern "C" fn __runtime_memcmp(dest: *const u8, src: *const u8, n: usize) -> i32 {
// if (src as u32) % 4 == (dest as u32) % 4 {
// return memcmp_aligned(dest, src, n);
// }
for i in 0..n {
if *dest.offset(i as isize) != *src.offset(i as isize) {
return *dest.offset(i as isize) as i32 - *src.offset(i as isize) as i32;
}
}
0i32
}
// pub unsafe fn memcmp_aligned(dest: *mut u8, src: *const u8, n: usize) -> i32 {
// let mut i: isize = 0;
// // Prologue: set bytes in the first non-aligned word
// let first_word_offset = (dest as u32) % 4;
// if first_word_offset != 0 {
// let value = *((src.offset(-(first_word_offset as isize))) as *mut u32);
// let aligned_dest = dest.offset(-(first_word_offset as isize)) as *mut u32;
// let mask = (1 << ((first_word_offset * 8) as u32)) - 1;
// if (*aligned_dest & !mask) != (value & !mask) {
// return false;
// }
// i += 4 - first_word_offset as isize;
// }
// while i + 3 < n as isize {
// if *((dest.offset(i)) as *mut u32) != *((src.offset(i)) as *mut u32) {
// return false;
// }
// i += 4;
// }
// // last part: set the final word
// if i < n as isize {
// let value = *((src.offset(i)) as *mut u32);
// let dest_value = (dest.offset(i)) as *mut u32;
// let mask = (1 << (((n as isize - i) * 8) as u32)) - 1;
// if (*dest_value & mask) != (value & mask) {
// return false;
// }
// }
// true
// }

View File

@@ -29,7 +29,7 @@ pub fn compile_riscv_asm(mut assemblies: BTreeMap<String, String>) -> String {
// Reduce to the code that is actually reachable from main
// (and the objects that are referred from there)
reachability::filter_reachable_from("main", &mut statements, &mut objects);
reachability::filter_reachable_from("__runtime_start", &mut statements, &mut objects);
// Replace dynamic references to code labels
replace_dynamic_label_references(&mut statements, &objects);
@@ -41,7 +41,7 @@ pub fn compile_riscv_asm(mut assemblies: BTreeMap<String, String>) -> String {
.into_iter()
.chain([
format!("// Set stack pointer\nx2 <=X= {stack_start};"),
"jump main;".to_string(),
"jump __runtime_start;".to_string(),
])
.chain(
insert_data_positions(statements, &data_positions)
@@ -552,100 +552,17 @@ fn runtime() -> &'static str {
}
*/
r#"
.globl rust_begin_unwind
rust_begin_unwind:
unimp
.globl memset@plt
memset@plt:
li a3, 4
blt a2, a3, __memset_LBB5_5
li a5, 0
lui a3, 4112
addi a3, a3, 257
mul a6, a1, a3
__memset_LBB5_2:
add a7, a0, a5
addi a3, a5, 4
addi a4, a5, 7
sw a6, 0(a7)
mv a5, a3
blt a4, a2, __memset_LBB5_2
bge a3, a2, __memset_LBB5_6
__memset_LBB5_4:
lui a4, 16
addi a4, a4, 257
mul a1, a1, a4
add a3, a3, a0
slli a2, a2, 3
lw a4, 0(a3)
li a5, -1
sll a2, a5, a2
not a5, a2
and a2, a2, a4
and a1, a1, a5
or a1, a1, a2
sw a1, 0(a3)
ret
__memset_LBB5_5:
li a3, 0
blt a3, a2, __memset_LBB5_4
__memset_LBB5_6:
ret
tail __runtime_memset
.globl memcpy@plt
memcpy@plt:
li a3, 4
blt a2, a3, __memcpy_LBB2_5
li a4, 0
__memcpy_LBB2_2:
add a3, a1, a4
lw a6, 0(a3)
add a7, a0, a4
addi a3, a4, 4
addi a5, a4, 7
sw a6, 0(a7)
mv a4, a3
blt a5, a2, __memcpy_LBB2_2
bge a3, a2, __memcpy_LBB2_6
__memcpy_LBB2_4:
add a1, a1, a3
lw a1, 0(a1)
add a3, a3, a0
slli a2, a2, 3
lw a4, 0(a3)
li a5, -1
sll a2, a5, a2
not a5, a2
and a2, a2, a4
and a1, a1, a5
or a1, a1, a2
sw a1, 0(a3)
ret
__memcpy_LBB2_5:
li a3, 0
blt a3, a2, __memcpy_LBB2_4
__memcpy_LBB2_6:
ret
tail __runtime_memcpy
.globl memcmp@plt
memcmp@plt:
beqz a2, .LBB270_3
.LBB270_1:
lbu a3, 0(a0)
lbu a4, 0(a1)
bne a3, a4, .LBB270_4
addi a1, a1, 1
addi a2, a2, -1
addi a0, a0, 1
bnez a2, .LBB270_1
.LBB270_3:
li a0, 0
ret
.LBB270_4:
sub a0, a3, a4
ret
tail __runtime_memcmp
"#
}

View File

@@ -124,6 +124,9 @@ pub fn compile_rust_to_riscv_asm(input_file: &str) -> BTreeMap<String, String> {
name = "{}"
version = "0.1.0"
edition = "2021"
[dependencies]
runtime = {{ path = "./runtime" }}
"#,
Path::new(input_file).file_stem().unwrap().to_str().unwrap()
),
@@ -136,6 +139,21 @@ edition = "2021"
src_file.push("lib.rs");
fs::write(src_file, fs::read_to_string(input_file).unwrap()).unwrap();
let mut runtime_file = crate_dir.clone();
runtime_file.push("runtime");
fs::create_dir_all(&runtime_file).unwrap();
let mut cargo_file_runtime = runtime_file.clone();
cargo_file_runtime.push("Cargo.toml");
fs::write(
cargo_file_runtime.clone(),
include_bytes!("../runtime/Cargo.toml"),
)
.unwrap();
runtime_file.push("src");
fs::create_dir(&runtime_file).unwrap();
runtime_file.push("lib.rs");
fs::write(runtime_file, include_bytes!("../runtime/src/lib.rs")).unwrap();
compile_rust_crate_to_riscv_asm(cargo_file.to_str().unwrap())
}

View File

@@ -50,6 +50,13 @@ fn test_double_word() {
);
}
#[test]
#[ignore = "Too slow"]
fn test_memfuncs() {
let case = "memfuncs";
verify_crate(case, vec![]);
}
#[test]
#[ignore = "Too slow"]
fn test_keccak() {

View File

@@ -3,7 +3,6 @@
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::panic::PanicInfo;
use core::ptr;
extern crate alloc;
@@ -67,26 +66,6 @@ pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mu
memcpy(dest, src, n)
}
// TODO c is ussualy a "c int"
pub unsafe extern "C" fn memset(s: *mut u8, c: u8, n: usize) -> *mut u8 {
// We only access u32 because then we do not have to deal with
// un-aligned memory access.
// TODO this does not really enforce that the pointers are u32-aligned.
let mut value = c as u32;
value = value | (value << 8) | (value << 16) | (value << 24);
let mut i: isize = 0;
while i + 3 < n as isize {
*((s.offset(i)) as *mut u32) = value;
i += 4;
}
if i < n as isize {
let dest_value = (s.offset(i)) as *mut u32;
let mask = (1 << (((n as isize - i) * 8) as u32)) - 1;
*dest_value = (*dest_value & !mask) | (value & mask);
}
s
}
// pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 {
// for i in 0..n as isize {
// let a = *s1.offset(i);
@@ -106,11 +85,6 @@ pub unsafe extern "C" fn memset(s: *mut u8, c: u8, n: usize) -> *mut u8 {
// i as usize
// }
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
// Declaration of the global memory allocator
// NOTE the user must ensure that the memory region `[0x2000_0100, 0x2000_0200]`
// is not used by other parts of the program
@@ -123,7 +97,7 @@ static HEAP: BumpPointerAlloc = BumpPointerAlloc {
static LONG_STR: &str = "aoeueeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
#[no_mangle]
pub extern "C" fn main() -> ! {
pub fn main() -> ! {
let mut xs = Vec::new();
xs.push(42);

View File

@@ -1,11 +1,11 @@
#![no_std]
use core::arch::asm;
use runtime::get_prover_input;
const X: &'static str = "abcdefg";
#[no_mangle]
pub extern "C" fn main() -> ! {
pub fn main() {
let replacement_index = get_prover_input(0) as usize;
let replacement_value = get_prover_input(1) as u8;
let mut x = [0; 10];
@@ -16,14 +16,4 @@ pub extern "C" fn main() -> ! {
let claimed_sum = get_prover_input(2) as u32;
let computed_sum = x.iter().map(|c| *c as u32).sum();
assert!(claimed_sum == computed_sum);
loop {}
}
#[inline]
fn get_prover_input(index: u32) -> u32 {
let mut value: u32;
unsafe {
asm!("ecall", lateout("a0") value, in("a0") index);
}
value
}

View File

@@ -1,9 +1,9 @@
#![no_std]
use core::arch::asm;
use runtime::get_prover_input;
#[no_mangle]
pub extern "C" fn main() -> ! {
pub fn main() {
let a0 = get_prover_input(0) as u64;
let a1 = (get_prover_input(1) as u64) << 32;
let b0 = get_prover_input(2) as u64;
@@ -13,14 +13,4 @@ pub extern "C" fn main() -> ! {
let c1 = ((c >> 32) & 0xffffffffu64) as u32;
assert!(c0 == get_prover_input(4));
assert!(c1 == get_prover_input(5));
loop {}
}
#[inline]
fn get_prover_input(index: u32) -> u32 {
let mut value: u32;
unsafe {
asm!("ecall", lateout("a0") value, in("a0") index);
}
value
}
}

View File

@@ -7,5 +7,6 @@ edition = "2021"
[dependencies]
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
runtime = { path = "../../../runtime" }
[workspace]

View File

@@ -3,7 +3,7 @@
use tiny_keccak::{Hasher, Keccak};
#[no_mangle]
pub extern "C" fn main() -> ! {
pub fn main() {
let input = b"Solidity";
let mut output = [0u8; 32];
let mut hasher = Keccak::v256();
@@ -16,5 +16,4 @@ pub extern "C" fn main() -> ! {
111, 47, 142, 70, 161, 157, 188, 119, 124, 54, 251, 12, 0,
],
);
loop {}
}

View File

@@ -0,0 +1,11 @@
[package]
name = "memfuncs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
runtime = { path = "../../../runtime" }
[workspace]

View File

@@ -0,0 +1,73 @@
#![no_std]
use runtime::*;
#[no_mangle]
pub fn main() {
let input = [1u8; 100];
let mut output = [0u8; 100];
// Check memset accross boundaries
unsafe {
__runtime_memset(output.as_mut_ptr().offset(5), 2, 32);
}
for i in 0..5 {
assert_eq!(
output[i],
0,
);
}
for i in 5..=36 {
assert_eq!(
output[i],
2,
);
}
for i in 37..100 {
assert_eq!(
output[i],
0,
);
}
// Check memcpy accross boundaries
unsafe {
__runtime_memcpy(output.as_mut_ptr().offset(5), input.as_ptr().offset(5), 32);
}
for i in 0..5 {
assert_eq!(
output[i],
0,
);
}
for i in 5..=36 {
assert_eq!(
output[i],
1,
);
}
for i in 37..100 {
assert_eq!(
output[i],
0,
);
}
// Check memcmp accross boundaries
// This doesn't work because it compiles to the unsupported
// assembly instruction "srai".
// unsafe {
// assert_eq!(
// __runtime_memcmp(output.as_ptr().offset(5), input.as_ptr().offset(5), 32),
// 0,
// );
// assert_eq!(
// __runtime_memcmp(output.as_ptr().offset(5), input.as_ptr().offset(6), 32),
// -1,
// );
// assert_eq!(
// __runtime_memcmp(output.as_ptr().offset(6), input.as_ptr().offset(5), 32),
// 1,
// );
// }
}

View File

@@ -1,9 +1,9 @@
#![no_std]
use core::arch::asm;
use runtime::get_prover_input;
#[no_mangle]
pub extern "C" fn main() -> ! {
pub fn main() {
let mut buffer = [0u32; 100];
let proposed_sum = get_prover_input(0);
let len = get_prover_input(1) as usize;
@@ -13,14 +13,4 @@ pub extern "C" fn main() -> ! {
}
let sum: u32 = buffer[..len].iter().sum();
assert!(sum == proposed_sum);
loop {}
}
#[inline]
fn get_prover_input(index: u32) -> u32 {
let mut value: u32;
unsafe {
asm!("ecall", lateout("a0") value, in("a0") index);
}
value
}
}

View File

@@ -1,6 +1,5 @@
#![no_std]
#[no_mangle]
pub extern "C" fn main() -> ! {
loop {}
pub fn main() {
}