2023-10-11 22:28:49 +02:00
|
|
|
use std::cmp::max;
|
|
|
|
|
|
2022-09-28 15:18:56 -07:00
|
|
|
use itertools::izip;
|
|
|
|
|
use plonky2::field::extension::Extendable;
|
|
|
|
|
use plonky2::field::packed::PackedField;
|
|
|
|
|
use plonky2::field::types::Field;
|
|
|
|
|
use plonky2::hash::hash_types::RichField;
|
|
|
|
|
use plonky2::iop::ext_target::ExtensionTarget;
|
|
|
|
|
|
|
|
|
|
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
|
|
|
|
use crate::cpu::columns::ops::OpsColumnsView;
|
|
|
|
|
use crate::cpu::columns::CpuColumnsView;
|
|
|
|
|
use crate::cpu::membus::NUM_GP_CHANNELS;
|
|
|
|
|
use crate::memory::segments::Segment;
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy)]
|
2023-08-12 11:08:01 -04:00
|
|
|
pub(crate) struct StackBehavior {
|
2023-10-11 22:28:49 +02:00
|
|
|
pub(crate) num_pops: usize,
|
|
|
|
|
pub(crate) pushes: bool,
|
2022-09-28 15:18:56 -07:00
|
|
|
disable_other_channels: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BASIC_BINARY_OP: Option<StackBehavior> = Some(StackBehavior {
|
|
|
|
|
num_pops: 2,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
});
|
|
|
|
|
const BASIC_TERNARY_OP: Option<StackBehavior> = Some(StackBehavior {
|
2023-04-12 17:35:32 +02:00
|
|
|
num_pops: 3,
|
2022-09-28 15:18:56 -07:00
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
});
|
2023-07-20 16:46:31 -04:00
|
|
|
pub(crate) const JUMP_OP: Option<StackBehavior> = Some(StackBehavior {
|
|
|
|
|
num_pops: 1,
|
|
|
|
|
pushes: false,
|
|
|
|
|
disable_other_channels: false,
|
|
|
|
|
});
|
|
|
|
|
pub(crate) const JUMPI_OP: Option<StackBehavior> = Some(StackBehavior {
|
|
|
|
|
num_pops: 2,
|
|
|
|
|
pushes: false,
|
|
|
|
|
disable_other_channels: false,
|
|
|
|
|
});
|
2022-09-28 15:18:56 -07:00
|
|
|
|
2023-09-15 00:51:02 +01:00
|
|
|
pub(crate) const MLOAD_GENERAL_OP: Option<StackBehavior> = Some(StackBehavior {
|
|
|
|
|
num_pops: 3,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: false,
|
|
|
|
|
});
|
|
|
|
|
|
2022-10-03 12:31:43 -07:00
|
|
|
// AUDITORS: If the value below is `None`, then the operation must be manually checked to ensure
|
|
|
|
|
// that every general-purpose memory channel is either disabled or has its read flag and address
|
|
|
|
|
// propertly constrained. The same applies when `disable_other_channels` is set to `false`,
|
|
|
|
|
// except the first `num_pops` and the last `pushes as usize` channels have their read flag and
|
|
|
|
|
// address constrained automatically in this file.
|
2023-10-11 22:28:49 +02:00
|
|
|
pub(crate) const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsColumnsView {
|
2023-09-14 10:36:48 -04:00
|
|
|
binary_op: BASIC_BINARY_OP,
|
|
|
|
|
ternary_op: BASIC_TERNARY_OP,
|
|
|
|
|
fp254_op: BASIC_BINARY_OP,
|
2023-08-12 11:08:01 -04:00
|
|
|
eq_iszero: None, // EQ is binary, IS_ZERO is unary.
|
2023-08-11 10:17:45 -04:00
|
|
|
logic_op: BASIC_BINARY_OP,
|
2023-10-11 22:28:49 +02:00
|
|
|
not: Some(StackBehavior {
|
|
|
|
|
num_pops: 1,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
}),
|
2023-09-14 10:36:48 -04:00
|
|
|
shift: Some(StackBehavior {
|
2023-04-12 17:35:32 +02:00
|
|
|
num_pops: 2,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: false,
|
|
|
|
|
}),
|
2023-07-30 10:43:26 -04:00
|
|
|
keccak_general: Some(StackBehavior {
|
|
|
|
|
num_pops: 4,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
}),
|
|
|
|
|
prover_input: None, // TODO
|
2023-06-07 18:27:23 -07:00
|
|
|
pop: Some(StackBehavior {
|
|
|
|
|
num_pops: 1,
|
|
|
|
|
pushes: false,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
}),
|
2023-07-20 16:46:31 -04:00
|
|
|
jumps: None, // Depends on whether it's a JUMP or a JUMPI.
|
2022-12-11 10:41:32 -08:00
|
|
|
pc: Some(StackBehavior {
|
|
|
|
|
num_pops: 0,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
}),
|
2022-12-11 11:02:19 -08:00
|
|
|
jumpdest: Some(StackBehavior {
|
|
|
|
|
num_pops: 0,
|
|
|
|
|
pushes: false,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
}),
|
2023-06-13 13:29:30 -07:00
|
|
|
push0: Some(StackBehavior {
|
|
|
|
|
num_pops: 0,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
}),
|
2022-12-11 11:02:19 -08:00
|
|
|
push: None, // TODO
|
2023-10-18 17:32:43 -04:00
|
|
|
dup_swap: None,
|
2023-10-30 12:56:11 -04:00
|
|
|
context_op: None,
|
2023-09-12 14:45:37 -04:00
|
|
|
mload_32bytes: Some(StackBehavior {
|
|
|
|
|
num_pops: 4,
|
|
|
|
|
pushes: true,
|
2023-10-11 22:28:49 +02:00
|
|
|
disable_other_channels: false,
|
|
|
|
|
}),
|
|
|
|
|
mstore_32bytes: Some(StackBehavior {
|
|
|
|
|
num_pops: 5,
|
|
|
|
|
pushes: false,
|
2023-09-12 14:45:37 -04:00
|
|
|
disable_other_channels: false,
|
|
|
|
|
}),
|
2022-12-11 11:08:33 -08:00
|
|
|
exit_kernel: Some(StackBehavior {
|
|
|
|
|
num_pops: 1,
|
|
|
|
|
pushes: false,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
}),
|
2023-09-15 00:51:02 +01:00
|
|
|
m_op_general: None,
|
2022-11-07 12:29:28 -08:00
|
|
|
syscall: Some(StackBehavior {
|
|
|
|
|
num_pops: 0,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: false,
|
|
|
|
|
}),
|
2023-06-02 15:51:26 -07:00
|
|
|
exception: Some(StackBehavior {
|
|
|
|
|
num_pops: 0,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: false,
|
|
|
|
|
}),
|
2022-09-28 15:18:56 -07:00
|
|
|
};
|
|
|
|
|
|
2023-10-11 22:28:49 +02:00
|
|
|
pub(crate) const EQ_STACK_BEHAVIOR: Option<StackBehavior> = Some(StackBehavior {
|
|
|
|
|
num_pops: 2,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
});
|
|
|
|
|
pub(crate) const IS_ZERO_STACK_BEHAVIOR: Option<StackBehavior> = Some(StackBehavior {
|
|
|
|
|
num_pops: 1,
|
|
|
|
|
pushes: true,
|
|
|
|
|
disable_other_channels: true,
|
|
|
|
|
});
|
2023-08-12 11:08:01 -04:00
|
|
|
|
|
|
|
|
pub(crate) fn eval_packed_one<P: PackedField>(
|
2022-09-28 15:18:56 -07:00
|
|
|
lv: &CpuColumnsView<P>,
|
2023-08-28 16:32:04 -04:00
|
|
|
nv: &CpuColumnsView<P>,
|
2022-09-28 15:18:56 -07:00
|
|
|
filter: P,
|
|
|
|
|
stack_behavior: StackBehavior,
|
|
|
|
|
yield_constr: &mut ConstraintConsumer<P>,
|
|
|
|
|
) {
|
2023-10-11 22:28:49 +02:00
|
|
|
// If you have pops.
|
|
|
|
|
if stack_behavior.num_pops > 0 {
|
|
|
|
|
for i in 1..stack_behavior.num_pops {
|
|
|
|
|
let channel = lv.mem_channels[i];
|
2022-09-28 15:18:56 -07:00
|
|
|
|
2023-10-11 22:28:49 +02:00
|
|
|
yield_constr.constraint(filter * (channel.used - P::ONES));
|
|
|
|
|
yield_constr.constraint(filter * (channel.is_read - P::ONES));
|
2022-09-28 15:18:56 -07:00
|
|
|
|
2023-10-11 22:28:49 +02:00
|
|
|
yield_constr.constraint(filter * (channel.addr_context - lv.context));
|
|
|
|
|
yield_constr.constraint(
|
|
|
|
|
filter
|
|
|
|
|
* (channel.addr_segment - P::Scalar::from_canonical_u64(Segment::Stack as u64)),
|
|
|
|
|
);
|
|
|
|
|
// Remember that the first read (`i == 1`) is for the second stack element at `stack[stack_len - 1]`.
|
|
|
|
|
let addr_virtual = lv.stack_len - P::Scalar::from_canonical_usize(i + 1);
|
|
|
|
|
yield_constr.constraint(filter * (channel.addr_virtual - addr_virtual));
|
|
|
|
|
}
|
2022-09-28 15:18:56 -07:00
|
|
|
|
2023-10-11 22:28:49 +02:00
|
|
|
// If you also push, you don't need to read the new top of the stack.
|
|
|
|
|
// If you don't:
|
|
|
|
|
// - if the stack isn't empty after the pops, you read the new top from an extra pop.
|
|
|
|
|
// - if not, the extra read is disabled.
|
|
|
|
|
// These are transition constraints: they don't apply to the last row.
|
|
|
|
|
if !stack_behavior.pushes {
|
|
|
|
|
// If stack_len != N...
|
|
|
|
|
let len_diff = lv.stack_len - P::Scalar::from_canonical_usize(stack_behavior.num_pops);
|
|
|
|
|
let new_filter = len_diff * filter;
|
|
|
|
|
// Read an extra element.
|
|
|
|
|
let channel = nv.mem_channels[0];
|
|
|
|
|
yield_constr.constraint_transition(new_filter * (channel.used - P::ONES));
|
|
|
|
|
yield_constr.constraint_transition(new_filter * (channel.is_read - P::ONES));
|
|
|
|
|
yield_constr.constraint_transition(new_filter * (channel.addr_context - nv.context));
|
|
|
|
|
yield_constr.constraint_transition(
|
|
|
|
|
new_filter
|
|
|
|
|
* (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 * (channel.addr_virtual - addr_virtual));
|
|
|
|
|
// Constrain `stack_inv_aux`.
|
|
|
|
|
yield_constr.constraint(
|
|
|
|
|
filter
|
|
|
|
|
* (len_diff * lv.general.stack().stack_inv - lv.general.stack().stack_inv_aux),
|
|
|
|
|
);
|
|
|
|
|
// Disable channel if stack_len == N.
|
|
|
|
|
let empty_stack_filter = filter * (lv.general.stack().stack_inv_aux - P::ONES);
|
|
|
|
|
yield_constr.constraint_transition(empty_stack_filter * channel.used);
|
|
|
|
|
}
|
2022-09-28 15:18:56 -07:00
|
|
|
}
|
2023-10-11 22:28:49 +02:00
|
|
|
// If the op only pushes, you only need to constrain the top of the stack if the stack isn't empty.
|
|
|
|
|
else if stack_behavior.pushes {
|
|
|
|
|
// If len > 0...
|
|
|
|
|
let new_filter = lv.stack_len * filter;
|
|
|
|
|
// You write the previous top of the stack in memory, in the last channel.
|
2022-09-28 15:18:56 -07:00
|
|
|
let channel = lv.mem_channels[NUM_GP_CHANNELS - 1];
|
2023-10-11 22:28:49 +02:00
|
|
|
yield_constr.constraint(new_filter * (channel.used - P::ONES));
|
|
|
|
|
yield_constr.constraint(new_filter * channel.is_read);
|
|
|
|
|
yield_constr.constraint(new_filter * (channel.addr_context - lv.context));
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(
|
2023-10-11 22:28:49 +02:00
|
|
|
new_filter
|
|
|
|
|
* (channel.addr_segment - P::Scalar::from_canonical_u64(Segment::Stack as u64)),
|
2022-09-28 15:18:56 -07:00
|
|
|
);
|
2023-10-11 22:28:49 +02:00
|
|
|
let addr_virtual = lv.stack_len - P::ONES;
|
|
|
|
|
yield_constr.constraint(new_filter * (channel.addr_virtual - addr_virtual));
|
|
|
|
|
for (limb_ch, limb_top) in channel.value.iter().zip(lv.mem_channels[0].value.iter()) {
|
|
|
|
|
yield_constr.constraint(new_filter * (*limb_ch - *limb_top));
|
|
|
|
|
}
|
|
|
|
|
// Else you disable the channel.
|
|
|
|
|
yield_constr.constraint(
|
|
|
|
|
filter
|
|
|
|
|
* (lv.stack_len * lv.general.stack().stack_inv - lv.general.stack().stack_inv_aux),
|
|
|
|
|
);
|
|
|
|
|
let empty_stack_filter = filter * (lv.general.stack().stack_inv_aux - P::ONES);
|
|
|
|
|
yield_constr.constraint(empty_stack_filter * channel.used);
|
|
|
|
|
}
|
|
|
|
|
// If the op doesn't pop nor push, the top of the stack must not change.
|
|
|
|
|
else {
|
|
|
|
|
yield_constr.constraint(filter * nv.mem_channels[0].used);
|
|
|
|
|
for (limb_old, limb_new) in lv.mem_channels[0]
|
|
|
|
|
.value
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(nv.mem_channels[0].value.iter())
|
|
|
|
|
{
|
|
|
|
|
yield_constr.constraint(filter * (*limb_old - *limb_new));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 15:18:56 -07:00
|
|
|
// Unused channels
|
|
|
|
|
if stack_behavior.disable_other_channels {
|
2023-10-11 22:28:49 +02:00
|
|
|
// The first channel contains (or not) the top od the stack and is constrained elsewhere.
|
|
|
|
|
for i in max(1, stack_behavior.num_pops)..NUM_GP_CHANNELS - (stack_behavior.pushes as usize)
|
|
|
|
|
{
|
2022-09-28 15:18:56 -07:00
|
|
|
let channel = lv.mem_channels[i];
|
|
|
|
|
yield_constr.constraint(filter * channel.used);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-28 16:32:04 -04:00
|
|
|
|
|
|
|
|
// Constrain new stack length.
|
|
|
|
|
let num_pops = P::Scalar::from_canonical_usize(stack_behavior.num_pops);
|
|
|
|
|
let push = P::Scalar::from_canonical_usize(stack_behavior.pushes as usize);
|
|
|
|
|
yield_constr.constraint_transition(filter * (nv.stack_len - (lv.stack_len - num_pops + push)));
|
2022-09-28 15:18:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn eval_packed<P: PackedField>(
|
|
|
|
|
lv: &CpuColumnsView<P>,
|
2023-08-28 16:32:04 -04:00
|
|
|
nv: &CpuColumnsView<P>,
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr: &mut ConstraintConsumer<P>,
|
|
|
|
|
) {
|
|
|
|
|
for (op, stack_behavior) in izip!(lv.op.into_iter(), STACK_BEHAVIORS.into_iter()) {
|
|
|
|
|
if let Some(stack_behavior) = stack_behavior {
|
2023-08-28 16:32:04 -04:00
|
|
|
eval_packed_one(lv, nv, op, stack_behavior, yield_constr);
|
2022-09-28 15:18:56 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-12 11:08:01 -04:00
|
|
|
pub(crate) fn eval_ext_circuit_one<F: RichField + Extendable<D>, const D: usize>(
|
2022-09-28 15:18:56 -07:00
|
|
|
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
|
|
|
|
|
lv: &CpuColumnsView<ExtensionTarget<D>>,
|
2023-08-28 16:32:04 -04:00
|
|
|
nv: &CpuColumnsView<ExtensionTarget<D>>,
|
2022-09-28 15:18:56 -07:00
|
|
|
filter: ExtensionTarget<D>,
|
|
|
|
|
stack_behavior: StackBehavior,
|
|
|
|
|
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
|
|
|
|
) {
|
2023-10-11 22:28:49 +02:00
|
|
|
// If you have pops.
|
|
|
|
|
if stack_behavior.num_pops > 0 {
|
|
|
|
|
for i in 1..stack_behavior.num_pops {
|
|
|
|
|
let channel = lv.mem_channels[i];
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let constr = builder.mul_sub_extension(filter, channel.used, filter);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let constr = builder.mul_sub_extension(filter, channel.is_read, filter);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let diff = builder.sub_extension(channel.addr_context, lv.context);
|
|
|
|
|
let constr = builder.mul_extension(filter, diff);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let constr = builder.arithmetic_extension(
|
|
|
|
|
F::ONE,
|
|
|
|
|
-F::from_canonical_u64(Segment::Stack as u64),
|
|
|
|
|
filter,
|
|
|
|
|
channel.addr_segment,
|
|
|
|
|
filter,
|
|
|
|
|
);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
// Remember that the first read (`i == 1`) is for the second stack element at `stack[stack_len - 1]`.
|
|
|
|
|
{
|
|
|
|
|
let diff = builder.sub_extension(channel.addr_virtual, lv.stack_len);
|
|
|
|
|
let constr = builder.arithmetic_extension(
|
|
|
|
|
F::ONE,
|
|
|
|
|
F::from_canonical_usize(i + 1),
|
|
|
|
|
filter,
|
|
|
|
|
diff,
|
|
|
|
|
filter,
|
|
|
|
|
);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-28 15:18:56 -07:00
|
|
|
|
2023-10-11 22:28:49 +02:00
|
|
|
// If you also push, you don't need to read the new top of the stack.
|
|
|
|
|
// If you don't:
|
|
|
|
|
// - if the stack isn't empty after the pops, you read the new top from an extra pop.
|
|
|
|
|
// - if not, the extra read is disabled.
|
|
|
|
|
// These are transition constraints: they don't apply to the last row.
|
|
|
|
|
if !stack_behavior.pushes {
|
|
|
|
|
// If stack_len != N...
|
|
|
|
|
let target_num_pops =
|
|
|
|
|
builder.constant_extension(F::from_canonical_usize(stack_behavior.num_pops).into());
|
|
|
|
|
let len_diff = builder.sub_extension(lv.stack_len, target_num_pops);
|
|
|
|
|
let new_filter = builder.mul_extension(filter, len_diff);
|
|
|
|
|
// Read an extra element.
|
|
|
|
|
let channel = nv.mem_channels[0];
|
2022-09-28 15:18:56 -07:00
|
|
|
|
2023-10-11 22:28:49 +02:00
|
|
|
{
|
|
|
|
|
let constr = builder.mul_sub_extension(new_filter, channel.used, new_filter);
|
|
|
|
|
yield_constr.constraint_transition(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let constr = builder.mul_sub_extension(new_filter, channel.is_read, new_filter);
|
|
|
|
|
yield_constr.constraint_transition(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let diff = builder.sub_extension(channel.addr_context, nv.context);
|
|
|
|
|
let constr = builder.mul_extension(new_filter, diff);
|
|
|
|
|
yield_constr.constraint_transition(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let constr = builder.arithmetic_extension(
|
|
|
|
|
F::ONE,
|
|
|
|
|
-F::from_canonical_u64(Segment::Stack as u64),
|
|
|
|
|
new_filter,
|
|
|
|
|
channel.addr_segment,
|
|
|
|
|
new_filter,
|
|
|
|
|
);
|
|
|
|
|
yield_constr.constraint_transition(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let diff = builder.sub_extension(channel.addr_virtual, nv.stack_len);
|
|
|
|
|
let constr =
|
|
|
|
|
builder.arithmetic_extension(F::ONE, F::ONE, new_filter, diff, new_filter);
|
|
|
|
|
yield_constr.constraint_transition(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
// Constrain `stack_inv_aux`.
|
|
|
|
|
{
|
|
|
|
|
let prod = builder.mul_extension(len_diff, lv.general.stack().stack_inv);
|
|
|
|
|
let diff = builder.sub_extension(prod, lv.general.stack().stack_inv_aux);
|
|
|
|
|
let constr = builder.mul_extension(filter, diff);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
// Disable channel if stack_len == N.
|
|
|
|
|
{
|
|
|
|
|
let empty_stack_filter =
|
|
|
|
|
builder.mul_sub_extension(filter, lv.general.stack().stack_inv_aux, filter);
|
|
|
|
|
let constr = builder.mul_extension(empty_stack_filter, channel.used);
|
|
|
|
|
yield_constr.constraint_transition(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If the op only pushes, you only need to constrain the top of the stack if the stack isn't empty.
|
|
|
|
|
else if stack_behavior.pushes {
|
|
|
|
|
// If len > 0...
|
|
|
|
|
let new_filter = builder.mul_extension(lv.stack_len, filter);
|
|
|
|
|
// You write the previous top of the stack in memory, in the last channel.
|
|
|
|
|
let channel = lv.mem_channels[NUM_GP_CHANNELS - 1];
|
2022-09-28 15:18:56 -07:00
|
|
|
{
|
2023-10-11 22:28:49 +02:00
|
|
|
let constr = builder.mul_sub_extension(new_filter, channel.used, new_filter);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
2023-10-11 22:28:49 +02:00
|
|
|
let constr = builder.mul_extension(new_filter, channel.is_read);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let diff = builder.sub_extension(channel.addr_context, lv.context);
|
2023-10-11 22:28:49 +02:00
|
|
|
let constr = builder.mul_extension(new_filter, diff);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let constr = builder.arithmetic_extension(
|
|
|
|
|
F::ONE,
|
|
|
|
|
-F::from_canonical_u64(Segment::Stack as u64),
|
2023-10-11 22:28:49 +02:00
|
|
|
new_filter,
|
2022-09-28 15:18:56 -07:00
|
|
|
channel.addr_segment,
|
2023-10-11 22:28:49 +02:00
|
|
|
new_filter,
|
2022-09-28 15:18:56 -07:00
|
|
|
);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
let diff = builder.sub_extension(channel.addr_virtual, lv.stack_len);
|
2023-10-11 22:28:49 +02:00
|
|
|
let constr = builder.arithmetic_extension(F::ONE, F::ONE, new_filter, diff, new_filter);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
2023-10-11 22:28:49 +02:00
|
|
|
for (limb_ch, limb_top) in channel.value.iter().zip(lv.mem_channels[0].value.iter()) {
|
|
|
|
|
let diff = builder.sub_extension(*limb_ch, *limb_top);
|
|
|
|
|
let constr = builder.mul_extension(new_filter, diff);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
2023-10-11 22:28:49 +02:00
|
|
|
// Else you disable the channel.
|
2022-09-28 15:18:56 -07:00
|
|
|
{
|
2023-10-11 22:28:49 +02:00
|
|
|
let diff = builder.mul_extension(lv.stack_len, lv.general.stack().stack_inv);
|
|
|
|
|
let diff = builder.sub_extension(diff, lv.general.stack().stack_inv_aux);
|
|
|
|
|
let constr = builder.mul_extension(filter, diff);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
2023-10-11 22:28:49 +02:00
|
|
|
let empty_stack_filter =
|
|
|
|
|
builder.mul_sub_extension(filter, lv.general.stack().stack_inv_aux, filter);
|
|
|
|
|
let constr = builder.mul_extension(empty_stack_filter, channel.used);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
2023-10-11 22:28:49 +02:00
|
|
|
}
|
|
|
|
|
// If the op doesn't pop nor push, the top of the stack must not change.
|
|
|
|
|
else {
|
2022-09-28 15:18:56 -07:00
|
|
|
{
|
2023-10-11 22:28:49 +02:00
|
|
|
let constr = builder.mul_extension(filter, nv.mem_channels[0].used);
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
{
|
2023-10-11 22:28:49 +02:00
|
|
|
for (limb_old, limb_new) in lv.mem_channels[0]
|
|
|
|
|
.value
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(nv.mem_channels[0].value.iter())
|
|
|
|
|
{
|
|
|
|
|
let diff = builder.sub_extension(*limb_old, *limb_new);
|
|
|
|
|
let constr = builder.mul_extension(filter, diff);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 15:18:56 -07:00
|
|
|
// Unused channels
|
|
|
|
|
if stack_behavior.disable_other_channels {
|
2023-10-11 22:28:49 +02:00
|
|
|
// The first channel contains (or not) the top od the stack and is constrained elsewhere.
|
|
|
|
|
for i in max(1, stack_behavior.num_pops)..NUM_GP_CHANNELS - (stack_behavior.pushes as usize)
|
|
|
|
|
{
|
2022-09-28 15:18:56 -07:00
|
|
|
let channel = lv.mem_channels[i];
|
|
|
|
|
let constr = builder.mul_extension(filter, channel.used);
|
|
|
|
|
yield_constr.constraint(builder, constr);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-28 16:32:04 -04:00
|
|
|
|
|
|
|
|
// Constrain new stack length.
|
|
|
|
|
let diff = builder.constant_extension(
|
|
|
|
|
F::Extension::from_canonical_usize(stack_behavior.num_pops)
|
|
|
|
|
- F::Extension::from_canonical_usize(stack_behavior.pushes as usize),
|
|
|
|
|
);
|
|
|
|
|
let diff = builder.sub_extension(lv.stack_len, diff);
|
|
|
|
|
let diff = builder.sub_extension(nv.stack_len, diff);
|
|
|
|
|
let constr = builder.mul_extension(filter, diff);
|
|
|
|
|
yield_constr.constraint_transition(builder, constr);
|
2022-09-28 15:18:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
|
|
|
|
|
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
|
|
|
|
|
lv: &CpuColumnsView<ExtensionTarget<D>>,
|
2023-08-28 16:32:04 -04:00
|
|
|
nv: &CpuColumnsView<ExtensionTarget<D>>,
|
2022-09-28 15:18:56 -07:00
|
|
|
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
|
|
|
|
) {
|
|
|
|
|
for (op, stack_behavior) in izip!(lv.op.into_iter(), STACK_BEHAVIORS.into_iter()) {
|
|
|
|
|
if let Some(stack_behavior) = stack_behavior {
|
2023-08-28 16:32:04 -04:00
|
|
|
eval_ext_circuit_one(builder, lv, nv, op, stack_behavior, yield_constr);
|
2022-09-28 15:18:56 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|