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

Address bundling #1426

Merged
merged 31 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7fc00a2
Start
Nashtare Oct 28, 2023
09c91e6
Scale TxnFields
Nashtare Nov 6, 2023
beab0c8
Speed-up
Nashtare Nov 13, 2023
81d4cc1
Merge branch 'main' into address_bundling
Nashtare Dec 11, 2023
38b6c94
Misc fixes
Nashtare Dec 11, 2023
481b6cd
Other fixes
Nashtare Dec 11, 2023
36e657e
Fix
Nashtare Dec 12, 2023
f1a20f4
Fix offset
Nashtare Dec 12, 2023
f55623c
One more fix
Nashtare Dec 13, 2023
b82eef2
And one more fix
Nashtare Dec 13, 2023
6fec3e4
Merge remote-tracking branch 'mir/main' into address_bundling
Nashtare Dec 13, 2023
54ce5e4
Fix
Nashtare Dec 14, 2023
db5de45
Fix
Nashtare Dec 14, 2023
3e0f427
Fix init
Nashtare Dec 14, 2023
534bd25
More interpreter fixes
Nashtare Dec 14, 2023
bdfef3d
Final fixes
Nashtare Dec 14, 2023
31209c1
Add helper methods
Nashtare Dec 14, 2023
99a6156
Merge remote-tracking branch 'mir/main' into address_bundling
Nashtare Dec 14, 2023
f68071b
Clippy
Nashtare Dec 14, 2023
633bab5
Merge remote-tracking branch 'mir/main' into address_bundling
Nashtare Dec 18, 2023
8f57d75
Apply suggestions
Nashtare Dec 18, 2023
33e4f36
Merge remote-tracking branch 'mir/main' into address_bundling
Nashtare Dec 18, 2023
25d2041
Merge branch 'main' into address_bundling
Nashtare Dec 19, 2023
df43538
Comments
Nashtare Dec 19, 2023
c3da149
Merge remote-tracking branch 'mir/main' into address_bundling
Nashtare Jan 2, 2024
32b7d6d
Update documentation
Nashtare Jan 2, 2024
cb90f4b
Regenerate pdf
Nashtare Jan 2, 2024
71b52c7
minor
Nashtare Jan 2, 2024
5e967fb
Rename some macros for consistency
Nashtare Jan 4, 2024
38b1464
Add utility method for unscaling segments and scaled metadata
Nashtare Jan 4, 2024
4d7fd30
Address comments
Nashtare Jan 8, 2024
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
49 changes: 41 additions & 8 deletions evm/src/cpu/byte_unpacking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,30 @@ pub(crate) fn eval_packed<P: PackedField>(
// The MSTORE_32BYTES opcodes are differentiated from MLOAD_32BYTES
// by the 5th bit set to 0.
let filter = lv.op.m_op_32bytes * (lv.opcode_bits[5] - P::ONES);
let new_offset = nv.mem_channels[0].value;
let virt = lv.mem_channels[2].value[0];

// The address to write to is stored in the first memory channel.
// It contains virt, segment, ctx in its first 3 limbs, and 0 otherwise.
// The new address is identical, except for its `virtual` limb that is increased by the corresponding `len` offset.
let new_addr = nv.mem_channels[0].value;
let written_addr = lv.mem_channels[0].value;

// Read len from opcode bits and constrain the pushed new offset.
let len_bits: P = lv.opcode_bits[..5]
.iter()
.enumerate()
.map(|(i, &bit)| bit * P::Scalar::from_canonical_u64(1 << i))
.sum();
let len = len_bits + P::ONES;
yield_constr.constraint(filter * (new_offset[0] - virt - len));
for &limb in &new_offset[1..] {

// Check that `virt` is increased properly.
yield_constr.constraint(filter * (new_addr[0] - written_addr[0] - len));

// Check that `segment` and `ctx` do not change.
yield_constr.constraint(filter * (new_addr[1] - written_addr[1]));
yield_constr.constraint(filter * (new_addr[2] - written_addr[2]));

// Check that the rest of the returned address is null.
for &limb in &new_addr[3..] {
yield_constr.constraint(filter * limb);
}
}
Expand All @@ -41,20 +54,40 @@ pub(crate) fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
// by the 5th bit set to 0.
let filter =
builder.mul_sub_extension(lv.op.m_op_32bytes, lv.opcode_bits[5], lv.op.m_op_32bytes);
let new_offset = nv.mem_channels[0].value;
let virt = lv.mem_channels[2].value[0];

// The address to write to is stored in the first memory channel.
// It contains virt, segment, ctx in its first 3 limbs, and 0 otherwise.
// The new address is identical, except for its `virtual` limb that is increased by the corresponding `len` offset.
let new_addr = nv.mem_channels[0].value;
let written_addr = lv.mem_channels[0].value;

// Read len from opcode bits and constrain the pushed new offset.
let len_bits = lv.opcode_bits[..5].iter().enumerate().fold(
builder.zero_extension(),
|cumul, (i, &bit)| {
builder.mul_const_add_extension(F::from_canonical_u64(1 << i), bit, cumul)
},
);
let diff = builder.sub_extension(new_offset[0], virt);

// Check that `virt` is increased properly.
let diff = builder.sub_extension(new_addr[0], written_addr[0]);
let diff = builder.sub_extension(diff, len_bits);
let constr = builder.mul_sub_extension(filter, diff, filter);
yield_constr.constraint(builder, constr);
for &limb in &new_offset[1..] {

// Check that `segment` and `ctx` do not change.
{
let diff = builder.sub_extension(new_addr[1], written_addr[1]);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint(builder, constr);

let diff = builder.sub_extension(new_addr[2], written_addr[2]);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint(builder, constr);
}

// Check that the rest of the returned address is null.
for &limb in &new_addr[3..] {
let constr = builder.mul_extension(filter, limb);
yield_constr.constraint(builder, constr);
}
Expand Down
63 changes: 43 additions & 20 deletions evm/src/cpu/contextops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use super::membus::NUM_GP_CHANNELS;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::CpuColumnsView;
use crate::cpu::kernel::constants::context_metadata::ContextMetadata;
use crate::memory::segments::Segment;
use crate::memory::segments::{Segment, SEGMENT_SCALING_FACTOR};
use crate::memory::VALUE_LIMBS;

// If true, the instruction will keep the current context for the next row.
// If false, next row's context is handled manually.
Expand Down Expand Up @@ -83,9 +84,13 @@ fn eval_packed_get<P: PackedField>(
// If the opcode is GET_CONTEXT, then lv.opcode_bits[0] = 0.
let filter = lv.op.context_op * (P::ONES - lv.opcode_bits[0]);
let new_stack_top = nv.mem_channels[0].value;
yield_constr.constraint(filter * (new_stack_top[0] - lv.context));
for &limb in &new_stack_top[1..] {
yield_constr.constraint(filter * limb);
// Context is scaled by 2^64, hence stored in the 3rd limb.
yield_constr.constraint(filter * (new_stack_top[2] - lv.context));

for (i, &limb) in new_stack_top.iter().enumerate() {
Nashtare marked this conversation as resolved.
Show resolved Hide resolved
if i != 2 {
yield_constr.constraint(filter * limb);
}
}

// Constrain new stack length.
Expand Down Expand Up @@ -113,14 +118,18 @@ fn eval_ext_circuit_get<F: RichField + Extendable<D>, const D: usize>(
let prod = builder.mul_extension(lv.op.context_op, lv.opcode_bits[0]);
let filter = builder.sub_extension(lv.op.context_op, prod);
let new_stack_top = nv.mem_channels[0].value;
// Context is scaled by 2^64, hence stored in the 3rd limb.
{
let diff = builder.sub_extension(new_stack_top[0], lv.context);
let diff = builder.sub_extension(new_stack_top[2], lv.context);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint(builder, constr);
}
for &limb in &new_stack_top[1..] {
let constr = builder.mul_extension(filter, limb);
yield_constr.constraint(builder, constr);

for (i, &limb) in new_stack_top.iter().enumerate() {
if i != 2 {
let constr = builder.mul_extension(filter, limb);
yield_constr.constraint(builder, constr);
}
}

// Constrain new stack length.
Expand Down Expand Up @@ -155,13 +164,20 @@ fn eval_packed_set<P: PackedField>(
let stack_top = lv.mem_channels[0].value;
let write_old_sp_channel = lv.mem_channels[1];
let read_new_sp_channel = lv.mem_channels[2];
let ctx_metadata_segment = P::Scalar::from_canonical_u64(Segment::ContextMetadata as u64);
let stack_size_field = P::Scalar::from_canonical_u64(ContextMetadata::StackSize as u64);
let ctx_metadata_segment =
P::Scalar::from_canonical_u64(Segment::ContextMetadata as u64 >> SEGMENT_SCALING_FACTOR);
let stack_size_field = P::Scalar::from_canonical_u64(
// We need to unscale the context metadata.
ContextMetadata::StackSize as u64 - Segment::ContextMetadata as u64,
);
let local_sp_dec = lv.stack_len - P::ONES;

// The next row's context is read from stack_top.
yield_constr.constraint(filter * (stack_top[0] - nv.context));
for &limb in &stack_top[1..] {
yield_constr.constraint(filter * (stack_top[2] - nv.context));
for &limb in &stack_top[0..2] {
Nashtare marked this conversation as resolved.
Show resolved Hide resolved
yield_constr.constraint(filter * limb);
}
for &limb in &stack_top[3..] {
yield_constr.constraint(filter * limb);
}

Expand Down Expand Up @@ -220,22 +236,27 @@ fn eval_ext_circuit_set<F: RichField + Extendable<D>, const D: usize>(
let stack_top = lv.mem_channels[0].value;
let write_old_sp_channel = lv.mem_channels[1];
let read_new_sp_channel = lv.mem_channels[2];
let ctx_metadata_segment = builder.constant_extension(F::Extension::from_canonical_u32(
Segment::ContextMetadata as u32,
let ctx_metadata_segment = builder.constant_extension(F::Extension::from_canonical_u64(
Segment::ContextMetadata as u64 >> SEGMENT_SCALING_FACTOR,
));
let stack_size_field = builder.constant_extension(F::Extension::from_canonical_u32(
ContextMetadata::StackSize as u32,
let stack_size_field = builder.constant_extension(F::Extension::from_canonical_u64(
// We need to unscale the context metadata.
ContextMetadata::StackSize as u64 - Segment::ContextMetadata as u64,
));
let one = builder.one_extension();
let local_sp_dec = builder.sub_extension(lv.stack_len, one);

// The next row's context is read from stack_top.
{
let diff = builder.sub_extension(stack_top[0], nv.context);
let diff = builder.sub_extension(stack_top[2], nv.context);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint(builder, constr);
}
for &limb in &stack_top[1..] {
for &limb in &stack_top[0..2] {
let constr = builder.mul_extension(filter, limb);
yield_constr.constraint(builder, constr);
}
for &limb in &stack_top[3..] {
let constr = builder.mul_extension(filter, limb);
yield_constr.constraint(builder, constr);
}
Expand Down Expand Up @@ -368,7 +389,9 @@ pub(crate) fn eval_packed<P: PackedField>(
yield_constr.constraint(new_filter * (channel.addr_context - nv.context));
// Same segment for both.
yield_constr.constraint(
new_filter * (channel.addr_segment - P::Scalar::from_canonical_u64(Segment::Stack as u64)),
new_filter
* (channel.addr_segment
- P::Scalar::from_canonical_u64(Segment::Stack as u64 >> SEGMENT_SCALING_FACTOR)),
);
// The address is one less than stack_len.
let addr_virtual = stack_len - P::ONES;
Expand Down Expand Up @@ -429,7 +452,7 @@ pub(crate) fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
{
let diff = builder.add_const_extension(
channel.addr_segment,
-F::from_canonical_u64(Segment::Stack as u64),
-F::from_canonical_u64(Segment::Stack as u64 >> SEGMENT_SCALING_FACTOR),
);
let constr = builder.mul_extension(new_filter, diff);
yield_constr.constraint(builder, constr);
Expand Down
65 changes: 41 additions & 24 deletions evm/src/cpu/cpu_stark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,21 @@ use crate::cpu::{
};
use crate::cross_table_lookup::{Column, Filter, TableWithColumns};
use crate::evaluation_frame::{StarkEvaluationFrame, StarkFrame};
use crate::memory::segments::Segment;
use crate::memory::segments::{Segment, SEGMENT_SCALING_FACTOR};
use crate::memory::{NUM_CHANNELS, VALUE_LIMBS};
use crate::stark::Stark;

/// Creates the vector of `Columns` corresponding to the General Purpose channels when calling the Keccak sponge:
/// the CPU reads the output of the sponge directly from the `KeccakSpongeStark` table.
pub(crate) fn ctl_data_keccak_sponge<F: Field>() -> Vec<Column<F>> {
// When executing KECCAK_GENERAL, the GP memory channels are used as follows:
// GP channel 0: stack[-1] = context
// GP channel 1: stack[-2] = segment
// GP channel 2: stack[-3] = virt
// GP channel 3: stack[-4] = len
// Next GP channel 0: pushed = outputs
let context = Column::single(COL_MAP.mem_channels[0].value[0]);
let segment = Column::single(COL_MAP.mem_channels[1].value[0]);
let virt = Column::single(COL_MAP.mem_channels[2].value[0]);
let len = Column::single(COL_MAP.mem_channels[3].value[0]);
// GP channel 0: stack[-1] = addr (context, segment, virt)
// GP channel 1: stack[-2] = len
// GP channel 4: pushed = outputs
Nashtare marked this conversation as resolved.
Show resolved Hide resolved
let context = Column::single(COL_MAP.mem_channels[0].value[2]);
Nashtare marked this conversation as resolved.
Show resolved Hide resolved
let segment = Column::single(COL_MAP.mem_channels[0].value[1]);
let virt = Column::single(COL_MAP.mem_channels[0].value[0]);
let len = Column::single(COL_MAP.mem_channels[1].value[0]);

let num_channels = F::from_canonical_usize(NUM_CHANNELS);
let timestamp = Column::linear_combination([(COL_MAP.clock, num_channels)]);
Expand Down Expand Up @@ -149,27 +147,30 @@ pub(crate) fn ctl_data_byte_unpacking<F: Field>() -> Vec<Column<F>> {
let is_read = Column::constant(F::ZERO);

// When executing MSTORE_32BYTES, the GP memory channels are used as follows:
// GP channel 0: stack[-1] = context
// GP channel 1: stack[-2] = segment
// GP channel 2: stack[-3] = virt
// GP channel 3: stack[-4] = val
// GP channel 0: stack[-1] = addr (context, segment, virt)
// GP channel 1: stack[-2] = val
// Next GP channel 0: pushed = new_offset (virt + len)
let context = Column::single(COL_MAP.mem_channels[0].value[0]);
let segment = Column::single(COL_MAP.mem_channels[1].value[0]);
let virt = Column::single(COL_MAP.mem_channels[2].value[0]);
let val = Column::singles(COL_MAP.mem_channels[3].value);
let (context, segment, virt) = get_addr(&COL_MAP, 0);
let mut res = vec![
is_read,
Column::single(context),
Column::single(segment),
Column::single(virt),
];

// len can be reconstructed as new_offset - virt.
let len = Column::linear_combination_and_next_row_with_constant(
[(COL_MAP.mem_channels[2].value[0], -F::ONE)],
[(COL_MAP.mem_channels[0].value[0], -F::ONE)],
[(COL_MAP.mem_channels[0].value[0], F::ONE)],
F::ZERO,
);
res.push(len);

let num_channels = F::from_canonical_usize(NUM_CHANNELS);
let timestamp = Column::linear_combination([(COL_MAP.clock, num_channels)]);
res.push(timestamp);

let mut res = vec![is_read, context, segment, virt, len, timestamp];
let val = Column::singles(COL_MAP.mem_channels[1].value);
res.extend(val);

res
Expand Down Expand Up @@ -224,6 +225,20 @@ pub(crate) const MEM_CODE_CHANNEL_IDX: usize = 0;
/// Index of the first general purpose memory channel.
pub(crate) const MEM_GP_CHANNELS_IDX_START: usize = MEM_CODE_CHANNEL_IDX + 1;

/// Recover the three components of an address, given a CPU row and
/// a provided memory channel index.
/// The components are recovered as follows:
///
/// - `context`, shifted by 2^64 (i.e. at index 2)
/// - `segment`, shifted by 2^32 (i.e. at index 1)
/// - `virtual`, not shifted (i.e. at index 0)
pub(crate) const fn get_addr<T: Copy>(lv: &CpuColumnsView<T>, mem_channel: usize) -> (T, T, T) {
let addr_context = lv.mem_channels[mem_channel].value[2];
let addr_segment = lv.mem_channels[mem_channel].value[1];
let addr_virtual = lv.mem_channels[mem_channel].value[0];
Nashtare marked this conversation as resolved.
Show resolved Hide resolved
(addr_context, addr_segment, addr_virtual)
}

/// Make the time/channel column for memory lookups.
fn mem_time_and_channel<F: Field>(channel: usize) -> Column<F> {
let scalar = F::from_canonical_usize(NUM_CHANNELS);
Expand All @@ -234,10 +249,12 @@ fn mem_time_and_channel<F: Field>(channel: usize) -> Column<F> {
/// Creates the vector of `Columns` corresponding to the contents of the code channel when reading code values.
pub(crate) fn ctl_data_code_memory<F: Field>() -> Vec<Column<F>> {
let mut cols = vec![
Column::constant(F::ONE), // is_read
Column::single(COL_MAP.code_context), // addr_context
Column::constant(F::from_canonical_u64(Segment::Code as u64)), // addr_segment
Column::single(COL_MAP.program_counter), // addr_virtual
Column::constant(F::ONE), // is_read
Column::single(COL_MAP.code_context), // addr_context
Column::constant(F::from_canonical_u64(
Segment::Code as u64 >> SEGMENT_SCALING_FACTOR,
)), // addr_segment
Column::single(COL_MAP.program_counter), // addr_virtual
];

// Low limb of the value matches the opcode bits
Expand Down
8 changes: 5 additions & 3 deletions evm/src/cpu/dup_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use plonky2::plonk::circuit_builder::CircuitBuilder;
use super::membus::NUM_GP_CHANNELS;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::{CpuColumnsView, MemoryChannelView};
use crate::memory::segments::Segment;
use crate::memory::segments::{Segment, SEGMENT_SCALING_FACTOR};
Nashtare marked this conversation as resolved.
Show resolved Hide resolved

/// Constrain two channels to have equal values.
fn channels_equal_packed<P: PackedField>(
Expand Down Expand Up @@ -54,7 +54,9 @@ fn constrain_channel_packed<P: PackedField>(
yield_constr.constraint(filter * (channel.is_read - P::Scalar::from_bool(is_read)));
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)),
filter
* (channel.addr_segment
- P::Scalar::from_canonical_u64(Segment::Stack as u64 >> SEGMENT_SCALING_FACTOR)),
);
// Top of the stack is at `addr = lv.stack_len - 1`.
let addr_virtual = lv.stack_len - P::ONES - offset;
Expand Down Expand Up @@ -94,7 +96,7 @@ fn constrain_channel_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
{
let constr = builder.arithmetic_extension(
F::ONE,
-F::from_canonical_u64(Segment::Stack as u64),
-F::from_canonical_u64(Segment::Stack as u64 >> SEGMENT_SCALING_FACTOR),
filter,
channel.addr_segment,
filter,
Expand Down
14 changes: 9 additions & 5 deletions evm/src/cpu/jumps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use plonky2::iop::ext_target::ExtensionTarget;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::CpuColumnsView;
use crate::cpu::membus::NUM_GP_CHANNELS;
use crate::memory::segments::Segment;
use crate::memory::segments::{Segment, SEGMENT_SCALING_FACTOR};
Nashtare marked this conversation as resolved.
Show resolved Hide resolved

/// Evaluates constraints for EXIT_KERNEL.
pub(crate) fn eval_packed_exit_kernel<P: PackedField>(
Expand Down Expand Up @@ -87,7 +87,9 @@ pub(crate) fn eval_packed_jump_jumpi<P: PackedField>(
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)),
new_filter
* (channel.addr_segment
- P::Scalar::from_canonical_u64(Segment::Stack as u64 >> SEGMENT_SCALING_FACTOR)),
);
let addr_virtual = nv.stack_len - P::ONES;
yield_constr.constraint_transition(new_filter * (channel.addr_virtual - addr_virtual));
Expand Down Expand Up @@ -134,7 +136,9 @@ pub(crate) fn eval_packed_jump_jumpi<P: PackedField>(
yield_constr.constraint(
filter
* (jumpdest_flag_channel.addr_segment
- P::Scalar::from_canonical_u64(Segment::JumpdestBits as u64)),
- P::Scalar::from_canonical_u64(
Segment::JumpdestBits as u64 >> SEGMENT_SCALING_FACTOR,
)),
);
yield_constr.constraint(filter * (jumpdest_flag_channel.addr_virtual - dst[0]));

Expand Down Expand Up @@ -203,7 +207,7 @@ pub(crate) fn eval_ext_circuit_jump_jumpi<F: RichField + Extendable<D>, const D:
{
let constr = builder.arithmetic_extension(
F::ONE,
-F::from_canonical_u64(Segment::Stack as u64),
-F::from_canonical_u64(Segment::Stack as u64 >> SEGMENT_SCALING_FACTOR),
new_filter,
channel.addr_segment,
new_filter,
Expand Down Expand Up @@ -306,7 +310,7 @@ pub(crate) fn eval_ext_circuit_jump_jumpi<F: RichField + Extendable<D>, const D:
{
let constr = builder.arithmetic_extension(
F::ONE,
-F::from_canonical_u64(Segment::JumpdestBits as u64),
-F::from_canonical_u64(Segment::JumpdestBits as u64 >> SEGMENT_SCALING_FACTOR),
filter,
jumpdest_flag_channel.addr_segment,
filter,
Expand Down
Loading
Loading