Skip to content

Commit

Permalink
Fix negative offset handling for SW and LW instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
moodlezoup committed Jan 21, 2025
1 parent b8d6f0a commit 33a4c15
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 27 deletions.
15 changes: 9 additions & 6 deletions common/src/rv_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ impl From<&RVTraceRow> for [MemoryOp; MEMORY_OPS_PER_INSTRUCTION] {
MemoryOp::noop_read(),
],
RV32InstructionFormat::I => match val.instruction.opcode {
RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT => [
RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT
| RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT => [
rs1_read(),
MemoryOp::noop_read(),
MemoryOp::noop_write(),
Expand Down Expand Up @@ -232,7 +233,8 @@ impl ELFInstruction {
| RV32IM::JALR
| RV32IM::SW
| RV32IM::LW
| RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT,
| RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT
| RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT,
);

flags[CircuitFlags::Load as usize] = matches!(
Expand Down Expand Up @@ -275,6 +277,7 @@ impl ELFInstruction {
| RV32IM::VIRTUAL_ASSERT_VALID_SIGNED_REMAINDER
| RV32IM::VIRTUAL_ASSERT_VALID_UNSIGNED_REMAINDER
| RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT
| RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT
);

flags[CircuitFlags::ConcatLookupQueryChunks as usize] = matches!(
Expand Down Expand Up @@ -314,12 +317,10 @@ impl ELFInstruction {
RV32IM::VIRTUAL_ASSERT_EQ |
RV32IM::VIRTUAL_ASSERT_LTE |
RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT |
RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT |
RV32IM::VIRTUAL_ASSERT_VALID_SIGNED_REMAINDER |
RV32IM::VIRTUAL_ASSERT_VALID_UNSIGNED_REMAINDER |
RV32IM::VIRTUAL_ASSERT_VALID_DIV0 |
// SW and LW perform a `AssertAlignedMemoryAccessInstruction` lookup
RV32IM::SW |
RV32IM::LW
RV32IM::VIRTUAL_ASSERT_VALID_DIV0
);

// All instructions in virtual sequence are mapped from the same
Expand Down Expand Up @@ -429,6 +430,7 @@ pub enum RV32IM {
VIRTUAL_ASSERT_EQ,
VIRTUAL_ASSERT_VALID_DIV0,
VIRTUAL_ASSERT_HALFWORD_ALIGNMENT,
VIRTUAL_ASSERT_WORD_ALIGNMENT,
}

impl FromStr for RV32IM {
Expand Down Expand Up @@ -537,6 +539,7 @@ impl RV32IM {
RV32IM::SLTIU |
RV32IM::VIRTUAL_MOVE |
RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT |
RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT |
RV32IM::VIRTUAL_MOVSIGN => RV32InstructionFormat::I,

RV32IM::LB |
Expand Down
6 changes: 4 additions & 2 deletions jolt-core/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ use crate::{
jolt::{
instruction::{
div::DIVInstruction, divu::DIVUInstruction, lb::LBInstruction, lbu::LBUInstruction,
lh::LHInstruction, lhu::LHUInstruction, mulh::MULHInstruction,
lh::LHInstruction, lhu::LHUInstruction, lw::LWInstruction, mulh::MULHInstruction,
mulhsu::MULHSUInstruction, rem::REMInstruction, remu::REMUInstruction,
sb::SBInstruction, sh::SHInstruction, VirtualInstructionSequence,
sb::SBInstruction, sh::SHInstruction, sw::SWInstruction, VirtualInstructionSequence,
},
vm::{bytecode::BytecodeRow, rv32i_vm::RV32I, JoltTraceStep},
},
Expand Down Expand Up @@ -197,8 +197,10 @@ impl Program {
tracer::RV32IM::DIVU => DIVUInstruction::<32>::virtual_trace(row),
tracer::RV32IM::REM => REMInstruction::<32>::virtual_trace(row),
tracer::RV32IM::REMU => REMUInstruction::<32>::virtual_trace(row),
tracer::RV32IM::SW => SWInstruction::<32>::virtual_trace(row),
tracer::RV32IM::SH => SHInstruction::<32>::virtual_trace(row),
tracer::RV32IM::SB => SBInstruction::<32>::virtual_trace(row),
tracer::RV32IM::LW => LWInstruction::<32>::virtual_trace(row),
tracer::RV32IM::LBU => LBUInstruction::<32>::virtual_trace(row),
tracer::RV32IM::LHU => LHUInstruction::<32>::virtual_trace(row),
tracer::RV32IM::LB => LBInstruction::<32>::virtual_trace(row),
Expand Down
134 changes: 134 additions & 0 deletions jolt-core/src/jolt/instruction/lw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use tracer::{ELFInstruction, MemoryState, RVTraceRow, RegisterState, RV32IM};

use super::VirtualInstructionSequence;
use crate::jolt::instruction::{
virtual_assert_aligned_memory_access::AssertAlignedMemoryAccessInstruction, JoltInstruction,
};
/// Loads a word from memory
pub struct LWInstruction<const WORD_SIZE: usize>;

impl<const WORD_SIZE: usize> VirtualInstructionSequence for LWInstruction<WORD_SIZE> {
const SEQUENCE_LENGTH: usize = 2;

fn virtual_trace(mut trace_row: RVTraceRow) -> Vec<RVTraceRow> {
assert_eq!(trace_row.instruction.opcode, RV32IM::LW);
// LW source registers
let rs1 = trace_row.instruction.rs1;
// LW operands
let rs1_val = trace_row.register_state.rs1_val.unwrap();
let offset = trace_row.instruction.imm.unwrap();

let mut virtual_trace = vec![];

let offset_unsigned = match WORD_SIZE {
32 => (offset & u32::MAX as i64) as u64,
64 => offset as u64,
_ => panic!("Unsupported WORD_SIZE: {}", WORD_SIZE),
};

let is_aligned =
AssertAlignedMemoryAccessInstruction::<WORD_SIZE, 4>(rs1_val, offset_unsigned)
.lookup_entry();
debug_assert_eq!(is_aligned, 1);
virtual_trace.push(RVTraceRow {
instruction: ELFInstruction {
address: trace_row.instruction.address,
opcode: RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT,
rs1,
rs2: None,
rd: None,
imm: Some(offset_unsigned as i64),
virtual_sequence_remaining: Some(Self::SEQUENCE_LENGTH - virtual_trace.len() - 1),
},
register_state: RegisterState {
rs1_val: Some(rs1_val),
rs2_val: None,
rd_post_val: None,
},
memory_state: None,
advice_value: None,
});

trace_row.instruction.virtual_sequence_remaining = Some(0);
virtual_trace.push(trace_row);

virtual_trace
}

fn sequence_output(_: u64, _: u64) -> u64 {
unimplemented!("")
}

fn virtual_sequence(instruction: ELFInstruction) -> Vec<ELFInstruction> {
let dummy_trace_row = RVTraceRow {
instruction,
register_state: RegisterState {
rs1_val: Some(0),
rs2_val: Some(0),
rd_post_val: Some(0),
},
memory_state: Some(MemoryState::Read {
address: 0,
value: 0,
}),
advice_value: None,
};
Self::virtual_trace(dummy_trace_row)
.into_iter()
.map(|trace_row| trace_row.instruction)
.collect()
}
}

#[cfg(test)]
mod test {
use ark_std::test_rng;
use rand_core::RngCore;

use super::*;

#[test]
fn lw_virtual_sequence_32() {
let mut rng = test_rng();
for _ in 0..256 {
let rs1 = rng.next_u64() % 32;
let rd = rng.next_u64() % 32;

let mut rs1_val = rng.next_u32() as u64;
let mut imm = rng.next_u64() as i64 % (1 << 12);

// Reroll rs1_val and imm until dest is aligned to a word
while (rs1_val as i64 + imm as i64) % 4 != 0 || (rs1_val as i64 + imm as i64) < 0 {
rs1_val = rng.next_u32() as u64;
imm = rng.next_u64() as i64 % (1 << 12);
}
let address = (rs1_val as i64 + imm as i64) as u64;
let word = rng.next_u32() as u64;

let lw_trace_row = RVTraceRow {
instruction: ELFInstruction {
address: rng.next_u64(),
opcode: RV32IM::LW,
rs1: Some(rs1),
rs2: None,
rd: Some(rd),
imm: Some(imm),
virtual_sequence_remaining: None,
},
register_state: RegisterState {
rs1_val: Some(rs1_val),
rs2_val: None,
rd_post_val: Some(word),
},
memory_state: Some(MemoryState::Read {
address,
value: word,
}),
advice_value: None,
};

let trace = LWInstruction::<32>::virtual_trace(lw_trace_row);
assert_eq!(trace.len(), LWInstruction::<32>::SEQUENCE_LENGTH);
}
}
}
2 changes: 2 additions & 0 deletions jolt-core/src/jolt/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub mod lb;
pub mod lbu;
pub mod lh;
pub mod lhu;
pub mod lw;
pub mod mul;
pub mod mulh;
pub mod mulhsu;
Expand All @@ -175,6 +176,7 @@ pub mod sltu;
pub mod sra;
pub mod srl;
pub mod sub;
pub mod sw;
pub mod virtual_advice;
pub mod virtual_assert_aligned_memory_access;
pub mod virtual_assert_lte;
Expand Down
138 changes: 138 additions & 0 deletions jolt-core/src/jolt/instruction/sw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use tracer::{ELFInstruction, MemoryState, RVTraceRow, RegisterState, RV32IM};

use super::VirtualInstructionSequence;
use crate::jolt::instruction::{
virtual_assert_aligned_memory_access::AssertAlignedMemoryAccessInstruction, JoltInstruction,
};
/// Stores a word to memory
pub struct SWInstruction<const WORD_SIZE: usize>;

impl<const WORD_SIZE: usize> VirtualInstructionSequence for SWInstruction<WORD_SIZE> {
const SEQUENCE_LENGTH: usize = 2;

fn virtual_trace(mut trace_row: RVTraceRow) -> Vec<RVTraceRow> {
assert_eq!(trace_row.instruction.opcode, RV32IM::SW);
// SW source registers
let rs1 = trace_row.instruction.rs1;
// SW operands
let dest = trace_row.register_state.rs1_val.unwrap();
let offset = trace_row.instruction.imm.unwrap();

let mut virtual_trace = vec![];

let offset_unsigned = match WORD_SIZE {
32 => (offset & u32::MAX as i64) as u64,
64 => offset as u64,
_ => panic!("Unsupported WORD_SIZE: {}", WORD_SIZE),
};

let is_aligned =
AssertAlignedMemoryAccessInstruction::<WORD_SIZE, 4>(dest, offset_unsigned)
.lookup_entry();
debug_assert_eq!(is_aligned, 1);
virtual_trace.push(RVTraceRow {
instruction: ELFInstruction {
address: trace_row.instruction.address,
opcode: RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT,
rs1,
rs2: None,
rd: None,
imm: Some(offset_unsigned as i64),
virtual_sequence_remaining: Some(Self::SEQUENCE_LENGTH - virtual_trace.len() - 1),
},
register_state: RegisterState {
rs1_val: Some(dest),
rs2_val: None,
rd_post_val: None,
},
memory_state: None,
advice_value: None,
});

trace_row.instruction.virtual_sequence_remaining = Some(0);
virtual_trace.push(trace_row);

virtual_trace
}

fn sequence_output(_: u64, _: u64) -> u64 {
unimplemented!("")
}

fn virtual_sequence(instruction: ELFInstruction) -> Vec<ELFInstruction> {
let dummy_trace_row = RVTraceRow {
instruction,
register_state: RegisterState {
rs1_val: Some(0),
rs2_val: Some(0),
rd_post_val: Some(0),
},
memory_state: Some(MemoryState::Read {
address: 0,
value: 0,
}),
advice_value: None,
};
Self::virtual_trace(dummy_trace_row)
.into_iter()
.map(|trace_row| trace_row.instruction)
.collect()
}
}

#[cfg(test)]
mod test {
use ark_std::test_rng;
use rand_core::RngCore;

use super::*;

#[test]
fn sw_virtual_sequence_32() {
let mut rng = test_rng();
for _ in 0..256 {
let rs1 = rng.next_u64() % 32;
let rs2 = rng.next_u64() % 32;

let mut rs1_val = rng.next_u32() as u64;
let mut imm = rng.next_u64() as i64 % (1 << 12);

// Reroll rs1_val and imm until dest is aligned to a word
while (rs1_val as i64 + imm as i64) % 4 != 0 || (rs1_val as i64 + imm as i64) < 0 {
rs1_val = rng.next_u32() as u64;
imm = rng.next_u64() as i64 % (1 << 12);
}

let dest = (rs1_val as i64 + imm as i64) as u64;

let word_before = rng.next_u32() as u64;
let word_after = rng.next_u32() as u64;

let sw_trace_row = RVTraceRow {
instruction: ELFInstruction {
address: rng.next_u64(),
opcode: RV32IM::SW,
rs1: Some(rs1),
rs2: Some(rs2),
rd: None,
imm: Some(imm),
virtual_sequence_remaining: None,
},
register_state: RegisterState {
rs1_val: Some(rs1_val),
rs2_val: Some(word_after),
rd_post_val: None,
},
memory_state: Some(MemoryState::Write {
address: dest,
pre_value: word_before,
post_value: word_after,
}),
advice_value: None,
};

let trace = SWInstruction::<32>::virtual_trace(sw_trace_row);
assert_eq!(trace.len(), SWInstruction::<32>::SEQUENCE_LENGTH);
}
}
}
8 changes: 2 additions & 6 deletions jolt-core/src/jolt/trace/rv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ impl TryFrom<&ELFInstruction> for RV32I {
RV32IM::VIRTUAL_ASSERT_VALID_SIGNED_REMAINDER => Ok(AssertValidSignedRemainderInstruction::default().into()),
RV32IM::VIRTUAL_ASSERT_VALID_DIV0 => Ok(AssertValidDiv0Instruction::default().into()),
RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT => Ok(AssertAlignedMemoryAccessInstruction::<32, 2>::default().into()),

RV32IM::LW => Ok(AssertAlignedMemoryAccessInstruction::<32, 4>::default().into()),
RV32IM::SW => Ok(AssertAlignedMemoryAccessInstruction::<32, 4>::default().into()),
RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT => Ok(AssertAlignedMemoryAccessInstruction::<32, 4>::default().into()),

_ => Err("No corresponding RV32I instruction")
}
Expand Down Expand Up @@ -136,9 +134,7 @@ impl TryFrom<&RVTraceRow> for RV32I {
RV32IM::VIRTUAL_ASSERT_VALID_SIGNED_REMAINDER => Ok(AssertValidSignedRemainderInstruction(row.register_state.rs1_val.unwrap(), row.register_state.rs2_val.unwrap()).into()),
RV32IM::VIRTUAL_ASSERT_VALID_DIV0 => Ok(AssertValidDiv0Instruction(row.register_state.rs1_val.unwrap(), row.register_state.rs2_val.unwrap()).into()),
RV32IM::VIRTUAL_ASSERT_HALFWORD_ALIGNMENT => Ok(AssertAlignedMemoryAccessInstruction::<32, 2>(row.register_state.rs1_val.unwrap(), row.imm_u32() as u64).into()),

RV32IM::LW => Ok(AssertAlignedMemoryAccessInstruction::<32, 4>(row.register_state.rs1_val.unwrap(), row.imm_u32() as u64).into()),
RV32IM::SW => Ok(AssertAlignedMemoryAccessInstruction::<32, 4>(row.register_state.rs1_val.unwrap(), row.imm_u32() as u64).into()),
RV32IM::VIRTUAL_ASSERT_WORD_ALIGNMENT => Ok(AssertAlignedMemoryAccessInstruction::<32, 4>(row.register_state.rs1_val.unwrap(), row.imm_u32() as u64).into()),

_ => Err("No corresponding RV32I instruction")
}
Expand Down
Loading

0 comments on commit 33a4c15

Please sign in to comment.