diff --git a/jolt-core/src/jolt/instruction/add.rs b/jolt-core/src/jolt/instruction/add.rs index 5b27e6a6c..86a74a678 100644 --- a/jolt-core/src/jolt/instruction/add.rs +++ b/jolt-core/src/jolt/instruction/add.rs @@ -59,12 +59,18 @@ impl JoltInstruction for ADDInstruction { } else if WORD_SIZE == 64 { self.0.overflowing_add(self.1).0 } else { - panic!("only implemented for u32 / u64") + panic!("ADD is only implemented for 32-bit or 64-bit word sizes") } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported") + } } } @@ -82,23 +88,26 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = ADDInstruction::<32>(x, y); + let instruction = ADDInstruction::(x, y); jolt_instruction_test!(instruction); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - ADDInstruction::<32>(100, 0), - ADDInstruction::<32>(0, 100), - ADDInstruction::<32>(1, 0), - ADDInstruction::<32>(0, u32_max), - ADDInstruction::<32>(u32_max, 0), - ADDInstruction::<32>(u32_max, u32_max), - ADDInstruction::<32>(u32_max, 1 << 8), - ADDInstruction::<32>(1 << 8, u32_max), + ADDInstruction::(100, 0), + ADDInstruction::(0, 100), + ADDInstruction::(1, 0), + ADDInstruction::(0, u32_max), + ADDInstruction::(u32_max, 0), + ADDInstruction::(u32_max, u32_max), + ADDInstruction::(u32_max, 1 << 8), + ADDInstruction::(1 << 8, u32_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); @@ -110,10 +119,32 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { - let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = ADDInstruction::<64>(x, y); + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = ADDInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + ADDInstruction::(100, 0), + ADDInstruction::(0, 100), + ADDInstruction::(1, 0), + ADDInstruction::(0, u64_max), + ADDInstruction::(u64_max, 0), + ADDInstruction::(u64_max, u64_max), + ADDInstruction::(u64_max, 1 << 32), + ADDInstruction::(1 << 32, u64_max), + ADDInstruction::(1 << 63, 1), + ADDInstruction::(1, 1 << 63), + ADDInstruction::(u64_max - 1, 1), + ADDInstruction::(1, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/and.rs b/jolt-core/src/jolt/instruction/and.rs index ed8a6f6f1..3d769725b 100644 --- a/jolt-core/src/jolt/instruction/and.rs +++ b/jolt-core/src/jolt/instruction/and.rs @@ -9,9 +9,9 @@ use crate::jolt::subtable::{and::AndSubtable, LassoSubtable}; use crate::utils::instruction_utils::{chunk_and_concatenate_operands, concatenate_lookups}; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct ANDInstruction(pub u64, pub u64); +pub struct ANDInstruction(pub u64, pub u64); -impl JoltInstruction for ANDInstruction { +impl JoltInstruction for ANDInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -37,11 +37,18 @@ impl JoltInstruction for ANDInstruction { } fn lookup_entry(&self) -> u64 { + // This is the same for 32-bit and 64-bit word sizes self.0 & self.1 } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported") + } } } @@ -60,10 +67,28 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = ANDInstruction(x, y); + let instruction = ANDInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u32_max: u64 = u32::MAX as u64; + let instructions = vec![ + ANDInstruction::(100, 0), + ANDInstruction::(0, 100), + ANDInstruction::(1, 0), + ANDInstruction::(0, u32_max), + ANDInstruction::(u32_max, 0), + ANDInstruction::(u32_max, u32_max), + ANDInstruction::(u32_max, 1 << 8), + ANDInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } @@ -73,25 +98,30 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = ANDInstruction(x, y); + let instruction = ANDInstruction::(x, y); jolt_instruction_test!(instruction); } - // Test edge-cases - let u32_max: u64 = u32::MAX as u64; + // Edge cases + let u64_max: u64 = u64::MAX; let instructions = vec![ - ANDInstruction(100, 0), - ANDInstruction(0, 100), - ANDInstruction(1, 0), - ANDInstruction(0, u32_max), - ANDInstruction(u32_max, 0), - ANDInstruction(u32_max, u32_max), - ANDInstruction(u32_max, 1 << 8), - ANDInstruction(1 << 8, u32_max), + ANDInstruction::(100, 0), + ANDInstruction::(0, 100), + ANDInstruction::(1, 0), + ANDInstruction::(0, u64_max), + ANDInstruction::(u64_max, 0), + ANDInstruction::(u64_max, u64_max), + ANDInstruction::(u64_max, 1 << 32), + ANDInstruction::(1 << 32, u64_max), + ANDInstruction::(1 << 63, 1), + ANDInstruction::(1, 1 << 63), + ANDInstruction::(u64_max - 1, 1), + ANDInstruction::(1, u64_max - 1), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/beq.rs b/jolt-core/src/jolt/instruction/beq.rs index e6cda119c..99d6bdcd8 100644 --- a/jolt-core/src/jolt/instruction/beq.rs +++ b/jolt-core/src/jolt/instruction/beq.rs @@ -13,9 +13,9 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct BEQInstruction(pub u64, pub u64); +pub struct BEQInstruction(pub u64, pub u64); -impl JoltInstruction for BEQInstruction { +impl JoltInstruction for BEQInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -41,11 +41,18 @@ impl JoltInstruction for BEQInstruction { } fn lookup_entry(&self) -> u64 { + // This is the same for both 32-bit and 64-bit (self.0 == self.1).into() } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported") + } } } @@ -64,25 +71,26 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = BEQInstruction(x, y); + let instruction = BEQInstruction::(x, y); jolt_instruction_test!(instruction); } // Test edge-cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - BEQInstruction(100, 0), - BEQInstruction(0, 100), - BEQInstruction(1, 0), - BEQInstruction(0, u32_max), - BEQInstruction(u32_max, 0), - BEQInstruction(u32_max, u32_max), - BEQInstruction(u32_max, 1 << 8), - BEQInstruction(1 << 8, u32_max), + BEQInstruction::(100, 0), + BEQInstruction::(0, 100), + BEQInstruction::(1, 0), + BEQInstruction::(0, u32_max), + BEQInstruction::(u32_max, 0), + BEQInstruction::(u32_max, u32_max), + BEQInstruction::(u32_max, 1 << 8), + BEQInstruction::(1 << 8, u32_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); @@ -94,10 +102,32 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = BEQInstruction(x, y); + let instruction = BEQInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Test edge-cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + BEQInstruction::(100, 0), + BEQInstruction::(0, 100), + BEQInstruction::(1, 0), + BEQInstruction::(0, u64_max), + BEQInstruction::(u64_max, 0), + BEQInstruction::(u64_max, u64_max), + BEQInstruction::(u64_max, 1 << 32), + BEQInstruction::(1 << 32, u64_max), + BEQInstruction::(1 << 63, 1), + BEQInstruction::(1, 1 << 63), + BEQInstruction::(u64_max - 1, u64_max), + BEQInstruction::(u64_max, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/bge.rs b/jolt-core/src/jolt/instruction/bge.rs index 381b4877b..b44339720 100644 --- a/jolt-core/src/jolt/instruction/bge.rs +++ b/jolt-core/src/jolt/instruction/bge.rs @@ -13,16 +13,16 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct BGEInstruction(pub u64, pub u64); +pub struct BGEInstruction(pub u64, pub u64); -impl JoltInstruction for BGEInstruction { +impl JoltInstruction for BGEInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } fn combine_lookups(&self, vals: &[F], C: usize, M: usize) -> F { - // 1 - LTS(x, y) = - F::one() - SLTInstruction(self.0, self.1).combine_lookups(vals, C, M) + // 1 - SLT(x, y) = + F::one() - SLTInstruction::(self.0, self.1).combine_lookups(vals, C, M) } fn g_poly_degree(&self, C: usize) -> usize { @@ -49,11 +49,27 @@ impl JoltInstruction for BGEInstruction { } fn lookup_entry(&self) -> u64 { - ((self.0 as i32) >= (self.1 as i32)).into() + if WORD_SIZE == 32 { + let x = self.0 as i32; + let y = self.1 as i32; + (x >= y) as u64 + } else if WORD_SIZE == 64 { + let x = self.0 as i64; + let y = self.1 as i64; + (x >= y) as u64 + } else { + panic!("BGE is only implemented for 32-bit or 64-bit word sizes") + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -68,17 +84,18 @@ mod test { use super::BGEInstruction; #[test] - fn bge_instruction_e2e() { + fn bge_instruction_32_e2e() { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; // Random for _ in 0..256 { let x = rng.next_u32(); let y = rng.next_u32(); - let instruction = BGEInstruction(x as u64, y as u64); + let instruction = BGEInstruction::(x as u64, y as u64); jolt_instruction_test!(instruction); } @@ -86,20 +103,65 @@ mod test { // Ones for _ in 0..256 { let x = rng.next_u32(); - jolt_instruction_test!(BGEInstruction(x as u64, x as u64)); + jolt_instruction_test!(BGEInstruction::(x as u64, x as u64)); } // Edge-cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - BGEInstruction(100, 0), - BGEInstruction(0, 100), - BGEInstruction(1, 0), - BGEInstruction(0, u32_max), - BGEInstruction(u32_max, 0), - BGEInstruction(u32_max, u32_max), - BGEInstruction(u32_max, 1 << 8), - BGEInstruction(1 << 8, u32_max), + BGEInstruction::(100, 0), + BGEInstruction::(0, 100), + BGEInstruction::(1, 0), + BGEInstruction::(0, u32_max), + BGEInstruction::(u32_max, 0), + BGEInstruction::(u32_max, u32_max), + BGEInstruction::(u32_max, 1 << 8), + BGEInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn bge_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let x = rng.next_u64(); + let y = rng.next_u64(); + let instruction = BGEInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Ones + for _ in 0..256 { + let x = rng.next_u64(); + jolt_instruction_test!(BGEInstruction::(x, x)); + } + + // Edge-cases + let i64_min = i64::MIN as u64; + let i64_max = i64::MAX as u64; + let instructions = vec![ + BGEInstruction::(100, 0), + BGEInstruction::(0, 100), + BGEInstruction::(1, 1), + BGEInstruction::(0, i64_max), + BGEInstruction::(i64_max, 0), + BGEInstruction::(i64_max, i64_max), + BGEInstruction::(i64_max, 1 << 32), + BGEInstruction::(1 << 32, i64_max), + BGEInstruction::(i64_min, 0), + BGEInstruction::(0, i64_min), + BGEInstruction::(i64_min, i64_max), + BGEInstruction::(i64_max, i64_min), + BGEInstruction::(-1i64 as u64, 0), + BGEInstruction::(0, -1i64 as u64), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/bgeu.rs b/jolt-core/src/jolt/instruction/bgeu.rs index cfa96a861..ffae8ed05 100644 --- a/jolt-core/src/jolt/instruction/bgeu.rs +++ b/jolt-core/src/jolt/instruction/bgeu.rs @@ -10,16 +10,16 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct BGEUInstruction(pub u64, pub u64); +pub struct BGEUInstruction(pub u64, pub u64); -impl JoltInstruction for BGEUInstruction { +impl JoltInstruction for BGEUInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } fn combine_lookups(&self, vals: &[F], C: usize, M: usize) -> F { - // 1 - LTU(x, y) = - F::one() - SLTUInstruction(self.0, self.1).combine_lookups(vals, C, M) + // 1 - SLTU(x, y) = + F::one() - SLTUInstruction::(self.0, self.1).combine_lookups(vals, C, M) } fn g_poly_degree(&self, C: usize) -> usize { @@ -42,11 +42,18 @@ impl JoltInstruction for BGEUInstruction { } fn lookup_entry(&self) -> u64 { + // This is the same for 32-bit and 64-bit (self.0 >= self.1).into() } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported") + } } } @@ -65,32 +72,33 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = BGEUInstruction(x, y); + let instruction = BGEUInstruction::(x, y); jolt_instruction_test!(instruction); } // Ones for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = BGEUInstruction(x, x); + let instruction = BGEUInstruction::(x, x); jolt_instruction_test!(instruction); } // Edge-cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - BGEUInstruction(100, 0), - BGEUInstruction(0, 100), - BGEUInstruction(1, 0), - BGEUInstruction(0, u32_max), - BGEUInstruction(u32_max, 0), - BGEUInstruction(u32_max, u32_max), - BGEUInstruction(u32_max, 1 << 8), - BGEUInstruction(1 << 8, u32_max), + BGEUInstruction::(100, 0), + BGEUInstruction::(0, 100), + BGEUInstruction::(1, 0), + BGEUInstruction::(0, u32_max), + BGEUInstruction::(u32_max, 0), + BGEUInstruction::(u32_max, u32_max), + BGEUInstruction::(u32_max, 1 << 8), + BGEUInstruction::(1 << 8, u32_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); @@ -102,15 +110,39 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = BGEUInstruction(x, y); + let instruction = BGEUInstruction::(x, y); jolt_instruction_test!(instruction); } + + // Ones for _ in 0..256 { let x = rng.next_u64(); - let instruction = BGEUInstruction(x, x); + let instruction = BGEUInstruction::(x, x); + jolt_instruction_test!(instruction); + } + + // Edge-cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + BGEUInstruction::(100, 0), + BGEUInstruction::(0, 100), + BGEUInstruction::(1, 0), + BGEUInstruction::(0, u64_max), + BGEUInstruction::(u64_max, 0), + BGEUInstruction::(u64_max, u64_max), + BGEUInstruction::(u64_max, 1 << 32), + BGEUInstruction::(1 << 32, u64_max), + BGEUInstruction::(1 << 63, 1), + BGEUInstruction::(1, 1 << 63), + BGEUInstruction::(u64_max - 1, u64_max), + BGEUInstruction::(u64_max, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/bne.rs b/jolt-core/src/jolt/instruction/bne.rs index 6f91a1db9..d0b37e65f 100644 --- a/jolt-core/src/jolt/instruction/bne.rs +++ b/jolt-core/src/jolt/instruction/bne.rs @@ -13,9 +13,9 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct BNEInstruction(pub u64, pub u64); +pub struct BNEInstruction(pub u64, pub u64); -impl JoltInstruction for BNEInstruction { +impl JoltInstruction for BNEInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -41,11 +41,18 @@ impl JoltInstruction for BNEInstruction { } fn lookup_entry(&self) -> u64 { + // This is the same for 32-bit and 64-bit word sizes (self.0 != self.1).into() } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -64,28 +71,33 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = BNEInstruction(x, y); + let instruction = BNEInstruction::(x, y); jolt_instruction_test!(instruction); } + + // x == y for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = BNEInstruction(x, x); + let instruction = BNEInstruction::(x, x); jolt_instruction_test!(instruction); } - let u32_max: u64 = u32::MAX as u64; + // Edge cases + let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - BNEInstruction(100, 0), - BNEInstruction(0, 100), - BNEInstruction(1, 0), - BNEInstruction(0, u32_max), - BNEInstruction(u32_max, 0), - BNEInstruction(u32_max, u32_max), - BNEInstruction(u32_max, 1 << 8), - BNEInstruction(1 << 8, u32_max), + BNEInstruction::(100, 0), + BNEInstruction::(0, 100), + BNEInstruction::(1, 0), + BNEInstruction::(0, u32_max), + BNEInstruction::(u32_max, 0), + BNEInstruction::(u32_max, u32_max), + BNEInstruction::(u32_max, 1 << 8), + BNEInstruction::(1 << 8, u32_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); @@ -97,15 +109,35 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = BNEInstruction(x, y); + let instruction = BNEInstruction::(x, y); jolt_instruction_test!(instruction); } + + // x == y for _ in 0..256 { let x = rng.next_u64(); - let instruction = BNEInstruction(x, x); + let instruction = BNEInstruction::(x, x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + BNEInstruction::(100, 0), + BNEInstruction::(0, 100), + BNEInstruction::(1, 0), + BNEInstruction::(0, u64_max), + BNEInstruction::(u64_max, 0), + BNEInstruction::(u64_max, u64_max), + BNEInstruction::(u64_max, 1 << 8), + BNEInstruction::(1 << 8, u64_max), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/div.rs b/jolt-core/src/jolt/instruction/div.rs index 2c05626d7..616f17d85 100644 --- a/jolt-core/src/jolt/instruction/div.rs +++ b/jolt-core/src/jolt/instruction/div.rs @@ -181,7 +181,7 @@ impl VirtualInstructionSequence for DIVInstruction(add_0, x).lookup_entry(); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { address: trace_row.instruction.address, diff --git a/jolt-core/src/jolt/instruction/divu.rs b/jolt-core/src/jolt/instruction/divu.rs index 4f231b56c..fbbcae950 100644 --- a/jolt-core/src/jolt/instruction/divu.rs +++ b/jolt-core/src/jolt/instruction/divu.rs @@ -102,7 +102,7 @@ impl VirtualInstructionSequence for DIVUInstruction(r, y).lookup_entry(); assert_eq!(is_valid, 1); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { @@ -123,7 +123,7 @@ impl VirtualInstructionSequence for DIVUInstruction(q_y, x).lookup_entry(); assert_eq!(lte, 1); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { @@ -185,7 +185,7 @@ impl VirtualInstructionSequence for DIVUInstruction(add_0, x).lookup_entry(); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { address: trace_row.instruction.address, diff --git a/jolt-core/src/jolt/instruction/lb.rs b/jolt-core/src/jolt/instruction/lb.rs index 7acb7e444..94393b629 100644 --- a/jolt-core/src/jolt/instruction/lb.rs +++ b/jolt-core/src/jolt/instruction/lb.rs @@ -11,14 +11,15 @@ use crate::jolt::subtable::{ use crate::utils::instruction_utils::chunk_operand_usize; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct LBInstruction(pub u64); +pub struct LBInstruction(pub u64); -impl JoltInstruction for LBInstruction { +impl JoltInstruction for LBInstruction { fn operands(&self) -> (u64, u64) { (0, self.0) } fn combine_lookups(&self, vals: &[F], C: usize, M: usize) -> F { + // result = byte + \sum_{i=1}^{C-1} 2^{8 * i} * sign_extension assert!(M >= 1 << 8); let byte = vals[0]; @@ -70,11 +71,23 @@ impl JoltInstruction for LBInstruction { fn lookup_entry(&self) -> u64 { // Sign-extend lower 8 bits of the loaded value - (self.0 & 0xff) as i8 as i32 as u32 as u64 + if WORD_SIZE == 32 { + (self.0 & 0xff) as i8 as i32 as u32 as u64 + } else if WORD_SIZE == 64 { + (self.0 & 0xff) as i8 as i64 as u64 + } else { + panic!("LB is only implemented for 32-bit or 64-bit word sizes"); + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -88,24 +101,57 @@ mod test { use crate::{jolt::instruction::JoltInstruction, jolt_instruction_test}; #[test] - fn lb_instruction_e2e() { + fn lb_instruction_32_e2e() { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = LBInstruction(x); + let instruction = LBInstruction::(x); jolt_instruction_test!(instruction); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - LBInstruction(0), - LBInstruction(1), - LBInstruction(100), - LBInstruction(u32_max), - LBInstruction(1 << 8), + LBInstruction::(0), + LBInstruction::(1), + LBInstruction::(100), + LBInstruction::(u32_max), + LBInstruction::(1 << 8), + LBInstruction::(u32_max - 100), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn lb_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let x = rng.next_u64(); + let instruction = LBInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + LBInstruction::(0), + LBInstruction::(1), + LBInstruction::(100), + LBInstruction::(u64_max), + LBInstruction::(1 << 8), + LBInstruction::(1 << 32 - 1), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/lh.rs b/jolt-core/src/jolt/instruction/lh.rs index 37b9e0969..df57695d7 100644 --- a/jolt-core/src/jolt/instruction/lh.rs +++ b/jolt-core/src/jolt/instruction/lh.rs @@ -10,21 +10,25 @@ use crate::jolt::subtable::{ use crate::utils::instruction_utils::chunk_operand_usize; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct LHInstruction(pub u64); +pub struct LHInstruction(pub u64); -impl JoltInstruction for LHInstruction { +impl JoltInstruction for LHInstruction { fn operands(&self) -> (u64, u64) { (0, self.0) } - fn combine_lookups(&self, vals: &[F], _C: usize, M: usize) -> F { + fn combine_lookups(&self, vals: &[F], C: usize, M: usize) -> F { // TODO(moodlezoup): make this work with different M assert!(M == 1 << 16); let half = vals[0]; let sign_extension = vals[1]; - half + F::from_u64(1 << 16).unwrap() * sign_extension + let mut result = half; + for i in 1..(C / 2) { + result += F::from_u64(1 << (16 * i)).unwrap() * sign_extension; + } + result } fn g_poly_degree(&self, _: usize) -> usize { @@ -65,11 +69,23 @@ impl JoltInstruction for LHInstruction { fn lookup_entry(&self) -> u64 { // Sign-extend lower 16 bits of the loaded value - (self.0 & 0xffff) as i16 as i32 as u32 as u64 + if WORD_SIZE == 32 { + (self.0 & 0xffff) as i16 as i32 as u32 as u64 + } else if WORD_SIZE == 64 { + (self.0 & 0xffff) as i16 as i64 as u64 + } else { + panic!("LH is only implemented for 32-bit or 64-bit word sizes"); + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -83,24 +99,60 @@ mod test { use crate::{jolt::instruction::JoltInstruction, jolt_instruction_test}; #[test] - fn lh_instruction_e2e() { + fn lh_instruction_32_e2e() { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = LHInstruction(x); + let instruction = LHInstruction::(x); jolt_instruction_test!(instruction); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - LHInstruction(0), - LHInstruction(1), - LHInstruction(100), - LHInstruction(u32_max), - LHInstruction(1 << 8), + LHInstruction::(0), + LHInstruction::(1), + LHInstruction::(100), + LHInstruction::(u32_max), + LHInstruction::(1 << 8), + LHInstruction::(1 << 16), + LHInstruction::(u32_max - 101), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn lh_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let x = rng.next_u64(); + let instruction = LHInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + LHInstruction::(0), + LHInstruction::(1), + LHInstruction::(100), + LHInstruction::(u64_max), + LHInstruction::(1 << 8), + LHInstruction::(1 << 16), + LHInstruction::(1 << 32), + LHInstruction::(u64_max - 10), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/mul.rs b/jolt-core/src/jolt/instruction/mul.rs index 7068d62e6..c824cafe5 100644 --- a/jolt-core/src/jolt/instruction/mul.rs +++ b/jolt-core/src/jolt/instruction/mul.rs @@ -62,12 +62,18 @@ impl JoltInstruction for MULInstruction { let y = self.1 as i64; x.wrapping_mul(y) as u64 } else { - panic!("only implemented for u32 / u64") + panic!("MUL is only implemented for 32-bit or 64-bit word sizes") } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -85,10 +91,29 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = MULInstruction::<32>(x, y); + let instruction = MULInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u32_max: u64 = u32::MAX as u64; + let instructions = vec![ + MULInstruction::(100, 0), + MULInstruction::(0, 100), + MULInstruction::(1, 0), + MULInstruction::(0, u32_max), + MULInstruction::(u32_max, 0), + MULInstruction::(2, u32_max), + MULInstruction::(u32_max, u32_max), + MULInstruction::(u32_max, 1 << 8), + MULInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } @@ -98,10 +123,32 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = MULInstruction::<64>(x, y); + let instruction = MULInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + MULInstruction::(100, 0), + MULInstruction::(0, 100), + MULInstruction::(1, 0), + MULInstruction::(0, u64_max), + MULInstruction::(u64_max, 0), + MULInstruction::(u64_max, u64_max), + MULInstruction::(u64_max, 1 << 32), + MULInstruction::(1 << 32, u64_max), + MULInstruction::(1 << 63, 1), + MULInstruction::(1, 1 << 63), + MULInstruction::(u64_max - 1, 1), + MULInstruction::(1, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/mulhu.rs b/jolt-core/src/jolt/instruction/mulhu.rs index cd611b43b..5e8827ce6 100644 --- a/jolt-core/src/jolt/instruction/mulhu.rs +++ b/jolt-core/src/jolt/instruction/mulhu.rs @@ -49,12 +49,18 @@ impl JoltInstruction for MULHUInstruction { } else if WORD_SIZE == 64 { ((self.0 as u128).wrapping_mul(self.1 as u128) >> 64) as u64 } else { - panic!("only implemented for u32 / u64") + panic!("MULHU is only implemented for 32-bit or 64-bit word sizes") } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -72,10 +78,27 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = MULHUInstruction::<32>(x, y); + let instruction = MULHUInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u32_max: u64 = u32::MAX as u64; + let instructions = vec![ + MULHUInstruction::(100, 0), + MULHUInstruction::(0, 100), + MULHUInstruction::(1, 0), + MULHUInstruction::(0, u32_max), + MULHUInstruction::(u32_max, 0), + MULHUInstruction::(u32_max, u32_max), + MULHUInstruction::(u32_max, 1 << 8), + MULHUInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } @@ -85,10 +108,32 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = MULHUInstruction::<64>(x, y); + let instruction = MULHUInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + MULHUInstruction::(100, 0), + MULHUInstruction::(0, 100), + MULHUInstruction::(1, 0), + MULHUInstruction::(0, u64_max), + MULHUInstruction::(u64_max, 0), + MULHUInstruction::(u64_max, u64_max), + MULHUInstruction::(u64_max, 1 << 32), + MULHUInstruction::(1 << 32, u64_max), + MULHUInstruction::(1 << 63, 1), + MULHUInstruction::(1, 1 << 63), + MULHUInstruction::(u64_max - 1, 1), + MULHUInstruction::(1, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/mulu.rs b/jolt-core/src/jolt/instruction/mulu.rs index 9a49c8cc0..9811e6052 100644 --- a/jolt-core/src/jolt/instruction/mulu.rs +++ b/jolt-core/src/jolt/instruction/mulu.rs @@ -58,12 +58,18 @@ impl JoltInstruction for MULUInstruction { } else if WORD_SIZE == 64 { self.0.wrapping_mul(self.1) } else { - panic!("only implemented for u32 / u64") + panic!("MULU is only implemented for 32-bit or 64-bit word sizes") } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -81,10 +87,28 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = MULUInstruction::<32>(x, y); + let instruction = MULUInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u32_max: u64 = u32::MAX as u64; + let instructions = vec![ + MULUInstruction::(100, 0), + MULUInstruction::(0, 100), + MULUInstruction::(1, 0), + MULUInstruction::(0, u32_max), + MULUInstruction::(u32_max, 0), + MULUInstruction::(u32_max, u32_max), + MULUInstruction::(u32_max, 1 << 8), + MULUInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } @@ -94,10 +118,32 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = MULUInstruction::<64>(x, y); + let instruction = MULUInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + MULUInstruction::(100, 0), + MULUInstruction::(0, 100), + MULUInstruction::(1, 0), + MULUInstruction::(0, u64_max), + MULUInstruction::(u64_max, 0), + MULUInstruction::(u64_max, u64_max), + MULUInstruction::(u64_max, 1 << 32), + MULUInstruction::(1 << 32, u64_max), + MULUInstruction::(1 << 63, 1), + MULUInstruction::(1, 1 << 63), + MULUInstruction::(u64_max - 1, 1), + MULUInstruction::(1, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/or.rs b/jolt-core/src/jolt/instruction/or.rs index 8883782c1..ad6e7dfc2 100644 --- a/jolt-core/src/jolt/instruction/or.rs +++ b/jolt-core/src/jolt/instruction/or.rs @@ -9,9 +9,9 @@ use crate::jolt::subtable::{or::OrSubtable, LassoSubtable}; use crate::utils::instruction_utils::{chunk_and_concatenate_operands, concatenate_lookups}; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct ORInstruction(pub u64, pub u64); +pub struct ORInstruction(pub u64, pub u64); -impl JoltInstruction for ORInstruction { +impl JoltInstruction for ORInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -37,11 +37,18 @@ impl JoltInstruction for ORInstruction { } fn lookup_entry(&self) -> u64 { + // This is the same for both 32-bit and 64-bit word sizes self.0 | self.1 } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -60,24 +67,25 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; for _ in 0..256 { let x = rng.next_u32() as u64; let y = rng.next_u32() as u64; - let instruction = ORInstruction(x, y); + let instruction = ORInstruction::(x, y); jolt_instruction_test!(instruction); } let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - ORInstruction(100, 0), - ORInstruction(0, 100), - ORInstruction(1, 0), - ORInstruction(0, u32_max), - ORInstruction(u32_max, 0), - ORInstruction(u32_max, u32_max), - ORInstruction(u32_max, 1 << 8), - ORInstruction(1 << 8, u32_max), + ORInstruction::(100, 0), + ORInstruction::(0, 100), + ORInstruction::(1, 0), + ORInstruction::(0, u32_max), + ORInstruction::(u32_max, 0), + ORInstruction::(u32_max, u32_max), + ORInstruction::(u32_max, 1 << 8), + ORInstruction::(1 << 8, u32_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); @@ -89,11 +97,30 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let x = rng.next_u64(); let y = rng.next_u64(); - let instruction = ORInstruction(x, y); + let instruction = ORInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + ORInstruction::(100, 0), + ORInstruction::(0, 100), + ORInstruction::(1, 0), + ORInstruction::(0, u64_max), + ORInstruction::(u64_max, 0), + ORInstruction::(u64_max, u64_max), + ORInstruction::(u64_max, 1 << 8), + ORInstruction::(1 << 8, u64_max), + ORInstruction::(u64_max, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/rem.rs b/jolt-core/src/jolt/instruction/rem.rs index dc9ab9b2d..8b6203421 100644 --- a/jolt-core/src/jolt/instruction/rem.rs +++ b/jolt-core/src/jolt/instruction/rem.rs @@ -161,7 +161,7 @@ impl VirtualInstructionSequence for REMInstruction(add_0, x).lookup_entry(); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { address: trace_row.instruction.address, diff --git a/jolt-core/src/jolt/instruction/remu.rs b/jolt-core/src/jolt/instruction/remu.rs index 8a5190670..23c0fd8a1 100644 --- a/jolt-core/src/jolt/instruction/remu.rs +++ b/jolt-core/src/jolt/instruction/remu.rs @@ -102,7 +102,7 @@ impl VirtualInstructionSequence for REMUInstruction(r, y).lookup_entry(); assert_eq!(is_valid, 1); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { @@ -123,7 +123,7 @@ impl VirtualInstructionSequence for REMUInstruction(q_y, x).lookup_entry(); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { address: trace_row.instruction.address, @@ -163,7 +163,7 @@ impl VirtualInstructionSequence for REMUInstruction(add_0, x).lookup_entry(); virtual_trace.push(RVTraceRow { instruction: ELFInstruction { address: trace_row.instruction.address, diff --git a/jolt-core/src/jolt/instruction/sb.rs b/jolt-core/src/jolt/instruction/sb.rs index f926c83a4..4ab59118b 100644 --- a/jolt-core/src/jolt/instruction/sb.rs +++ b/jolt-core/src/jolt/instruction/sb.rs @@ -9,9 +9,9 @@ use crate::jolt::subtable::{truncate_overflow::TruncateOverflowSubtable, LassoSu use crate::utils::instruction_utils::chunk_operand_usize; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct SBInstruction(pub u64); +pub struct SBInstruction(pub u64); -impl JoltInstruction for SBInstruction { +impl JoltInstruction for SBInstruction { fn operands(&self) -> (u64, u64) { (0, self.0) } @@ -53,12 +53,19 @@ impl JoltInstruction for SBInstruction { } fn lookup_entry(&self) -> u64 { - // Lower 8 bits of the rs2 value + // Lower 8 bits of the rs2 value, no sign extension + // Same for both 32-bit and 64-bit word sizes self.0 & 0xff } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -72,24 +79,56 @@ mod test { use crate::{jolt::instruction::JoltInstruction, jolt_instruction_test}; #[test] - fn sb_instruction_e2e() { + fn sb_instruction_32_e2e() { let mut rng = test_rng(); - const C: usize = 4; + const C: usize = 2; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = SBInstruction(x); + let instruction = SBInstruction::(x); jolt_instruction_test!(instruction); } let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SBInstruction(0), - SBInstruction(1), - SBInstruction(100), - SBInstruction(u32_max), - SBInstruction(1 << 8), + SBInstruction::(0), + SBInstruction::(1), + SBInstruction::(100), + SBInstruction::(1 << 8), + SBInstruction::(1 << 8 - 1), + SBInstruction::(u32_max), + SBInstruction::(u32_max - 1), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn sb_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 4; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + for _ in 0..256 { + let x = rng.next_u64(); + let instruction = SBInstruction::(x); + jolt_instruction_test!(instruction); + } + + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SBInstruction::(0), + SBInstruction::(1), + SBInstruction::(100), + SBInstruction::(1 << 8), + SBInstruction::(1 << 32), + SBInstruction::(1 << 40 - 10), + SBInstruction::(u64_max), + SBInstruction::(u64_max - 1), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/sh.rs b/jolt-core/src/jolt/instruction/sh.rs index c231af277..e11127b1a 100644 --- a/jolt-core/src/jolt/instruction/sh.rs +++ b/jolt-core/src/jolt/instruction/sh.rs @@ -8,9 +8,9 @@ use crate::jolt::subtable::{identity::IdentitySubtable, LassoSubtable}; use crate::utils::instruction_utils::chunk_operand_usize; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct SHInstruction(pub u64); +pub struct SHInstruction(pub u64); -impl JoltInstruction for SHInstruction { +impl JoltInstruction for SHInstruction { fn operands(&self) -> (u64, u64) { (0, self.0) } @@ -51,12 +51,19 @@ impl JoltInstruction for SHInstruction { } fn lookup_entry(&self) -> u64 { - // Lower 16 bits of the rs2 value + // Lower 16 bits of the rs2 value, no sign extension + // Same for both 32-bit and 64-bit word sizes self.0 & 0xffff } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -70,24 +77,54 @@ mod test { use crate::{jolt::instruction::JoltInstruction, jolt_instruction_test}; #[test] - fn sh_instruction_e2e() { + fn sh_instruction_32_e2e() { let mut rng = test_rng(); - const C: usize = 4; + const C: usize = 2; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = SHInstruction(x); + let instruction = SHInstruction::(x); jolt_instruction_test!(instruction); } let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SHInstruction(0), - SHInstruction(1), - SHInstruction(100), - SHInstruction(u32_max), - SHInstruction(1 << 8), + SHInstruction::(0), + SHInstruction::(1), + SHInstruction::(100), + SHInstruction::(1 << 8), + SHInstruction::(u32_max), + SHInstruction::(u32_max - 1), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn sh_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 4; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + for _ in 0..256 { + let x = rng.next_u64(); + let instruction = SHInstruction::(x); + jolt_instruction_test!(instruction); + } + + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SHInstruction::(0), + SHInstruction::(1), + SHInstruction::(100), + SHInstruction::(1 << 16), + SHInstruction::(1 << 48), + SHInstruction::(u64_max), + SHInstruction::(u64_max - 1), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/sll.rs b/jolt-core/src/jolt/instruction/sll.rs index ee0240bea..c74f38e84 100644 --- a/jolt-core/src/jolt/instruction/sll.rs +++ b/jolt-core/src/jolt/instruction/sll.rs @@ -60,15 +60,29 @@ impl JoltInstruction for SLLInstruction { } fn lookup_entry(&self) -> u64 { - // SLL is specified to ignore all but the last 5 bits of y: https://jemu.oscc.cc/SLL - (self.0 as u32) - .checked_shl(self.1 as u32 % WORD_SIZE as u32) - .unwrap_or(0) - .into() + // SLL is specified to ignore all but the last 5 (resp. 6) bits of y: https://jemu.oscc.cc/SLL + if WORD_SIZE == 32 { + (self.0 as u32) + .checked_shl(self.1 as u32 % WORD_SIZE as u32) + .unwrap_or(0) + .into() + } else if WORD_SIZE == 64 { + self.0 + .checked_shl((self.1 % WORD_SIZE as u64) as u32) + .unwrap_or(0) + } else { + panic!("SLL is only implemented for 32-bit or 64-bit word sizes") + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -89,22 +103,57 @@ mod test { const M: usize = 1 << 16; const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32(), rng.next_u32()); let instruction = SLLInstruction::(x as u64, y as u64); jolt_instruction_test!(instruction); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SLLInstruction::<32>(100, 0), - SLLInstruction::<32>(0, 100), - SLLInstruction::<32>(1, 0), - SLLInstruction::<32>(0, u32_max), - SLLInstruction::<32>(u32_max, 0), - SLLInstruction::<32>(u32_max, u32_max), - SLLInstruction::<32>(u32_max, 1 << 8), - SLLInstruction::<32>(1 << 8, u32_max), + SLLInstruction::(100, 0), + SLLInstruction::(0, 100), + SLLInstruction::(1, 0), + SLLInstruction::(0, u32_max), + SLLInstruction::(u32_max, 0), + SLLInstruction::(u32_max, u32_max), + SLLInstruction::(u32_max, 1 << 8), + SLLInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn sll_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = SLLInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SLLInstruction::(100, 0), + SLLInstruction::(0, 100), + SLLInstruction::(1, 0), + SLLInstruction::(0, u64_max), + SLLInstruction::(u64_max, 0), + SLLInstruction::(u64_max, u64_max), + SLLInstruction::(u64_max, 1 << 8), + SLLInstruction::(1 << 8, u64_max), + SLLInstruction::(u64_max, 1 << 63), + SLLInstruction::(1 << 63, 63), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/slt.rs b/jolt-core/src/jolt/instruction/slt.rs index 1a809f249..5506fa256 100644 --- a/jolt-core/src/jolt/instruction/slt.rs +++ b/jolt-core/src/jolt/instruction/slt.rs @@ -13,9 +13,9 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct SLTInstruction(pub u64, pub u64); +pub struct SLTInstruction(pub u64, pub u64); -impl JoltInstruction for SLTInstruction { +impl JoltInstruction for SLTInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -70,11 +70,27 @@ impl JoltInstruction for SLTInstruction { } fn lookup_entry(&self) -> u64 { - ((self.0 as i32) < (self.1 as i32)).into() + if WORD_SIZE == 32 { + let x = self.0 as i32; + let y = self.1 as i32; + (x < y) as u64 + } else if WORD_SIZE == 64 { + let x = self.0 as i64; + let y = self.1 as i64; + (x < y) as u64 + } else { + panic!("SLT is only implemented for 32-bit or 64-bit word sizes") + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -93,23 +109,59 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; for _ in 0..256 { let x = rng.next_u32() as u64; let y = rng.next_u32() as u64; - let instruction = SLTInstruction(x, y); + let instruction = SLTInstruction::(x, y); jolt_instruction_test!(instruction); } let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SLTInstruction(100, 0), - SLTInstruction(0, 100), - SLTInstruction(1, 0), - SLTInstruction(0, u32_max), - SLTInstruction(u32_max, 0), - SLTInstruction(u32_max, u32_max), - SLTInstruction(u32_max, 1 << 8), - SLTInstruction(1 << 8, u32_max), + SLTInstruction::<32>(100, 0), + SLTInstruction::<32>(0, 100), + SLTInstruction::<32>(1, 0), + SLTInstruction::<32>(0, u32_max), + SLTInstruction::<32>(u32_max, 0), + SLTInstruction::<32>(u32_max, u32_max), + SLTInstruction::<32>(u32_max, 1 << 8), + SLTInstruction::<32>(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn slt_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + for _ in 0..256 { + let x = rng.next_u64(); + let y = rng.next_u64(); + let instruction = SLTInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + let i64_min = i64::MIN as u64; + let i64_max = i64::MAX as u64; + let instructions = vec![ + SLTInstruction::<64>(100, 0), + SLTInstruction::<64>(0, 100), + SLTInstruction::<64>(1, 0), + SLTInstruction::<64>(0, i64_max), + SLTInstruction::<64>(i64_max, 0), + SLTInstruction::<64>(i64_max, i64_max), + SLTInstruction::<64>(i64_max, 1 << 32), + SLTInstruction::<64>(1 << 32, i64_max), + SLTInstruction::<64>(i64_min, 0), + SLTInstruction::<64>(0, i64_min), + SLTInstruction::<64>(i64_min, i64_max), + SLTInstruction::<64>(i64_max, i64_min), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/sltu.rs b/jolt-core/src/jolt/instruction/sltu.rs index 3dc45d0ca..5a44cef53 100644 --- a/jolt-core/src/jolt/instruction/sltu.rs +++ b/jolt-core/src/jolt/instruction/sltu.rs @@ -13,9 +13,9 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct SLTUInstruction(pub u64, pub u64); +pub struct SLTUInstruction(pub u64, pub u64); -impl JoltInstruction for SLTUInstruction { +impl JoltInstruction for SLTUInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -55,11 +55,18 @@ impl JoltInstruction for SLTUInstruction { } fn lookup_entry(&self) -> u64 { + // This is the same for 32-bit and 64-bit word sizes (self.0 < self.1).into() } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported") + } } } @@ -78,27 +85,71 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = SLTUInstruction(x, y); + let instruction = SLTUInstruction::(x, y); jolt_instruction_test!(instruction); } + + // x == y for _ in 0..256 { let x = rng.next_u32() as u64; - jolt_instruction_test!(SLTUInstruction(x, x)); + jolt_instruction_test!(SLTUInstruction::(x, x)); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SLTUInstruction(100, 0), - SLTUInstruction(0, 100), - SLTUInstruction(1, 0), - SLTUInstruction(0, u32_max), - SLTUInstruction(u32_max, 0), - SLTUInstruction(u32_max, u32_max), - SLTUInstruction(u32_max, 1 << 8), - SLTUInstruction(1 << 8, u32_max), + SLTUInstruction::(100, 0), + SLTUInstruction::(0, 100), + SLTUInstruction::(1, 0), + SLTUInstruction::(0, u32_max), + SLTUInstruction::(u32_max, 0), + SLTUInstruction::(u32_max, u32_max), + SLTUInstruction::(u32_max, 1 << 8), + SLTUInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn sltu_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = SLTUInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // x == y + for _ in 0..256 { + let x = rng.next_u64(); + jolt_instruction_test!(SLTUInstruction::(x, x)); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SLTUInstruction::(100, 0), + SLTUInstruction::(0, 100), + SLTUInstruction::(1, 0), + SLTUInstruction::(0, u64_max), + SLTUInstruction::(u64_max, 0), + SLTUInstruction::(u64_max, u64_max), + SLTUInstruction::(u64_max, 1 << 32), + SLTUInstruction::(1 << 32, u64_max), + SLTUInstruction::(1 << 63, 1 << 63 - 1), + SLTUInstruction::(1 << 63 - 1, 1 << 63), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/sra.rs b/jolt-core/src/jolt/instruction/sra.rs index 75047c43e..7bb8668db 100644 --- a/jolt-core/src/jolt/instruction/sra.rs +++ b/jolt-core/src/jolt/instruction/sra.rs @@ -30,6 +30,9 @@ impl JoltInstruction for SRAInstruction { C: usize, _: usize, ) -> Vec<(Box>, SubtableIndices)> { + // We have to pre-define subtables in this way because `CHUNK_INDEX` needs to be a constant, + // i.e. known at compile time (so we cannot do a `map` over the range of `C`, + // which only happens at runtime). let mut subtables: Vec>> = vec![ Box::new(SrlSubtable::::new()), Box::new(SrlSubtable::::new()), @@ -62,13 +65,27 @@ impl JoltInstruction for SRAInstruction { } fn lookup_entry(&self) -> u64 { - let x = self.0 as i32; - let y = self.1 as u32 % (WORD_SIZE as u32); - (x.checked_shr(y).unwrap_or(0) as u32).into() + if WORD_SIZE == 32 { + let x = self.0 as i32; + let y = self.1 as u32 % 32; + (x.wrapping_shr(y) as u32).into() + } else if WORD_SIZE == 64 { + let x = self.0 as i64; + let y = (self.1 % 64) as u32; + x.wrapping_shr(y) as u64 + } else { + panic!("SRA is only implemented for 32-bit or 64-bit word sizes") + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -89,24 +106,61 @@ mod test { const M: usize = 1 << 16; const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32(), rng.next_u32()); let instruction = SRAInstruction::(x as u64, y as u64); jolt_instruction_test!(instruction); } + + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SRAInstruction::<32>(100, 0), - SRAInstruction::<32>(0, 2), - SRAInstruction::<32>(1, 2), - SRAInstruction::<32>(0, 32), - SRAInstruction::<32>(u32_max, 0), - SRAInstruction::<32>(u32_max, 31), - SRAInstruction::<32>(u32_max, 1 << 8), - SRAInstruction::<32>(1 << 8, 1 << 16), + SRAInstruction::(100, 0), + SRAInstruction::(0, 2), + SRAInstruction::(1, 2), + SRAInstruction::(0, 32), + SRAInstruction::(u32_max, 0), + SRAInstruction::(u32_max, 31), + SRAInstruction::(u32_max, 1 << 8), + SRAInstruction::(1 << 8, 1 << 16), ]; for instruction in instructions { jolt_instruction_test!(instruction); } } + + #[test] + fn sra_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = SRAInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SRAInstruction::(100, 0), + SRAInstruction::(0, 2), + SRAInstruction::(1, 2), + SRAInstruction::(0, 64), + SRAInstruction::(u64_max, 0), + SRAInstruction::(u64_max, 63), + SRAInstruction::(u64_max, 1 << 8), + SRAInstruction::(1 << 32, 1 << 16), + SRAInstruction::(1 << 63, 1), + SRAInstruction::((1 << 63) - 1, 1), + ]; + + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } } diff --git a/jolt-core/src/jolt/instruction/srl.rs b/jolt-core/src/jolt/instruction/srl.rs index 67f987689..91d152673 100644 --- a/jolt-core/src/jolt/instruction/srl.rs +++ b/jolt-core/src/jolt/instruction/srl.rs @@ -30,6 +30,9 @@ impl JoltInstruction for SRLInstruction { C: usize, _: usize, ) -> Vec<(Box>, SubtableIndices)> { + // We have to pre-define subtables in this way because `CHUNK_INDEX` needs to be a constant, + // i.e. known at compile time (so we cannot do a `map` over the range of `C`, + // which only happens at runtime). let mut subtables: Vec>> = vec![ Box::new(SrlSubtable::::new()), Box::new(SrlSubtable::::new()), @@ -55,13 +58,27 @@ impl JoltInstruction for SRLInstruction { } fn lookup_entry(&self) -> u64 { - let x = self.0 as u32; - let y = (self.1 % WORD_SIZE as u64) as u32; - x.checked_shr(y).unwrap_or(0).into() + if WORD_SIZE == 32 { + let x = self.0 as u32; + let y = (self.1 % 32) as u32; + (x.wrapping_shr(y)).into() + } else if WORD_SIZE == 64 { + let x = self.0; + let y = (self.1 % 64) as u32; + x.wrapping_shr(y) + } else { + panic!("SRL is only implemented for 32-bit or 64-bit word sizes") + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -76,7 +93,7 @@ mod test { use super::SRLInstruction; #[test] - fn srl_instruction_e2e() { + fn srl_instruction_32_e2e() { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; @@ -102,4 +119,36 @@ mod test { jolt_instruction_test!(instruction); } } + + #[test] + fn srl_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + for _ in 0..256 { + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = SRLInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SRLInstruction::<64>(100, 0), + SRLInstruction::<64>(0, 2), + SRLInstruction::<64>(1, 2), + SRLInstruction::<64>(0, 64), + SRLInstruction::<64>(u64_max, 0), + SRLInstruction::<64>(u64_max, 63), + SRLInstruction::<64>(u64_max, 1 << 8), + SRLInstruction::<64>(1 << 32, 1 << 16), + SRLInstruction::<64>(1 << 63, 1), + SRLInstruction::<64>((1 << 63) - 1, 1), + ]; + + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } } diff --git a/jolt-core/src/jolt/instruction/sub.rs b/jolt-core/src/jolt/instruction/sub.rs index b5772536f..36fa187d4 100644 --- a/jolt-core/src/jolt/instruction/sub.rs +++ b/jolt-core/src/jolt/instruction/sub.rs @@ -59,11 +59,23 @@ impl JoltInstruction for SUBInstruction { } fn lookup_entry(&self) -> u64 { - (self.0 as u32).overflowing_sub(self.1 as u32).0.into() + if WORD_SIZE == 32 { + (self.0 as u32).overflowing_sub(self.1 as u32).0.into() + } else if WORD_SIZE == 64 { + self.0.overflowing_sub(self.1).0 + } else { + panic!("SUB is only implemented for 32-bit or 64-bit word sizes"); + } } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -78,28 +90,63 @@ mod test { use super::SUBInstruction; #[test] - fn sub_instruction_e2e() { + fn sub_instruction_32_e2e() { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32(), rng.next_u32()); let instruction = SUBInstruction::(x as u64, y as u64); jolt_instruction_test!(instruction); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SUBInstruction::<32>(100, 0), - SUBInstruction::<32>(0, 100), - SUBInstruction::<32>(1, 0), - SUBInstruction::<32>(0, u32_max), - SUBInstruction::<32>(u32_max, 0), - SUBInstruction::<32>(u32_max, u32_max), - SUBInstruction::<32>(u32_max, 1 << 8), - SUBInstruction::<32>(1 << 8, u32_max), + SUBInstruction::(100, 0), + SUBInstruction::(0, 100), + SUBInstruction::(1, 0), + SUBInstruction::(0, u32_max), + SUBInstruction::(u32_max, 0), + SUBInstruction::(u32_max, u32_max), + SUBInstruction::(u32_max, 1 << 8), + SUBInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn sub_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 4; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = SUBInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SUBInstruction::(100, 0), + SUBInstruction::(0, 100), + SUBInstruction::(1, 0), + SUBInstruction::(0, u64_max), + SUBInstruction::(u64_max, 0), + SUBInstruction::(u64_max, u64_max), + SUBInstruction::(u64_max, 1 << 8), + SUBInstruction::(1 << 8, u64_max), + SUBInstruction::(u64_max, 1 << 32), + SUBInstruction::(1 << 32, u64_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/sw.rs b/jolt-core/src/jolt/instruction/sw.rs index 50e723b14..f20815ab0 100644 --- a/jolt-core/src/jolt/instruction/sw.rs +++ b/jolt-core/src/jolt/instruction/sw.rs @@ -1,26 +1,25 @@ use crate::field::JoltField; -use ark_std::log2; use rand::prelude::StdRng; use rand::RngCore; use serde::{Deserialize, Serialize}; use super::{JoltInstruction, SubtableIndices}; use crate::jolt::subtable::{identity::IdentitySubtable, LassoSubtable}; -use crate::utils::instruction_utils::{chunk_operand_usize, concatenate_lookups}; +use crate::utils::instruction_utils::chunk_operand_usize; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct SWInstruction(pub u64); +pub struct SWInstruction(pub u64); -impl JoltInstruction for SWInstruction { +impl JoltInstruction for SWInstruction { fn operands(&self) -> (u64, u64) { (0, self.0) } - fn combine_lookups(&self, vals: &[F], _: usize, M: usize) -> F { + fn combine_lookups(&self, vals: &[F], _C: usize, M: usize) -> F { // TODO(moodlezoup): make this work with different M assert!(M == 1 << 16); - assert!(vals.len() == 2); - concatenate_lookups(vals, 2, log2(M) as usize) + // Only concatenate the first two lookup results + vals[0] * F::from_u64(M as u64).unwrap() + vals[1] } fn g_poly_degree(&self, _: usize) -> usize { @@ -35,10 +34,19 @@ impl JoltInstruction for SWInstruction { // This assertion ensures that we only need two IdentitySubtables // TODO(moodlezoup): make this work with different M assert!(M == 1 << 16); - vec![( - Box::new(IdentitySubtable::::new()), - SubtableIndices::from(C - 2..C), - )] + vec![ + ( + Box::new(IdentitySubtable::::new()), + SubtableIndices::from(C - 2..C), + ), + // quang : disabling this right now since it may add overhead for 32-bit word sizes + // ( + // // Not used for lookup, but this implicitly range-checks + // // the remaining query chunks (only relevant for 64-bit word sizes) + // Box::new(IdentitySubtable::::new()), + // SubtableIndices::from(0..C - 2), + // ), + ] } fn to_indices(&self, C: usize, log_M: usize) -> Vec { @@ -46,12 +54,19 @@ impl JoltInstruction for SWInstruction { } fn lookup_entry(&self) -> u64 { - // Lower 32 bits of the rs2 value + // Lower 32 bits of the rs2 value, no sign extension + // Same for both 32-bit and 64-bit word sizes self.0 & 0xffffffff } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -65,24 +80,60 @@ mod test { use crate::{jolt::instruction::JoltInstruction, jolt_instruction_test}; #[test] - fn sw_instruction_e2e() { + fn sw_instruction_32_e2e() { let mut rng = test_rng(); + // This works for any `C >= 2` const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = SWInstruction(x); + let instruction = SWInstruction::(x); jolt_instruction_test!(instruction); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - SWInstruction(0), - SWInstruction(1), - SWInstruction(100), - SWInstruction(u32_max), - SWInstruction(1 << 8), + SWInstruction::(0), + SWInstruction::(1), + SWInstruction::(100), + SWInstruction::(1 << 8), + SWInstruction::(u32_max), + SWInstruction::(u32_max - 2), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn sw_instruction_64_e2e() { + let mut rng = test_rng(); + // This works for any `C >= 2` + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let x = rng.next_u64(); + let instruction = SWInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + SWInstruction::(0), + SWInstruction::(1), + SWInstruction::(100), + SWInstruction::(1 << 8), + SWInstruction::(1 << 40), + SWInstruction::(u64_max), + SWInstruction::(u64_max - 1), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/virtual_advice.rs b/jolt-core/src/jolt/instruction/virtual_advice.rs index 47396f579..8b3a0bd14 100644 --- a/jolt-core/src/jolt/instruction/virtual_advice.rs +++ b/jolt-core/src/jolt/instruction/virtual_advice.rs @@ -48,11 +48,18 @@ impl JoltInstruction for ADVICEInstruction { } fn lookup_entry(&self) -> u64 { + // Same for both 32-bit and 64-bit word sizes self.0 } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -70,10 +77,59 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = ADVICEInstruction::<32>(x); + let instruction = ADVICEInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u32_max: u64 = u32::MAX as u64; + let instructions = vec![ + ADVICEInstruction::(0), + ADVICEInstruction::(1), + ADVICEInstruction::(100), + ADVICEInstruction::(1 << 8), + ADVICEInstruction::(1 << 16), + ADVICEInstruction::(u32_max), + ADVICEInstruction::(u32_max - 101), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn advice_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let x = rng.next_u64(); + let instruction = ADVICEInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + ADVICEInstruction::(0), + ADVICEInstruction::(1), + ADVICEInstruction::(100), + ADVICEInstruction::(1 << 8), + ADVICEInstruction::(1 << 16), + ADVICEInstruction::(1 << 32), + ADVICEInstruction::(1 << 48 + 2), + ADVICEInstruction::(u64_max), + ADVICEInstruction::(u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/virtual_assert_lte.rs b/jolt-core/src/jolt/instruction/virtual_assert_lte.rs index f8114c71c..837b1bc74 100644 --- a/jolt-core/src/jolt/instruction/virtual_assert_lte.rs +++ b/jolt-core/src/jolt/instruction/virtual_assert_lte.rs @@ -10,9 +10,9 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct ASSERTLTEInstruction(pub u64, pub u64); +pub struct ASSERTLTEInstruction(pub u64, pub u64); -impl JoltInstruction for ASSERTLTEInstruction { +impl JoltInstruction for ASSERTLTEInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -56,11 +56,18 @@ impl JoltInstruction for ASSERTLTEInstruction { } fn lookup_entry(&self) -> u64 { + // Same for both 32-bit and 64-bit word sizes (self.0 <= self.1).into() } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -75,17 +82,18 @@ mod test { use super::ASSERTLTEInstruction; #[test] - fn assert_lte_instruction_e2e() { + fn assert_lte_instruction_32_e2e() { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; // Random for _ in 0..256 { let x = rng.next_u32(); let y = rng.next_u32(); - let instruction = ASSERTLTEInstruction(x as u64, y as u64); + let instruction = ASSERTLTEInstruction::(x as u64, y as u64); jolt_instruction_test!(instruction); } @@ -93,20 +101,61 @@ mod test { // Ones for _ in 0..256 { let x = rng.next_u32(); - jolt_instruction_test!(ASSERTLTEInstruction(x as u64, x as u64)); + jolt_instruction_test!(ASSERTLTEInstruction::(x as u64, x as u64)); } // Edge-cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - ASSERTLTEInstruction(100, 0), - ASSERTLTEInstruction(0, 100), - ASSERTLTEInstruction(1, 0), - ASSERTLTEInstruction(0, u32_max), - ASSERTLTEInstruction(u32_max, 0), - ASSERTLTEInstruction(u32_max, u32_max), - ASSERTLTEInstruction(u32_max, 1 << 8), - ASSERTLTEInstruction(1 << 8, u32_max), + ASSERTLTEInstruction::(100, 0), + ASSERTLTEInstruction::(0, 100), + ASSERTLTEInstruction::(1, 0), + ASSERTLTEInstruction::(0, u32_max), + ASSERTLTEInstruction::(u32_max, 0), + ASSERTLTEInstruction::(u32_max, u32_max), + ASSERTLTEInstruction::(u32_max, 1 << 8), + ASSERTLTEInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn assert_lte_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let x = rng.next_u64(); + let y = rng.next_u64(); + + let instruction = ASSERTLTEInstruction::(x, y); + + jolt_instruction_test!(instruction); + } + + // Ones + for _ in 0..256 { + let x = rng.next_u64(); + jolt_instruction_test!(ASSERTLTEInstruction::(x, x)); + } + + // Edge-cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + ASSERTLTEInstruction::(100, 0), + ASSERTLTEInstruction::(0, 100), + ASSERTLTEInstruction::(1, 0), + ASSERTLTEInstruction::(0, u64_max), + ASSERTLTEInstruction::(u64_max, 0), + ASSERTLTEInstruction::(u64_max, u64_max), + ASSERTLTEInstruction::(u64_max, u64_max - 1), + ASSERTLTEInstruction::(u64_max, 1 << 8), + ASSERTLTEInstruction::(1 << 8, u64_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/virtual_assert_valid_div0.rs b/jolt-core/src/jolt/instruction/virtual_assert_valid_div0.rs index abd80663a..9518677be 100644 --- a/jolt-core/src/jolt/instruction/virtual_assert_valid_div0.rs +++ b/jolt-core/src/jolt/instruction/virtual_assert_valid_div0.rs @@ -69,7 +69,13 @@ impl JoltInstruction for AssertValidDiv0Instruction Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -88,27 +94,69 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = AssertValidDiv0Instruction::<32>(x, y); + let instruction = AssertValidDiv0Instruction::(x, y); jolt_instruction_test!(instruction); } + + // x == y for _ in 0..256 { let x = rng.next_u32() as u64; - jolt_instruction_test!(AssertValidDiv0Instruction::<32>(x, x)); + jolt_instruction_test!(AssertValidDiv0Instruction::(x, x)); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - AssertValidDiv0Instruction::<32>(100, 0), - AssertValidDiv0Instruction(0, 100), - AssertValidDiv0Instruction(1, 0), - AssertValidDiv0Instruction(0, u32_max), - AssertValidDiv0Instruction(u32_max, 0), - AssertValidDiv0Instruction(u32_max, u32_max), - AssertValidDiv0Instruction(u32_max, 1 << 8), - AssertValidDiv0Instruction(1 << 8, u32_max), + AssertValidDiv0Instruction::(100, 0), + AssertValidDiv0Instruction::(0, 100), + AssertValidDiv0Instruction::(1, 0), + AssertValidDiv0Instruction::(0, u32_max), + AssertValidDiv0Instruction::(u32_max, 0), + AssertValidDiv0Instruction::(u32_max, u32_max), + AssertValidDiv0Instruction::(u32_max, 1 << 8), + AssertValidDiv0Instruction::(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn assert_valid_div0_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = AssertValidDiv0Instruction::(x, y); + jolt_instruction_test!(instruction); + } + + // x == y + for _ in 0..256 { + let x = rng.next_u64(); + jolt_instruction_test!(AssertValidDiv0Instruction::(x, x)); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + AssertValidDiv0Instruction::(100, 0), + AssertValidDiv0Instruction::(0, 100), + AssertValidDiv0Instruction::(1, 0), + AssertValidDiv0Instruction::(0, u64_max), + AssertValidDiv0Instruction::(u64_max, 0), + AssertValidDiv0Instruction::(u64_max, u64_max), + AssertValidDiv0Instruction::(u64_max, 1 << 8), + AssertValidDiv0Instruction::(1 << 8, u64_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/virtual_assert_valid_signed_remainder.rs b/jolt-core/src/jolt/instruction/virtual_assert_valid_signed_remainder.rs index 9dd548b5c..69af6ea2d 100644 --- a/jolt-core/src/jolt/instruction/virtual_assert_valid_signed_remainder.rs +++ b/jolt-core/src/jolt/instruction/virtual_assert_valid_signed_remainder.rs @@ -117,7 +117,13 @@ impl JoltInstruction for AssertValidSignedRemainderInstr } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -136,23 +142,34 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let x = rng.next_u32() as u64; let y = rng.next_u32() as u64; - let instruction = AssertValidSignedRemainderInstruction::<32>(x, y); + let instruction = AssertValidSignedRemainderInstruction::(x, y); jolt_instruction_test!(instruction); } + + // x == y + for _ in 0..256 { + let x = rng.next_u32() as u64; + let instruction = AssertValidSignedRemainderInstruction::(x, x); + jolt_instruction_test!(instruction); + } + + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - AssertValidSignedRemainderInstruction::<32>(100, 0), - AssertValidSignedRemainderInstruction::<32>(0, 100), - AssertValidSignedRemainderInstruction::<32>(1, 0), - AssertValidSignedRemainderInstruction::<32>(0, u32_max), - AssertValidSignedRemainderInstruction::<32>(u32_max, 0), - AssertValidSignedRemainderInstruction::<32>(u32_max, u32_max), - AssertValidSignedRemainderInstruction::<32>(u32_max, 1 << 8), - AssertValidSignedRemainderInstruction::<32>(1 << 8, u32_max), + AssertValidSignedRemainderInstruction::(100, 0), + AssertValidSignedRemainderInstruction::(0, 100), + AssertValidSignedRemainderInstruction::(1, 0), + AssertValidSignedRemainderInstruction::(0, u32_max), + AssertValidSignedRemainderInstruction::(u32_max, 0), + AssertValidSignedRemainderInstruction::(u32_max, u32_max), + AssertValidSignedRemainderInstruction::(u32_max, 1 << 8), + AssertValidSignedRemainderInstruction::(1 << 8, u32_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); @@ -164,15 +181,37 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = AssertValidSignedRemainderInstruction::<64>(x, y); + let instruction = AssertValidSignedRemainderInstruction::(x, y); jolt_instruction_test!(instruction); } + + // x == y for _ in 0..256 { let x = rng.next_u64(); - let instruction = AssertValidSignedRemainderInstruction::<64>(x, x); + let instruction = AssertValidSignedRemainderInstruction::(x, x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + AssertValidSignedRemainderInstruction::(100, 0), + AssertValidSignedRemainderInstruction::(0, 100), + AssertValidSignedRemainderInstruction::(1, 0), + AssertValidSignedRemainderInstruction::(0, u64_max), + AssertValidSignedRemainderInstruction::(u64_max, 0), + AssertValidSignedRemainderInstruction::(u64_max, u64_max), + AssertValidSignedRemainderInstruction::(u64_max, 1 << 8), + AssertValidSignedRemainderInstruction::(1 << 8, u64_max), + AssertValidSignedRemainderInstruction::(u64_max, 1 << 40), + AssertValidSignedRemainderInstruction::(u64_max, u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/virtual_assert_valid_unsigned_remainder.rs b/jolt-core/src/jolt/instruction/virtual_assert_valid_unsigned_remainder.rs index 647ad84c8..f9b48154a 100644 --- a/jolt-core/src/jolt/instruction/virtual_assert_valid_unsigned_remainder.rs +++ b/jolt-core/src/jolt/instruction/virtual_assert_valid_unsigned_remainder.rs @@ -13,9 +13,11 @@ use crate::{ }; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct AssertValidUnsignedRemainderInstruction(pub u64, pub u64); +pub struct AssertValidUnsignedRemainderInstruction(pub u64, pub u64); -impl JoltInstruction for AssertValidUnsignedRemainderInstruction { +impl JoltInstruction + for AssertValidUnsignedRemainderInstruction +{ fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -61,13 +63,20 @@ impl JoltInstruction for AssertValidUnsignedRemainderInstruction { } fn lookup_entry(&self) -> u64 { + // Same for both 32-bit and 64-bit word sizes let remainder = self.0; let divisor = self.1; (divisor == 0 || remainder < divisor).into() } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -86,27 +95,72 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = AssertValidUnsignedRemainderInstruction(x, y); + let instruction = AssertValidUnsignedRemainderInstruction::(x, y); jolt_instruction_test!(instruction); } + + // x == y for _ in 0..256 { let x = rng.next_u32() as u64; - jolt_instruction_test!(AssertValidUnsignedRemainderInstruction(x, x)); + jolt_instruction_test!(AssertValidUnsignedRemainderInstruction::(x, x)); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - AssertValidUnsignedRemainderInstruction(100, 0), - AssertValidUnsignedRemainderInstruction(0, 100), - AssertValidUnsignedRemainderInstruction(1, 0), - AssertValidUnsignedRemainderInstruction(0, u32_max), - AssertValidUnsignedRemainderInstruction(u32_max, 0), - AssertValidUnsignedRemainderInstruction(u32_max, u32_max), - AssertValidUnsignedRemainderInstruction(u32_max, 1 << 8), - AssertValidUnsignedRemainderInstruction(1 << 8, u32_max), + AssertValidUnsignedRemainderInstruction::(100, 0), + AssertValidUnsignedRemainderInstruction::(0, 100), + AssertValidUnsignedRemainderInstruction::(1, 0), + AssertValidUnsignedRemainderInstruction::(0, u32_max), + AssertValidUnsignedRemainderInstruction::(u32_max, 0), + AssertValidUnsignedRemainderInstruction::(u32_max, u32_max), + AssertValidUnsignedRemainderInstruction::(u32_max, 1 << 8), + AssertValidUnsignedRemainderInstruction::(1 << 8, u32_max), + ]; + for instruction in instructions { + jolt_instruction_test!(instruction); + } + } + + #[test] + fn assert_valid_unsigned_remainder_instruction_64_e2e() { + let mut rng = test_rng(); + const C: usize = 8; + const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + + // Random + for _ in 0..256 { + let (x, y) = (rng.next_u64(), rng.next_u64()); + let instruction = AssertValidUnsignedRemainderInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // x == y + for _ in 0..256 { + let x = rng.next_u64(); + let instruction = AssertValidUnsignedRemainderInstruction::(x, x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + AssertValidUnsignedRemainderInstruction::(100, 0), + AssertValidUnsignedRemainderInstruction::(0, 100), + AssertValidUnsignedRemainderInstruction::(1, 0), + AssertValidUnsignedRemainderInstruction::(0, u64_max), + AssertValidUnsignedRemainderInstruction::(u64_max, 0), + AssertValidUnsignedRemainderInstruction::(u64_max, u64_max), + AssertValidUnsignedRemainderInstruction::(u64_max, 1 << 8), + AssertValidUnsignedRemainderInstruction::(1 << 8, u64_max), + AssertValidUnsignedRemainderInstruction::(u64_max, u64_max - 1), + AssertValidUnsignedRemainderInstruction::(u64_max - 1, u64_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); diff --git a/jolt-core/src/jolt/instruction/virtual_move.rs b/jolt-core/src/jolt/instruction/virtual_move.rs index 77be2ac9c..b0223375c 100644 --- a/jolt-core/src/jolt/instruction/virtual_move.rs +++ b/jolt-core/src/jolt/instruction/virtual_move.rs @@ -47,11 +47,18 @@ impl JoltInstruction for MOVEInstruction { } fn lookup_entry(&self) -> u64 { + // Same for both 32-bit and 64-bit word sizes self.0 } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -70,11 +77,26 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; // Random for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = MOVEInstruction::<32>(x); + let instruction = MOVEInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u32_max: u64 = u32::MAX as u64; + let instructions = vec![ + MOVEInstruction::(0), + MOVEInstruction::(1), + MOVEInstruction::(100), + MOVEInstruction::(1 << 16), + MOVEInstruction::(u32_max), + MOVEInstruction::(u32_max - 10), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } @@ -84,10 +106,28 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let x = rng.next_u64(); - let instruction = MOVEInstruction::<64>(x); + let instruction = MOVEInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + MOVEInstruction::(0), + MOVEInstruction::(1), + MOVEInstruction::(100), + MOVEInstruction::(1 << 16), + MOVEInstruction::(1 << 32), + MOVEInstruction::(1 << 48), + MOVEInstruction::(u64_max), + MOVEInstruction::(u64_max - 2), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/virtual_movsign.rs b/jolt-core/src/jolt/instruction/virtual_movsign.rs index 01a854b02..a8af36f3e 100644 --- a/jolt-core/src/jolt/instruction/virtual_movsign.rs +++ b/jolt-core/src/jolt/instruction/virtual_movsign.rs @@ -85,7 +85,13 @@ impl JoltInstruction for MOVSIGNInstruction { } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -95,7 +101,11 @@ mod test { use ark_std::test_rng; use rand_chacha::rand_core::RngCore; - use crate::{jolt::instruction::JoltInstruction, jolt_instruction_test}; + use crate::{ + jolt::instruction::virtual_movsign::{SIGN_BIT_32, SIGN_BIT_64}, + jolt::instruction::JoltInstruction, + jolt_instruction_test, + }; use super::MOVSIGNInstruction; @@ -104,11 +114,28 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; // Random for _ in 0..256 { let x = rng.next_u32() as u64; - let instruction = MOVSIGNInstruction::<32>(x); + let instruction = MOVSIGNInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u32_max: u64 = u32::MAX as u64; + let instructions = vec![ + MOVSIGNInstruction::(0), + MOVSIGNInstruction::(1), + MOVSIGNInstruction::(100), + MOVSIGNInstruction::(1 << 8), + MOVSIGNInstruction::(1 << 16), + MOVSIGNInstruction::(SIGN_BIT_32), + MOVSIGNInstruction::(u32_max), + MOVSIGNInstruction::(u32_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } @@ -118,10 +145,30 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let x = rng.next_u64(); - let instruction = MOVSIGNInstruction::<64>(x); + let instruction = MOVSIGNInstruction::(x); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + MOVSIGNInstruction::(0), + MOVSIGNInstruction::(1), + MOVSIGNInstruction::(100), + MOVSIGNInstruction::(1 << 8), + MOVSIGNInstruction::(1 << 16), + MOVSIGNInstruction::(1 << 32), + MOVSIGNInstruction::(SIGN_BIT_32), + MOVSIGNInstruction::(SIGN_BIT_64), + MOVSIGNInstruction::(u64_max), + MOVSIGNInstruction::(u64_max - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/instruction/xor.rs b/jolt-core/src/jolt/instruction/xor.rs index 2ba41d51e..4e9e70a75 100644 --- a/jolt-core/src/jolt/instruction/xor.rs +++ b/jolt-core/src/jolt/instruction/xor.rs @@ -10,9 +10,9 @@ use crate::jolt::subtable::{xor::XorSubtable, LassoSubtable}; use crate::utils::instruction_utils::{chunk_and_concatenate_operands, concatenate_lookups}; #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] -pub struct XORInstruction(pub u64, pub u64); +pub struct XORInstruction(pub u64, pub u64); -impl JoltInstruction for XORInstruction { +impl JoltInstruction for XORInstruction { fn operands(&self) -> (u64, u64) { (self.0, self.1) } @@ -38,11 +38,18 @@ impl JoltInstruction for XORInstruction { } fn lookup_entry(&self) -> u64 { + // This is the same for both 32-bit and 64-bit word sizes self.0 ^ self.1 } fn random(&self, rng: &mut StdRng) -> Self { - Self(rng.next_u32() as u64, rng.next_u32() as u64) + if WORD_SIZE == 32 { + Self(rng.next_u32() as u64, rng.next_u32() as u64) + } else if WORD_SIZE == 64 { + Self(rng.next_u64(), rng.next_u64()) + } else { + panic!("Only 32-bit and 64-bit word sizes are supported"); + } } } @@ -61,23 +68,26 @@ mod test { let mut rng = test_rng(); const C: usize = 4; const M: usize = 1 << 16; + const WORD_SIZE: usize = 32; + // Random for _ in 0..256 { let (x, y) = (rng.next_u32() as u64, rng.next_u32() as u64); - let instruction = XORInstruction(x, y); + let instruction = XORInstruction::(x, y); jolt_instruction_test!(instruction); } + // Edge cases let u32_max: u64 = u32::MAX as u64; let instructions = vec![ - XORInstruction(100, 0), - XORInstruction(0, 100), - XORInstruction(1, 0), - XORInstruction(0, u32_max), - XORInstruction(u32_max, 0), - XORInstruction(u32_max, u32_max), - XORInstruction(u32_max, 1 << 8), - XORInstruction(1 << 8, u32_max), + XORInstruction::(100, 0), + XORInstruction::(0, 100), + XORInstruction::(1, 0), + XORInstruction::(0, u32_max), + XORInstruction::(u32_max, 0), + XORInstruction::(u32_max, u32_max), + XORInstruction::(u32_max, 1 << 8), + XORInstruction::(1 << 8, u32_max), ]; for instruction in instructions { jolt_instruction_test!(instruction); @@ -89,10 +99,29 @@ mod test { let mut rng = test_rng(); const C: usize = 8; const M: usize = 1 << 16; + const WORD_SIZE: usize = 64; + // Random for _ in 0..256 { let (x, y) = (rng.next_u64(), rng.next_u64()); - let instruction = XORInstruction(x, y); + let instruction = XORInstruction::(x, y); + jolt_instruction_test!(instruction); + } + + // Edge cases + let u64_max: u64 = u64::MAX; + let instructions = vec![ + XORInstruction::(100, 0), + XORInstruction::(0, 100), + XORInstruction::(1, 0), + XORInstruction::(0, u64_max), + XORInstruction::(u64_max, 0), + XORInstruction::(u64_max, u64_max), + XORInstruction::(u64_max, 1 << 8), + XORInstruction::(1 << 8, u64_max), + XORInstruction::(u64_max, 1 << 32 - 1), + ]; + for instruction in instructions { jolt_instruction_test!(instruction); } } diff --git a/jolt-core/src/jolt/subtable/sll.rs b/jolt-core/src/jolt/subtable/sll.rs index 27ab69e52..94a38d0b6 100644 --- a/jolt-core/src/jolt/subtable/sll.rs +++ b/jolt-core/src/jolt/subtable/sll.rs @@ -26,7 +26,7 @@ impl LassoSubtab for SllSubtable { fn materialize(&self, M: usize) -> Vec { - // table[x | y] = (x << (y % WORD_SIZE)) % (1 << (WORD_SIZE - suffix_length)) + // table[x | y] = (x << (y % WORD_SIZE)) & ((1 << (WORD_SIZE - suffix_length)) - 1) // where `suffix_length = operand_chunk_width * CHUNK_INDEX` let mut entries: Vec = Vec::with_capacity(M); @@ -36,10 +36,14 @@ impl LassoSubtab for idx in 0..M { let (x, y) = split_bits(idx, operand_chunk_width); - let row = (x as u64) - .checked_shl((y % WORD_SIZE) as u32) - .unwrap_or(0) - .rem_euclid(1 << (WORD_SIZE - suffix_length)); + // Need to handle u64::MAX in a special case because of overflow + let truncate_mask = if WORD_SIZE - suffix_length >= 64 { + u64::MAX + } else { + (1 << (WORD_SIZE - suffix_length)) - 1 + }; + + let row = (x as u64).checked_shl((y % WORD_SIZE) as u32).unwrap_or(0) & truncate_mask; entries.push(F::from_u64(row).unwrap()); } @@ -108,16 +112,31 @@ mod test { subtable_materialize_mle_parity_test, }; - subtable_materialize_mle_parity_test!(sll_materialize_mle_parity0, SllSubtable, Fr, 1 << 10); - subtable_materialize_mle_parity_test!(sll_materialize_mle_parity1, SllSubtable, Fr, 1 << 10); - subtable_materialize_mle_parity_test!(sll_materialize_mle_parity2, SllSubtable, Fr, 1 << 10); - subtable_materialize_mle_parity_test!(sll_materialize_mle_parity3, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity0_32, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity1_32, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity2_32, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity3_32, SllSubtable, Fr, 1 << 10); + + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity0_64, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity1_64, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity2_64, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity3_64, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity4_64, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity5_64, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity6_64, SllSubtable, Fr, 1 << 10); + subtable_materialize_mle_parity_test!(sll_materialize_mle_parity7_64, SllSubtable, Fr, 1 << 10); - // This test is noticeably slow subtable_materialize_mle_parity_test!( - sll_binius_materialize_mle_parity3, + sll_binius_materialize_mle_parity3_32, SllSubtable, 3, 32>, BiniusField, - 1 << 16 + 1 << 10 + ); + + subtable_materialize_mle_parity_test!( + sll_binius_materialize_mle_parity3_64, + SllSubtable, 0, 64>, + BiniusField, + 1 << 10 ); } diff --git a/jolt-core/src/jolt/subtable/sra_sign.rs b/jolt-core/src/jolt/subtable/sra_sign.rs index b995403d0..f4c26e2c6 100644 --- a/jolt-core/src/jolt/subtable/sra_sign.rs +++ b/jolt-core/src/jolt/subtable/sra_sign.rs @@ -47,7 +47,7 @@ impl LassoSubtable for SraSignSubtable< } fn evaluate_mle(&self, point: &[F]) -> F { - // \sum_{k = 0}^{WORD_SIZE - 1} eq(y, bin(k)) * x_sign * \prod_{i = 0}^{k-1} 2^{WORD_SIZE - 1 - k}, + // \sum_{k = 0}^{WORD_SIZE - 1} eq(y, bin(k)) * x_sign * \prod_{j = 0}^{k-1} 2^{WORD_SIZE - j - 1}, // where x_sign = x_{b - 1 - (WORD_SIZE - 1) % b} // first half is chunk X_last diff --git a/jolt-core/src/jolt/vm/rv32i_vm.rs b/jolt-core/src/jolt/vm/rv32i_vm.rs index d70535e80..52fe46dda 100644 --- a/jolt-core/src/jolt/vm/rv32i_vm.rs +++ b/jolt-core/src/jolt/vm/rv32i_vm.rs @@ -98,20 +98,20 @@ instruction_set!( RV32I, ADD: ADDInstruction, SUB: SUBInstruction, - AND: ANDInstruction, - OR: ORInstruction, - XOR: XORInstruction, - LB: LBInstruction, - LH: LHInstruction, - SB: SBInstruction, - SH: SHInstruction, - SW: SWInstruction, - BEQ: BEQInstruction, - BGE: BGEInstruction, - BGEU: BGEUInstruction, - BNE: BNEInstruction, - SLT: SLTInstruction, - SLTU: SLTUInstruction, + AND: ANDInstruction, + OR: ORInstruction, + XOR: XORInstruction, + LB: LBInstruction, + LH: LHInstruction, + SB: SBInstruction, + SH: SHInstruction, + SW: SWInstruction, + BEQ: BEQInstruction, + BGE: BGEInstruction, + BGEU: BGEUInstruction, + BNE: BNEInstruction, + SLT: SLTInstruction, + SLTU: SLTUInstruction, SLL: SLLInstruction, SRA: SRAInstruction, SRL: SRLInstruction, @@ -121,9 +121,9 @@ instruction_set!( MULHU: MULHUInstruction, VIRTUAL_ADVICE: ADVICEInstruction, VIRTUAL_MOVE: MOVEInstruction, - VIRTUAL_ASSERT_LTE: ASSERTLTEInstruction, + VIRTUAL_ASSERT_LTE: ASSERTLTEInstruction, VIRTUAL_ASSERT_VALID_SIGNED_REMAINDER: AssertValidSignedRemainderInstruction, - VIRTUAL_ASSERT_VALID_UNSIGNED_REMAINDER: AssertValidUnsignedRemainderInstruction, + VIRTUAL_ASSERT_VALID_UNSIGNED_REMAINDER: AssertValidUnsignedRemainderInstruction, VIRTUAL_ASSERT_VALID_DIV0: AssertValidDiv0Instruction ); subtable_enum!( diff --git a/jolt-core/src/lasso/surge.rs b/jolt-core/src/lasso/surge.rs index 680d99c99..f3e05ac27 100644 --- a/jolt-core/src/lasso/surge.rs +++ b/jolt-core/src/lasso/surge.rs @@ -791,12 +791,14 @@ mod tests { use ark_bn254::{Fr, G1Projective}; #[test] - fn e2e() { + fn surge_32_e2e() { + const WORD_SIZE: usize = 32; + let ops = vec![ - XORInstruction(12, 12), - XORInstruction(12, 82), - XORInstruction(12, 12), - XORInstruction(25, 12), + XORInstruction::(12, 12), + XORInstruction::(12, 82), + XORInstruction::(12, 12), + XORInstruction::(25, 12), ]; const C: usize = 8; const M: usize = 1 << 8; @@ -804,15 +806,16 @@ mod tests { let mut transcript = ProofTranscript::new(b"test_transcript"); let preprocessing = SurgePreprocessing::preprocess(); let generators = PedersenGenerators::new( - SurgeProof::, XORInstruction, C, M>::num_generators(128), + SurgeProof::, XORInstruction, C, M>::num_generators(128), b"LassoV1", ); - let proof = SurgeProof::, XORInstruction, C, M>::prove( - &preprocessing, - &generators, - ops, - &mut transcript, - ); + let proof = + SurgeProof::, XORInstruction, C, M>::prove( + &preprocessing, + &generators, + ops, + &mut transcript, + ); let mut transcript = ProofTranscript::new(b"test_transcript"); SurgeProof::verify(&preprocessing, &generators, proof, &mut transcript) @@ -820,13 +823,15 @@ mod tests { } #[test] - fn e2e_non_pow_2() { + fn surge_32_e2e_non_pow_2() { + const WORD_SIZE: usize = 32; + let ops = vec![ - XORInstruction(0, 1), - XORInstruction(101, 101), - XORInstruction(202, 1), - XORInstruction(220, 1), - XORInstruction(220, 1), + XORInstruction::(0, 1), + XORInstruction::(101, 101), + XORInstruction::(202, 1), + XORInstruction::(220, 1), + XORInstruction::(220, 1), ]; const C: usize = 2; const M: usize = 1 << 8; @@ -834,15 +839,16 @@ mod tests { let mut transcript = ProofTranscript::new(b"test_transcript"); let preprocessing = SurgePreprocessing::preprocess(); let generators = PedersenGenerators::new( - SurgeProof::, XORInstruction, C, M>::num_generators(128), + SurgeProof::, XORInstruction, C, M>::num_generators(128), b"LassoV1", ); - let proof = SurgeProof::, XORInstruction, C, M>::prove( - &preprocessing, - &generators, - ops, - &mut transcript, - ); + let proof = + SurgeProof::, XORInstruction, C, M>::prove( + &preprocessing, + &generators, + ops, + &mut transcript, + ); let mut transcript = ProofTranscript::new(b"test_transcript"); SurgeProof::verify(&preprocessing, &generators, proof, &mut transcript) diff --git a/jolt-core/src/utils/instruction_utils.rs b/jolt-core/src/utils/instruction_utils.rs index f52c2b7d2..be7087594 100644 --- a/jolt-core/src/utils/instruction_utils.rs +++ b/jolt-core/src/utils/instruction_utils.rs @@ -5,11 +5,29 @@ pub fn assert_valid_parameters(word_size: usize, C: usize, log_M: usize) { assert!(C * log_M >= word_size); } -/// Concatenates a slice `vals` of `C` field elements (assumed to be `operand_bits`-bit numbers) -/// into a single field element. -/// `operand_bits` is the number of bits required to represent each element in `vals`. -/// If an element of `vals` is larger it will not be truncated, which -/// is commonly used by the collation functions of instructions. +/// Concatenates a slice of field elements into a single field element. +/// +/// # Arguments +/// +/// * `vals` - A slice of `C` field elements, each assumed to be an `operand_bits`-bit number. +/// * `C` - The number of field elements in `vals`. +/// * `operand_bits` - The number of bits required to represent each element in `vals`. +/// +/// # Notes +/// +/// If an element of `vals` is larger than `operand_bits`, it will not be truncated. +/// This behavior is commonly used by the collation functions of instructions. +/// +/// # Examples +/// +/// ``` +/// use jolt_core::utils::instruction_utils::concatenate_lookups; +/// use ark_bn254::Fr; +/// +/// let vals = vec![Fr::from(1), Fr::from(2), Fr::from(3)]; +/// let result = concatenate_lookups(&vals, 3, 2); +/// assert_eq!(result, Fr::from(0b01_10_11)); +/// ``` pub fn concatenate_lookups(vals: &[F], C: usize, operand_bits: usize) -> F { assert_eq!(vals.len(), C); @@ -78,13 +96,37 @@ pub fn chunk_operand_usize(x: u64, C: usize, chunk_len: usize) -> Vec { .collect() } -/// Chunks `x` || `y` into `C` chunks bitwise. +/// Chunks and concatenates two 64-bit unsigned integers `x` and `y` into a `C`-length vector of `usize`, +/// where each element represents a `log_M`-bit chunk that is the concatenation of `log_M / 2`-bit +/// chunks from each of `x` and `y`. /// -/// `log_M` is the number of bits of each of the `C` expected results, so we expect -/// `log_M = num_bits(x || y) / C`. +/// # Arguments +/// +/// * `x` - The first 64-bit unsigned integer to chunk and concatenate. +/// * `y` - The second 64-bit unsigned integer to chunk and concatenate. +/// * `C` - The number of chunks to produce. +/// * `log_M` - The number of bits in each resulting chunk. +/// +/// # Examples /// -/// Given the operation x_0, x_1, x_2, x_3 || y_0, y_1, y_2, y_3 with `C=2`, `log_M =4`, -/// chunks to `vec![x_0||x_1||y_0||y_1, x_2||x_3||y_2||y_3]`. +/// ``` +/// use jolt_core::utils::instruction_utils::chunk_and_concatenate_operands; +/// +/// // Normal usage +/// let x = 0b1100_1010; +/// let y = 0b1111_0000; +/// assert_eq!(chunk_and_concatenate_operands(x, y, 2, 8), vec![0b1100_1111, 0b1010_0000]); +/// +/// let x = 0b11_00_11; +/// let y = 0b00_11_00; +/// assert_eq!(chunk_and_concatenate_operands(x, y, 3, 4), vec![0b1100, 0b0011, 0b1100]); +/// +/// // More chunks than bits in x | y +/// assert_eq!(chunk_and_concatenate_operands(0b11, 0b11, 5, 2), vec![0, 0, 0, 3, 3]); +/// +/// // Fewer chunks than bits in x | y (remaining bits discarded) +/// assert_eq!(chunk_and_concatenate_operands(0xFF, 0xFF, 2, 4), vec![15, 15]); +/// ``` pub fn chunk_and_concatenate_operands(x: u64, y: u64, C: usize, log_M: usize) -> Vec { let operand_bits: usize = log_M / 2; @@ -112,6 +154,21 @@ pub fn chunk_and_concatenate_operands(x: u64, y: u64, C: usize, log_M: usize) -> /// Chunks `z` into `C` chunks bitwise where `z = x + y`. /// `log_M` is the number of bits for each of the `C` chunks of `z`. +/// +/// # Examples +/// +/// ``` +/// use jolt_core::utils::instruction_utils::add_and_chunk_operands; +/// +/// // Normal usage +/// assert_eq!(add_and_chunk_operands(12, 3, 2, 2), vec![0b11, 0b11]); +/// +/// // More chunks than bits in x | y +/// assert_eq!(add_and_chunk_operands(3, 4, 2, 2), vec![0b01, 0b11]); +/// +/// // Fewer chunks than bits in x | y (remaining bits discarded) +/// assert_eq!(add_and_chunk_operands(31, 31, 3, 2), vec![0b11, 0b11, 0b10]); +/// ``` pub fn add_and_chunk_operands(x: u128, y: u128, C: usize, log_M: usize) -> Vec { let sum_chunk_bits: usize = log_M; let sum_chunk_bit_mask: usize = (1 << sum_chunk_bits) - 1; @@ -127,6 +184,21 @@ pub fn add_and_chunk_operands(x: u128, y: u128, C: usize, log_M: usize) -> Vec Vec { let product_chunk_bits: usize = log_M; let product_chunk_bit_mask: usize = (1 << product_chunk_bits) - 1; @@ -139,8 +211,34 @@ pub fn multiply_and_chunk_operands(x: u128, y: u128, C: usize, log_M: usize) -> .collect() } -/// Splits `x`, `y` into `C` chunks and writes [ x_{C-1} || y_0, ..., x_0 || y_0 ] -/// where `x_{C-1}`` is the the big end of `x``, and `y_0`` is the small end of `y`. +/// Chunks and concatenates two 64-bit unsigned integers `x` and `y` into a vector of concatenated chunks, +/// where the second half of each concatenated chunk is always `y_0`, the last chunk of `y` (from left to right). +/// +/// # Arguments +/// +/// * `x` - The first 64-bit unsigned integer to chunk. +/// * `y` - The second 64-bit unsigned integer to chunk. +/// * `C` - The number of chunks to produce. +/// * `log_M` - The number of bits in each resulting concatenated chunk. +/// +/// # Result +/// [ x_{C-1} || y_0, ..., x_0 || y_0 ], +/// where `x_{C-1}` is the big end of `x`, and `y_0` is the small end of `y`. +/// +/// # Examples +/// +/// ``` +/// use jolt_core::utils::instruction_utils::chunk_and_concatenate_for_shift; +/// +/// // Normal usage +/// assert_eq!(chunk_and_concatenate_for_shift(0b1001, 0b0010, 2, 4), vec![0b1010, 0b0110]); +/// +/// // More chunks than bits in x | y +/// assert_eq!(chunk_and_concatenate_for_shift(0b10_11, 0b00_01, 3, 4), vec![0b00_01, 0b10_01, 0b11_01]); +/// +/// // Fewer chunks than bits in x | y (chunks larger than `C` discarded) +/// assert_eq!(chunk_and_concatenate_for_shift(0b10_01_11, 0b00_00_01, 2, 4), vec![0b01_01, 0b11_01]); +/// ``` pub fn chunk_and_concatenate_for_shift(x: u64, y: u64, C: usize, log_M: usize) -> Vec { let operand_bits: usize = log_M / 2; let operand_bit_mask: usize = (1 << operand_bits) - 1;