Skip to content

Commit f38589b

Browse files
evan-schottsjudson
authored andcommitted
Write exit code to output (#345)
* impl * indexing bug * sam + duc comments * fix
1 parent e6f4d39 commit f38589b

File tree

7 files changed

+73
-39
lines changed

7 files changed

+73
-39
lines changed

runtime/src/io.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ extern crate alloc;
55
mod riscv32 {
66
extern crate alloc;
77
use crate::{
8-
ecall, rin, wou, SYS_CYCLE_COUNT, SYS_EXIT, SYS_LOG, SYS_READ_PRIVATE_INPUT, WORD_SIZE,
8+
ecall, read_input, write_output, SYS_CYCLE_COUNT, SYS_EXIT, SYS_LOG,
9+
SYS_READ_PRIVATE_INPUT, WORD_SIZE,
910
};
1011
use serde::{de::DeserializeOwned, Serialize};
1112

@@ -24,6 +25,9 @@ mod riscv32 {
2425

2526
/// Exit the program with the given exit code.
2627
pub fn exit(exit_code: i32) -> ! {
28+
// Write the exit code to the output.
29+
let _ = write_output!(0, exit_code);
30+
// Exit the program.
2731
let _ = ecall!(SYS_EXIT, exit_code);
2832
// Ecall will trigger exit syscall, so we will never return.
2933
unsafe {
@@ -54,13 +58,13 @@ mod riscv32 {
5458
pub fn read_public_input<T: DeserializeOwned>() -> Result<T, postcard::Error> {
5559
// The first word stores the length of the input (in bytes).
5660
// This length does not take into account the first word itself.
57-
let len = rin!(0) as usize;
61+
let len = read_input!(0) as usize;
5862
let padded_len = (len + 3) & !3;
5963
let mut input = alloc::vec![0u8; padded_len];
6064

6165
// Read the input into the vector.
6266
for i in 0..((padded_len) / WORD_SIZE) {
63-
let word = rin!((i + 1) * WORD_SIZE);
67+
let word = read_input!((i + 1) * WORD_SIZE);
6468
input[i * WORD_SIZE..(i + 1) * WORD_SIZE].copy_from_slice(&word.to_le_bytes());
6569
}
6670

@@ -78,7 +82,7 @@ mod riscv32 {
7882
// Write bytes in word chunks to output memory.
7983
bytes.chunks(WORD_SIZE).enumerate().for_each(|(i, chunk)| {
8084
let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
81-
wou!(i * WORD_SIZE, word);
85+
write_output!((i + 1) * WORD_SIZE, word); // word 0 is reserved for the exit code
8286
});
8387

8488
Ok(())

runtime/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ macro_rules! ecall {
8787

8888
/// Reads word from specified byte address of the public input.
8989
#[macro_export]
90-
macro_rules! rin {
90+
macro_rules! read_input {
9191
($i:expr) => {{
9292
let mut out: u32;
9393
unsafe {
@@ -106,7 +106,7 @@ macro_rules! rin {
106106

107107
/// Writes word to the public output at specified byte address.
108108
#[macro_export]
109-
macro_rules! wou {
109+
macro_rules! write_output {
110110
($i:expr, $data:expr) => {
111111
unsafe {
112112
core::arch::asm!(

runtime/src/runtime.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
// Nexus VM runtime environment
22
// Note: adapted from riscv-rt, which was adapted from cortex-m.
33
use crate::alloc::sys_alloc_aligned;
4-
use crate::{ecall, EXIT_PANIC, EXIT_SUCCESS, SYS_EXIT, SYS_LOG, SYS_OVERWRITE_SP};
4+
use crate::{ecall, write_output, EXIT_PANIC, EXIT_SUCCESS, SYS_EXIT, SYS_LOG, SYS_OVERWRITE_SP};
55
use core::alloc::{GlobalAlloc, Layout};
66
use core::panic::PanicInfo;
77

88
#[inline(never)]
99
#[panic_handler]
1010
fn panic(_info: &PanicInfo) -> ! {
11+
// Write the exit code to the output.
12+
let _ = write_output!(0, EXIT_PANIC);
13+
// Finish with exit syscall.
1114
let _ = ecall!(SYS_EXIT, EXIT_PANIC);
1215
// Ecall will trigger exit syscall, so we will never return.
1316
unsafe {
@@ -62,6 +65,8 @@ pub unsafe extern "C" fn start_rust() -> u32 {
6265
// Run the program.
6366
main();
6467

68+
// Write the exit code to the output.
69+
let _ = write_output!(0, EXIT_SUCCESS);
6570
// Finish with exit syscall.
6671
ecall!(SYS_EXIT, EXIT_SUCCESS)
6772
}

tests/testing-framework/src/lib.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(test)]
22
mod test {
3+
use nexus_common::constants::WORD_SIZE;
34
use nexus_common::cpu::InstructionResult;
45

56
use nexus_vm::elf::ElfFile;
@@ -72,6 +73,23 @@ mod test {
7273
}
7374
}
7475

76+
/// Parse the output bytes as exit code and output.
77+
fn parse_output<T: DeserializeOwned>(
78+
output: Vec<u8>,
79+
) -> Result<(u32, Option<T>), postcard::Error> {
80+
// The first 4 bytes store the exit code.
81+
let exit_code =
82+
u32::from_le_bytes(output[0..4].try_into().expect("Failed to parse exit code"));
83+
84+
if output.len() == WORD_SIZE {
85+
Ok((exit_code, None))
86+
} else {
87+
// Deserialize the rest as the output.
88+
let output: T = from_bytes(&output[WORD_SIZE..]).expect("Deserialization failed");
89+
Ok((exit_code, Some(output)))
90+
}
91+
}
92+
7593
/// Create a temporary directory with a new Cargo project that has nexus_rt as a local dependency.
7694
fn create_tmp_dir() -> TempDir {
7795
// Create a temporary directory.
@@ -197,6 +215,7 @@ mod test {
197215
}
198216

199217
let mut deserialized_output: Option<V> = None;
218+
let mut exit_code: u32;
200219
let ad = vec![0u8; 0xbeef as usize]; // placeholder ad until we have use for it
201220

202221
for elf in elfs {
@@ -210,11 +229,12 @@ mod test {
210229
assert_eq!(&emulator.execute(), &io_args.expected_result);
211230

212231
// Deserialize the output.
213-
if io_args.expected_output.is_some() {
214-
let output_bytes = emulator.get_output().unwrap();
215-
deserialized_output =
216-
Some(from_bytes(&output_bytes).expect("Deserialization failed"));
217-
}
232+
(exit_code, deserialized_output) =
233+
parse_output(emulator.get_output().unwrap()).unwrap();
234+
assert_eq!(
235+
Err(nexus_vm::error::VMError::VMExited(exit_code)),
236+
io_args.expected_result
237+
);
218238

219239
// Run a second pass with a linear emulator constructed from the harvard emulator.
220240
if matches!(emulator_type, EmulatorType::TwoPass) {
@@ -230,11 +250,12 @@ mod test {
230250
assert_eq!(&linear_emulator.execute(), &io_args.expected_result);
231251

232252
// Deserialize the output.
233-
if io_args.expected_output.is_some() {
234-
let output_bytes = linear_emulator.get_output().unwrap();
235-
deserialized_output =
236-
Some(from_bytes(&output_bytes).expect("Deserialization failed"));
237-
}
253+
(exit_code, deserialized_output) =
254+
parse_output(linear_emulator.get_output().unwrap()).unwrap();
255+
assert_eq!(
256+
Err(nexus_vm::error::VMError::VMExited(exit_code)),
257+
io_args.expected_result
258+
);
238259
}
239260
}
240261
EmulatorType::Linear(heap_size, stack_size, program_size) => {
@@ -269,11 +290,12 @@ mod test {
269290
assert_eq!(&emulator.execute(), &io_args.expected_result);
270291

271292
// Deserialize the output.
272-
if io_args.expected_output.is_some() {
273-
let output_bytes = emulator.get_output().unwrap();
274-
deserialized_output =
275-
Some(from_bytes(&output_bytes).expect("Deserialization failed"));
276-
}
293+
(exit_code, deserialized_output) =
294+
parse_output(emulator.get_output().unwrap()).unwrap();
295+
assert_eq!(
296+
Err(nexus_vm::error::VMError::VMExited(exit_code)),
297+
io_args.expected_result
298+
);
277299
}
278300
};
279301
}

vm/src/emulator/executor.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ impl LinearEmulator {
520520
* WORD_SIZE) as u32,
521521
ad.len() as u32,
522522
public_input.len() as u32,
523-
output_memory.len() as u32,
523+
(output_memory.len() - WORD_SIZE) as u32, // Exclude the first word which is the exit code
524524
)
525525
.unwrap();
526526

@@ -615,10 +615,13 @@ impl LinearEmulator {
615615
let _ = memory.add_fixed_ro(&input_memory).unwrap();
616616
}
617617

618-
let output_len = (memory_layout.public_output_end() - memory_layout.panic()) as usize; // we include panic in the output segment
618+
let output_len = (memory_layout.public_output_end() - memory_layout.exit_code()) as usize; // we include the exit code in the output segment
619619
if output_len > 0 {
620-
let output_memory =
621-
FixedMemory::<WO>::from_vec(memory_layout.panic(), output_len, vec![0; output_len]);
620+
let output_memory = FixedMemory::<WO>::from_vec(
621+
memory_layout.exit_code(),
622+
output_len,
623+
vec![0; output_len],
624+
);
622625
let _ = memory.add_fixed_wo(&output_memory).unwrap();
623626
}
624627

@@ -656,7 +659,7 @@ impl LinearEmulator {
656659
8,
657660
&[
658661
memory_layout.public_input_start(),
659-
memory_layout.public_output_start(),
662+
memory_layout.exit_code(), // The exit code is the first word of the output
660663
],
661664
))
662665
.unwrap();
@@ -682,7 +685,7 @@ impl LinearEmulator {
682685
pub fn get_output(&self) -> Result<Vec<u8>, MemoryError> {
683686
Ok(self.memory.segment_bytes(
684687
(Modes::WO as usize, 0),
685-
self.memory_layout.public_output_start(),
688+
self.memory_layout.exit_code(),
686689
Some(self.memory_layout.public_output_end()),
687690
)?)
688691
}

vm/src/emulator/layout.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
88
pub struct LinearMemoryLayout {
99
// start of the public input
1010
public_input: u32,
11-
// location of the panic byte
12-
panic: u32,
11+
// location of the exit code
12+
exit_code: u32,
1313
// start of the public output
1414
public_output: u32,
1515
// start of the associated data hash
@@ -49,8 +49,8 @@ impl LinearMemoryLayout {
4949
|| self.gap_start() < self.heap_start()
5050
|| self.heap_start() < self.ad_start()
5151
|| self.ad_start() < self.public_output_start()
52-
|| self.public_output_start() <= self.panic()
53-
|| self.panic() <= self.public_input_start() // First word of input stores input length, so must be non-empty
52+
|| self.public_output_start() <= self.exit_code()
53+
|| self.exit_code() <= self.public_input_start() // First word of input stores input length, so must be non-empty
5454
|| self.public_input_start() <= self.program_start() // Program assumed to be non-empty
5555
|| self.program_start() <= self.public_output_start_location()
5656
{
@@ -69,8 +69,8 @@ impl LinearMemoryLayout {
6969
ad_size: u32,
7070
) -> Self {
7171
let public_input = ELF_TEXT_START + program_size;
72-
let panic = public_input + public_input_size + WORD_SIZE as u32;
73-
let public_output = panic + WORD_SIZE as u32;
72+
let exit_code = public_input + public_input_size + WORD_SIZE as u32;
73+
let public_output = exit_code + WORD_SIZE as u32;
7474
let ad = public_output + public_output_size;
7575
let heap = ad + ad_size;
7676
let gap = heap + max_heap_size;
@@ -79,7 +79,7 @@ impl LinearMemoryLayout {
7979

8080
Self {
8181
public_input,
82-
panic,
82+
exit_code,
8383
public_output,
8484
ad,
8585
heap,
@@ -143,11 +143,11 @@ impl LinearMemoryLayout {
143143
}
144144

145145
pub fn public_input_end(&self) -> u32 {
146-
self.panic
146+
self.exit_code
147147
}
148148

149-
pub fn panic(&self) -> u32 {
150-
self.panic
149+
pub fn exit_code(&self) -> u32 {
150+
self.exit_code
151151
}
152152

153153
pub fn public_output_start(&self) -> u32 {

vm/src/memory/fixed.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl<M: Mode> FixedMemory<M> {
9393
let s = (start - self.base_address) / WORD_SIZE as u32;
9494

9595
if let Some(mut e) = end {
96-
e -= self.base_address;
96+
e = (e - self.base_address) / WORD_SIZE as u32;
9797
&self.vec[s as usize..e as usize]
9898
} else {
9999
&self.vec[s as usize..]

0 commit comments

Comments
 (0)