diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs index ecc5e83a..9f03d3e3 100644 --- a/evm/src/cpu/columns/ops.rs +++ b/evm/src/cpu/columns/ops.rs @@ -22,9 +22,8 @@ pub struct OpsColumnsView { pub submod: T, pub lt: T, pub gt: T, - pub eq: T, // Note: This column must be 0 when is_cpu_cycle = 0. - pub iszero: T, // Note: This column must be 0 when is_cpu_cycle = 0. - pub logic_op: T, // Combines AND, OR and XOR flags. + pub eq_iszero: T, // Combines EQ and ISZERO flags. + pub logic_op: T, // Combines AND, OR and XOR flags. pub not: T, pub byte: T, // TODO: combine SHL and SHR into one flag diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index eec2378b..e27b0710 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -8,7 +8,7 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cpu::columns::{CpuColumnsView, COL_MAP}; use crate::cpu::kernel::aggregator::KERNEL; -const NATIVE_INSTRUCTIONS: [usize; 30] = [ +const NATIVE_INSTRUCTIONS: [usize; 29] = [ COL_MAP.op.add, COL_MAP.op.mul, COL_MAP.op.sub, @@ -21,8 +21,7 @@ const NATIVE_INSTRUCTIONS: [usize; 30] = [ COL_MAP.op.subfp254, COL_MAP.op.lt, COL_MAP.op.gt, - COL_MAP.op.eq, - COL_MAP.op.iszero, + COL_MAP.op.eq_iszero, COL_MAP.op.logic_op, COL_MAP.op.not, COL_MAP.op.shl, diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index 88c44c7c..871da319 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -22,7 +22,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; /// behavior. /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to /// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid. -const OPCODES: [(u8, usize, bool, usize); 34] = [ +const OPCODES: [(u8, usize, bool, usize); 33] = [ // (start index of block, number of top bits to check (log2), kernel-only, flag column) (0x01, 0, false, COL_MAP.op.add), (0x02, 0, false, COL_MAP.op.mul), @@ -36,8 +36,7 @@ const OPCODES: [(u8, usize, bool, usize); 34] = [ (0x0e, 0, true, COL_MAP.op.subfp254), (0x10, 0, false, COL_MAP.op.lt), (0x11, 0, false, COL_MAP.op.gt), - (0x14, 0, false, COL_MAP.op.eq), - (0x15, 0, false, COL_MAP.op.iszero), + (0x14, 1, false, COL_MAP.op.eq_iszero), // AND, OR and XOR flags are handled partly manually here, and partly through the Logic table CTL. (0x19, 0, false, COL_MAP.op.not), (0x1a, 0, false, COL_MAP.op.byte), @@ -64,9 +63,8 @@ const OPCODES: [(u8, usize, bool, usize); 34] = [ pub fn generate(lv: &mut CpuColumnsView) { let cycle_filter = lv.is_cpu_cycle; if cycle_filter == F::ZERO { - // These columns cannot be shared. - lv.op.eq = F::ZERO; - lv.op.iszero = F::ZERO; + // This column cannot be shared. + lv.op.eq_iszero = F::ZERO; return; } // This assert is not _strictly_ necessary, but I include it as a sanity check. diff --git a/evm/src/cpu/gas.rs b/evm/src/cpu/gas.rs index a7235137..5a1393e8 100644 --- a/evm/src/cpu/gas.rs +++ b/evm/src/cpu/gas.rs @@ -31,8 +31,7 @@ const SIMPLE_OPCODES: OpsColumnsView> = OpsColumnsView { submod: KERNEL_ONLY_INSTR, lt: G_VERYLOW, gt: G_VERYLOW, - eq: G_VERYLOW, - iszero: G_VERYLOW, + eq_iszero: G_VERYLOW, logic_op: G_VERYLOW, not: G_VERYLOW, byte: G_VERYLOW, diff --git a/evm/src/cpu/simple_logic/eq_iszero.rs b/evm/src/cpu/simple_logic/eq_iszero.rs index 8a084c14..f16901f5 100644 --- a/evm/src/cpu/simple_logic/eq_iszero.rs +++ b/evm/src/cpu/simple_logic/eq_iszero.rs @@ -8,6 +8,7 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::CpuColumnsView; +use crate::cpu::stack::{self, EQ_STACK_BEHAVIOR, IS_ZERO_STACK_BEHAVIOR}; fn limbs(x: U256) -> [u32; 8] { let mut res = [0; 8]; @@ -57,9 +58,10 @@ pub fn eval_packed( let input1 = lv.mem_channels[1].value; let output = lv.mem_channels[2].value; - let eq_filter = lv.op.eq; - let iszero_filter = lv.op.iszero; - let eq_or_iszero_filter = eq_filter + iszero_filter; + // EQ (0x14) and ISZERO (0x15) are differentiated by their first opcode bit. + let eq_filter = lv.op.eq_iszero * (P::ONES - lv.opcode_bits[0]); + let iszero_filter = lv.op.eq_iszero * lv.opcode_bits[0]; + let eq_or_iszero_filter = lv.op.eq_iszero; let equal = output[0]; let unequal = P::ONES - equal; @@ -90,6 +92,15 @@ pub fn eval_packed( .map(|(limb0, limb1, diff_pinv_el)| (limb0 - limb1) * diff_pinv_el) .sum(); yield_constr.constraint(eq_or_iszero_filter * (dot - unequal)); + + // Stack constraints. + stack::eval_packed_one(lv, eq_filter, EQ_STACK_BEHAVIOR.unwrap(), yield_constr); + stack::eval_packed_one( + lv, + iszero_filter, + IS_ZERO_STACK_BEHAVIOR.unwrap(), + yield_constr, + ); } pub fn eval_ext_circuit, const D: usize>( @@ -105,9 +116,12 @@ pub fn eval_ext_circuit, const D: usize>( let input1 = lv.mem_channels[1].value; let output = lv.mem_channels[2].value; - let eq_filter = lv.op.eq; - let iszero_filter = lv.op.iszero; - let eq_or_iszero_filter = builder.add_extension(eq_filter, iszero_filter); + // EQ (0x14) and ISZERO (0x15) are differentiated by their first opcode bit. + let eq_filter = builder.mul_extension(lv.op.eq_iszero, lv.opcode_bits[0]); + let eq_filter = builder.sub_extension(lv.op.eq_iszero, eq_filter); + + let iszero_filter = builder.mul_extension(lv.op.eq_iszero, lv.opcode_bits[0]); + let eq_or_iszero_filter = lv.op.eq_iszero; let equal = output[0]; let unequal = builder.sub_extension(one, equal); @@ -154,4 +168,20 @@ pub fn eval_ext_circuit, const D: usize>( let constr = builder.mul_extension(eq_or_iszero_filter, constr); yield_constr.constraint(builder, constr); } + + // Stack constraints. + stack::eval_ext_circuit_one( + builder, + lv, + eq_filter, + EQ_STACK_BEHAVIOR.unwrap(), + yield_constr, + ); + stack::eval_ext_circuit_one( + builder, + lv, + iszero_filter, + IS_ZERO_STACK_BEHAVIOR.unwrap(), + yield_constr, + ); } diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index ef290d0a..4a5509da 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -12,7 +12,7 @@ use crate::cpu::membus::NUM_GP_CHANNELS; use crate::memory::segments::Segment; #[derive(Clone, Copy)] -struct StackBehavior { +pub(crate) struct StackBehavior { num_pops: usize, pushes: bool, disable_other_channels: bool, @@ -53,8 +53,7 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { submod: BASIC_TERNARY_OP, lt: BASIC_BINARY_OP, gt: BASIC_BINARY_OP, - eq: BASIC_BINARY_OP, - iszero: BASIC_UNARY_OP, + eq_iszero: None, // EQ is binary, IS_ZERO is unary. logic_op: BASIC_BINARY_OP, not: BASIC_UNARY_OP, byte: BASIC_BINARY_OP, @@ -140,7 +139,10 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { }), }; -fn eval_packed_one( +pub(crate) const EQ_STACK_BEHAVIOR: Option = BASIC_BINARY_OP; +pub(crate) const IS_ZERO_STACK_BEHAVIOR: Option = BASIC_UNARY_OP; + +pub(crate) fn eval_packed_one( lv: &CpuColumnsView

, filter: P, stack_behavior: StackBehavior, @@ -201,7 +203,7 @@ pub fn eval_packed( } } -fn eval_ext_circuit_one, const D: usize>( +pub(crate) fn eval_ext_circuit_one, const D: usize>( builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, lv: &CpuColumnsView>, filter: ExtensionTarget, diff --git a/evm/src/witness/transition.rs b/evm/src/witness/transition.rs index b8929114..d5e1b848 100644 --- a/evm/src/witness/transition.rs +++ b/evm/src/witness/transition.rs @@ -156,10 +156,9 @@ fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) { Operation::Push(1..) => &mut flags.push, Operation::Dup(_) => &mut flags.dup, Operation::Swap(_) => &mut flags.swap, - Operation::Iszero => &mut flags.iszero, + Operation::Iszero | Operation::Eq => &mut flags.eq_iszero, Operation::Not => &mut flags.not, Operation::Syscall(_, _, _) => &mut flags.syscall, - Operation::Eq => &mut flags.eq, Operation::BinaryLogic(_) => &mut flags.logic_op, Operation::BinaryArithmetic(arithmetic::BinaryOperator::Add) => &mut flags.add, Operation::BinaryArithmetic(arithmetic::BinaryOperator::Mul) => &mut flags.mul,