Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge NOT and POP flags. #1257

Merged
merged 9 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions evm/src/cpu/columns/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ pub struct OpsColumnsView<T: Copy> {
pub fp254_op: T, // Combines ADD_FP254, MUL_FP254 and SUB_FP254 flags.
pub eq_iszero: T, // Combines EQ and ISZERO flags.
pub logic_op: T, // Combines AND, OR and XOR flags.
pub not: T,
pub shift: T, // Combines SHL and SHR flags.
pub not_pop: T, // Combines NOT and POP flags.
pub shift: T, // Combines SHL and SHR flags.
pub keccak_general: T,
pub prover_input: T,
pub pop: T,
pub jumps: T, // Combines JUMP and JUMPI flags.
pub pc: T,
pub jumpdest: T,
Expand Down
5 changes: 2 additions & 3 deletions evm/src/cpu/control_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer
use crate::cpu::columns::{CpuColumnsView, COL_MAP};
use crate::cpu::kernel::aggregator::KERNEL;

const NATIVE_INSTRUCTIONS: [usize; 16] = [
const NATIVE_INSTRUCTIONS: [usize; 15] = [
COL_MAP.op.binary_op,
COL_MAP.op.ternary_op,
COL_MAP.op.fp254_op,
COL_MAP.op.eq_iszero,
COL_MAP.op.logic_op,
COL_MAP.op.not,
COL_MAP.op.not_pop,
COL_MAP.op.shift,
COL_MAP.op.keccak_general,
COL_MAP.op.prover_input,
COL_MAP.op.pop,
// not JUMPS (possible need to jump)
COL_MAP.op.pc,
COL_MAP.op.jumpdest,
Expand Down
29 changes: 25 additions & 4 deletions evm/src/cpu/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,17 @@ 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); 15] = [
const OPCODES: [(u8, usize, bool, usize); 13] = [
// (start index of block, number of top bits to check (log2), kernel-only, flag column)
// ADD, MUL, SUB, DIV, MOD, LT, GT and BYTE flags are handled partly manually here, and partly through the Arithmetic table CTL.
// ADDMOD, MULMOD and SUBMOD flags are handled partly manually here, and partly through the Arithmetic table CTL.
// FP254 operation flags are handled partly manually here, and partly through the Arithmetic table CTL.
(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),
// NOT and POP are handled manually here.
// SHL and SHR flags are handled partly manually here, and partly through the Logic table CTL.
(0x21, 0, true, COL_MAP.op.keccak_general),
(0x49, 0, true, COL_MAP.op.prover_input),
(0x50, 0, false, COL_MAP.op.pop),
(0x56, 1, false, COL_MAP.op.jumps), // 0x56-0x57
(0x58, 0, false, COL_MAP.op.pc),
(0x5b, 0, false, COL_MAP.op.jumpdest),
Expand All @@ -51,13 +50,14 @@ const OPCODES: [(u8, usize, bool, usize); 15] = [
/// List of combined opcodes requiring a special handling.
/// Each index in the list corresponds to an arbitrary combination
/// of opcodes defined in evm/src/cpu/columns/ops.rs.
const COMBINED_OPCODES: [usize; 6] = [
const COMBINED_OPCODES: [usize; 7] = [
COL_MAP.op.logic_op,
COL_MAP.op.fp254_op,
COL_MAP.op.binary_op,
COL_MAP.op.ternary_op,
COL_MAP.op.shift,
COL_MAP.op.m_op_general,
COL_MAP.op.not_pop,
];

/// Break up an opcode (which is 8 bits long) into its eight bits.
Expand Down Expand Up @@ -146,6 +146,14 @@ pub fn eval_packed_generic<P: PackedField>(
* (opcode - P::Scalar::from_canonical_usize(0xfc_usize))
* lv.op.m_op_general;
yield_constr.constraint(m_op_constr);

// Manually check lv.op.not_pop.
// Both NOT and POP can be called outside of the kernel mode:
// there is no need to constrain them in that regard.
let not_pop_op = (opcode - P::Scalar::from_canonical_usize(0x19_usize))
* (opcode - P::Scalar::from_canonical_usize(0x50_usize))
* lv.op.not_pop;
yield_constr.constraint(not_pop_op);
}

pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
Expand Down Expand Up @@ -248,4 +256,17 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
m_op_constr = builder.mul_extension(m_op_constr, lv.op.m_op_general);

yield_constr.constraint(builder, m_op_constr);

// Manually check lv.op.not_pop.
// Both NOT and POP can be called outside of the kernel mode:
// there is no need to constrain them in that regard.
let not_opcode = builder.constant_extension(F::Extension::from_canonical_usize(0x19_usize));
let pop_opcode = builder.constant_extension(F::Extension::from_canonical_usize(0x50_usize));

let not_constr = builder.sub_extension(opcode, not_opcode);
let pop_constr = builder.sub_extension(opcode, pop_opcode);

let mut not_pop_constr = builder.mul_extension(not_constr, pop_constr);
not_pop_constr = builder.mul_extension(lv.op.not_pop, not_pop_constr);
yield_constr.constraint(builder, not_pop_constr);
}
24 changes: 22 additions & 2 deletions evm/src/cpu/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ const SIMPLE_OPCODES: OpsColumnsView<Option<u32>> = OpsColumnsView {
fp254_op: KERNEL_ONLY_INSTR,
eq_iszero: G_VERYLOW,
logic_op: G_VERYLOW,
not: G_VERYLOW,
not_pop: None, // This is handled manually below
shift: G_VERYLOW,
keccak_general: KERNEL_ONLY_INSTR,
prover_input: KERNEL_ONLY_INSTR,
pop: G_BASE,
jumps: None, // Combined flag handled separately.
pc: G_BASE,
jumpdest: G_JUMPDEST,
Expand Down Expand Up @@ -104,6 +103,13 @@ fn eval_packed_accumulate<P: PackedField>(
let ternary_op_cost = P::Scalar::from_canonical_u32(G_MID.unwrap())
- lv.opcode_bits[1] * P::Scalar::from_canonical_u32(G_MID.unwrap());
yield_constr.constraint_transition(lv.op.ternary_op * (gas_diff - ternary_op_cost));

// For NOT and POP.
// NOT is differentiated from POP by its first bit set to 1.
let not_pop_cost = (P::ONES - lv.opcode_bits[0])
* P::Scalar::from_canonical_u32(G_BASE.unwrap())
+ lv.opcode_bits[0] * P::Scalar::from_canonical_u32(G_VERYLOW.unwrap());
yield_constr.constraint_transition(lv.op.not_pop * (gas_diff - not_pop_cost));
}

fn eval_packed_init<P: PackedField>(
Expand Down Expand Up @@ -232,6 +238,20 @@ fn eval_ext_circuit_accumulate<F: RichField + Extendable<D>, const D: usize>(
let gas_diff = builder.sub_extension(nv_lv_diff, ternary_op_cost);
let constr = builder.mul_extension(filter, gas_diff);
yield_constr.constraint_transition(builder, constr);

// For NOT and POP.
// NOT is differentiated from POP by its first bit set to 1.
let filter = lv.op.not_pop;
let one = builder.one_extension();
let mut not_pop_cost =
builder.mul_const_extension(F::from_canonical_u32(G_VERYLOW.unwrap()), lv.opcode_bits[0]);
let mut pop_cost = builder.sub_extension(one, lv.opcode_bits[0]);
pop_cost = builder.mul_const_extension(F::from_canonical_u32(G_BASE.unwrap()), pop_cost);
not_pop_cost = builder.add_extension(not_pop_cost, pop_cost);

let not_pop_gas_diff = builder.sub_extension(nv_lv_diff, not_pop_cost);
let not_pop_constr = builder.mul_extension(filter, not_pop_gas_diff);
yield_constr.constraint_transition(builder, not_pop_constr);
}

fn eval_ext_circuit_init<F: RichField + Extendable<D>, const D: usize>(
Expand Down
18 changes: 16 additions & 2 deletions evm/src/cpu/simple_logic/not.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use plonky2::iop::ext_target::ExtensionTarget;

use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::CpuColumnsView;
use crate::cpu::stack;

const LIMB_SIZE: usize = 32;
const ALL_1_LIMB: u64 = (1 << LIMB_SIZE) - 1;
Expand All @@ -18,12 +19,15 @@ pub fn eval_packed<P: PackedField>(
// This is simple: just do output = 0xffffffff - input.
let input = lv.mem_channels[0].value;
let output = nv.mem_channels[0].value;
let filter = lv.op.not;
let filter = lv.op.not_pop * lv.opcode_bits[0];
for (input_limb, output_limb) in input.into_iter().zip(output) {
yield_constr.constraint(
filter * (output_limb + input_limb - P::Scalar::from_canonical_u64(ALL_1_LIMB)),
);
}

// Stack constraints.
stack::eval_packed_one(lv, nv, filter, stack::BASIC_UNARY_OP.unwrap(), yield_constr);
}

pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
Expand All @@ -34,7 +38,7 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
) {
let input = lv.mem_channels[0].value;
let output = nv.mem_channels[0].value;
let filter = lv.op.not;
let filter = builder.mul_extension(lv.op.not_pop, lv.opcode_bits[0]);
for (input_limb, output_limb) in input.into_iter().zip(output) {
let constr = builder.add_extension(output_limb, input_limb);
let constr = builder.arithmetic_extension(
Expand All @@ -46,4 +50,14 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
);
yield_constr.constraint(builder, constr);
}

// Stack constraints.
stack::eval_ext_circuit_one(
builder,
lv,
nv,
filter,
stack::BASIC_UNARY_OP.unwrap(),
yield_constr,
);
}
140 changes: 129 additions & 11 deletions evm/src/cpu/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pub(crate) struct StackBehavior {
disable_other_channels: bool,
}

pub(crate) const BASIC_UNARY_OP: Option<StackBehavior> = Some(StackBehavior {
num_pops: 1,
pushes: true,
disable_other_channels: true,
});
const BASIC_BINARY_OP: Option<StackBehavior> = Some(StackBehavior {
num_pops: 2,
pushes: true,
Expand All @@ -30,6 +35,7 @@ const BASIC_TERNARY_OP: Option<StackBehavior> = Some(StackBehavior {
pushes: true,
disable_other_channels: true,
});

pub(crate) const JUMP_OP: Option<StackBehavior> = Some(StackBehavior {
num_pops: 1,
pushes: false,
Expand Down Expand Up @@ -58,11 +64,7 @@ pub(crate) const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsCol
fp254_op: BASIC_BINARY_OP,
eq_iszero: None, // EQ is binary, IS_ZERO is unary.
logic_op: BASIC_BINARY_OP,
not: Some(StackBehavior {
num_pops: 1,
pushes: true,
disable_other_channels: true,
}),
not_pop: None,
shift: Some(StackBehavior {
num_pops: 2,
pushes: true,
Expand All @@ -74,12 +76,7 @@ pub(crate) const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsCol
disable_other_channels: true,
}),
prover_input: None, // TODO
pop: Some(StackBehavior {
num_pops: 1,
pushes: false,
disable_other_channels: true,
}),
jumps: None, // Depends on whether it's a JUMP or a JUMPI.
jumps: None, // Depends on whether it's a JUMP or a JUMPI.
pc: Some(StackBehavior {
num_pops: 0,
pushes: true,
Expand Down Expand Up @@ -256,6 +253,50 @@ pub fn eval_packed<P: PackedField>(
eval_packed_one(lv, nv, op, stack_behavior, yield_constr);
}
}

// Stack constraints for POP.
// The only constraints POP has are stack constraints.
// Since POP and NOT are combined into one flag and they have
// different stack behaviors, POP needs special stack constraints.
// Constrain `stack_inv_aux`.
let len_diff = lv.stack_len - P::Scalar::ONES;
yield_constr.constraint(
lv.op.not_pop
* (len_diff * lv.general.stack().stack_inv - lv.general.stack().stack_inv_aux),
);

// If stack_len != 1 and POP, read new top of the stack in nv.mem_channels[0].
let top_read_channel = nv.mem_channels[0];
let is_top_read = lv.general.stack().stack_inv_aux * (P::ONES - lv.opcode_bits[0]);

// Constrain `stack_inv_aux_2`. It contains `stack_inv_aux * (1 - opcode_bits[0])`.
yield_constr.constraint(lv.op.not_pop * (lv.general.stack().stack_inv_aux_2 - is_top_read));
let new_filter = lv.op.not_pop * lv.general.stack().stack_inv_aux_2;
yield_constr.constraint_transition(new_filter * (top_read_channel.used - P::ONES));
yield_constr.constraint_transition(new_filter * (top_read_channel.is_read - P::ONES));
yield_constr.constraint_transition(new_filter * (top_read_channel.addr_context - nv.context));
yield_constr.constraint_transition(
new_filter
* (top_read_channel.addr_segment
- P::Scalar::from_canonical_u64(Segment::Stack as u64)),
);
let addr_virtual = nv.stack_len - P::ONES;
yield_constr.constraint_transition(new_filter * (top_read_channel.addr_virtual - addr_virtual));
// If stack_len == 1 or NOT, disable the channel.
hratoanina marked this conversation as resolved.
Show resolved Hide resolved
// If NOT or (len==1 and POP), then `stack_inv_aux_2` = 0.
yield_constr.constraint(
lv.op.not_pop * (lv.general.stack().stack_inv_aux_2 - P::ONES) * top_read_channel.used,
);

// Disable remaining memory channels.
for &channel in &lv.mem_channels[1..] {
yield_constr.constraint(lv.op.not_pop * (lv.opcode_bits[0] - P::ONES) * channel.used);
}
hratoanina marked this conversation as resolved.
Show resolved Hide resolved

// Constrain the new stack length for POP.
yield_constr.constraint_transition(
lv.op.not_pop * (lv.opcode_bits[0] - P::ONES) * (nv.stack_len - lv.stack_len + P::ONES),
);
}

pub(crate) fn eval_ext_circuit_one<F: RichField + Extendable<D>, const D: usize>(
Expand Down Expand Up @@ -473,4 +514,81 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
eval_ext_circuit_one(builder, lv, nv, op, stack_behavior, yield_constr);
}
}

// Stack constraints for POP.
// The only constraints POP has are stack constraints.
// Since POP and NOT are combined into one flag and they have
// different stack behaviors, POP needs special stack constraints.
// Constrain `stack_inv_aux`.
{
let len_diff = builder.add_const_extension(lv.stack_len, F::NEG_ONE);
let diff = builder.mul_sub_extension(
len_diff,
lv.general.stack().stack_inv,
lv.general.stack().stack_inv_aux,
);
let constr = builder.mul_extension(lv.op.not_pop, diff);
yield_constr.constraint(builder, constr);
}
// If stack_len != 4 and MSTORE, read new top of the stack in nv.mem_channels[0].
let top_read_channel = nv.mem_channels[0];
let is_top_read = builder.mul_extension(lv.general.stack().stack_inv_aux, lv.opcode_bits[0]);
let is_top_read = builder.sub_extension(lv.general.stack().stack_inv_aux, is_top_read);
// Constrain `stack_inv_aux_2`. It contains `stack_inv_aux * opcode_bits[0]`.
{
let diff = builder.sub_extension(lv.general.stack().stack_inv_aux_2, is_top_read);
let constr = builder.mul_extension(lv.op.not_pop, diff);
yield_constr.constraint(builder, constr);
}
let new_filter = builder.mul_extension(lv.op.not_pop, lv.general.stack().stack_inv_aux_2);
{
let constr = builder.mul_sub_extension(new_filter, top_read_channel.used, new_filter);
yield_constr.constraint_transition(builder, constr);
}
{
let constr = builder.mul_sub_extension(new_filter, top_read_channel.is_read, new_filter);
yield_constr.constraint_transition(builder, constr);
}
{
let diff = builder.sub_extension(top_read_channel.addr_context, nv.context);
let constr = builder.mul_extension(new_filter, diff);
yield_constr.constraint_transition(builder, constr);
}
{
let diff = builder.add_const_extension(
top_read_channel.addr_segment,
-F::from_canonical_u64(Segment::Stack as u64),
);
let constr = builder.mul_extension(new_filter, diff);
yield_constr.constraint_transition(builder, constr);
}
{
let addr_virtual = builder.add_const_extension(nv.stack_len, -F::ONE);
let diff = builder.sub_extension(top_read_channel.addr_virtual, addr_virtual);
let constr = builder.mul_extension(new_filter, diff);
yield_constr.constraint_transition(builder, constr);
}
// If stack_len == 1 or NOT, disable the channel.
{
let diff = builder.mul_sub_extension(
lv.op.not_pop,
lv.general.stack().stack_inv_aux_2,
lv.op.not_pop,
);
let constr = builder.mul_extension(diff, top_read_channel.used);
yield_constr.constraint(builder, constr);
}

// Disable remaining memory channels.
let filter = builder.mul_sub_extension(lv.op.not_pop, lv.opcode_bits[0], lv.op.not_pop);
for &channel in &lv.mem_channels[1..] {
let constr = builder.mul_extension(filter, channel.used);
yield_constr.constraint(builder, constr);
}

// Constrain the new stack length for POP.
let diff = builder.sub_extension(nv.stack_len, lv.stack_len);
let mut constr = builder.add_const_extension(diff, F::ONES);
constr = builder.mul_extension(filter, constr);
yield_constr.constraint_transition(builder, constr);
}
Loading
Loading