Memory channel for program counter (#717)

This commit is contained in:
Jacqueline Nabaglo 2022-09-22 18:09:23 -07:00 committed by GitHub
parent c27e40e7bb
commit 084700a7f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 28 deletions

View File

@ -1,3 +1,5 @@
use std::iter;
use plonky2::field::extension::Extendable;
use plonky2::field::types::Field;
use plonky2::hash::hash_types::RichField;
@ -5,6 +7,7 @@ use plonky2::hash::hash_types::RichField;
use crate::config::StarkConfig;
use crate::cpu::cpu_stark;
use crate::cpu::cpu_stark::CpuStark;
use crate::cpu::membus::NUM_GP_CHANNELS;
use crate::cross_table_lookup::{CrossTableLookup, TableWithColumns};
use crate::keccak::keccak_stark;
use crate::keccak::keccak_stark::KeccakStark;
@ -13,8 +16,8 @@ use crate::keccak_memory::keccak_memory_stark;
use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark;
use crate::logic;
use crate::logic::LogicStark;
use crate::memory::memory_stark;
use crate::memory::memory_stark::MemoryStark;
use crate::memory::{memory_stark, NUM_CHANNELS};
use crate::stark::Stark;
#[derive(Clone)]
@ -129,11 +132,16 @@ fn ctl_logic<F: Field>() -> CrossTableLookup<F> {
}
fn ctl_memory<F: Field>() -> CrossTableLookup<F> {
let cpu_memory_ops = (0..NUM_CHANNELS).map(|channel| {
let cpu_memory_code_read = TableWithColumns::new(
Table::Cpu,
cpu_stark::ctl_data_code_memory(),
Some(cpu_stark::ctl_filter_code_memory()),
);
let cpu_memory_gp_ops = (0..NUM_GP_CHANNELS).map(|channel| {
TableWithColumns::new(
Table::Cpu,
cpu_stark::ctl_data_memory(channel),
Some(cpu_stark::ctl_filter_memory(channel)),
cpu_stark::ctl_data_gp_memory(channel),
Some(cpu_stark::ctl_filter_gp_memory(channel)),
)
});
let keccak_memory_reads = (0..KECCAK_WIDTH_BYTES).map(|i| {
@ -150,7 +158,8 @@ fn ctl_memory<F: Field>() -> CrossTableLookup<F> {
Some(keccak_memory_stark::ctl_filter()),
)
});
let all_lookers = cpu_memory_ops
let all_lookers = iter::once(cpu_memory_code_read)
.chain(cpu_memory_gp_ops)
.chain(keccak_memory_reads)
.chain(keccak_memory_writes)
.collect();
@ -725,6 +734,7 @@ mod tests {
}
#[test]
#[ignore] // Ignoring but not deleting so the test can serve as an API usage example
fn test_all_stark() -> Result<()> {
let config = StarkConfig::standard_fast_config();
let (all_stark, proof) = get_proof(&config)?;
@ -732,6 +742,7 @@ mod tests {
}
#[test]
#[ignore] // Ignoring but not deleting so the test can serve as an API usage example
fn test_all_stark_recursive_verifier() -> Result<()> {
init_logger();

View File

@ -7,6 +7,7 @@ use std::mem::{size_of, transmute};
use std::ops::{Index, IndexMut};
use crate::cpu::columns::general::CpuGeneralColumnsView;
use crate::cpu::membus::NUM_GP_CHANNELS;
use crate::memory;
use crate::util::{indices_arr, transmute_no_compile_time_size_checks};
@ -35,6 +36,13 @@ pub struct CpuColumnsView<T: Copy> {
/// Lets us re-use columns in non-cycle rows.
pub is_cpu_cycle: T,
/// If CPU cycle: Current context.
// TODO: this is currently unconstrained
pub context: T,
/// If CPU cycle: Context for code memory channel.
pub code_context: T,
/// If CPU cycle: The program counter for the current instruction.
pub program_counter: T,
@ -159,7 +167,7 @@ pub struct CpuColumnsView<T: Copy> {
pub(crate) general: CpuGeneralColumnsView<T>,
pub(crate) clock: T,
pub mem_channels: [MemoryChannelView<T>; memory::NUM_CHANNELS],
pub mem_channels: [MemoryChannelView<T>; NUM_GP_CHANNELS],
}
// `u8` is guaranteed to have a `size_of` of 1.

View File

@ -1,4 +1,5 @@
use std::borrow::{Borrow, BorrowMut};
use std::iter::repeat;
use std::marker::PhantomData;
use itertools::Itertools;
@ -10,10 +11,11 @@ use plonky2::hash::hash_types::RichField;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS};
use crate::cpu::{
bootstrap_kernel, control_flow, decode, jumps, simple_logic, stack_bounds, syscalls,
bootstrap_kernel, control_flow, decode, jumps, membus, simple_logic, stack_bounds, syscalls,
};
use crate::cross_table_lookup::Column;
use crate::memory::NUM_CHANNELS;
use crate::memory::segments::Segment;
use crate::memory::{NUM_CHANNELS, VALUE_LIMBS};
use crate::stark::Stark;
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
@ -25,14 +27,13 @@ pub fn ctl_data_keccak<F: Field>() -> Vec<Column<F>> {
}
pub fn ctl_data_keccak_memory<F: Field>() -> Vec<Column<F>> {
// When executing KECCAK_GENERAL, the memory channels are used as follows:
// channel 0: instruction
// channel 1: stack[-1] = context
// channel 2: stack[-2] = segment
// channel 3: stack[-3] = virtual
let context = Column::single(COL_MAP.mem_channels[1].value[0]);
let segment = Column::single(COL_MAP.mem_channels[2].value[0]);
let virt = Column::single(COL_MAP.mem_channels[3].value[0]);
// 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] = virtual
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 num_channels = F::from_canonical_usize(NUM_CHANNELS);
let clock = Column::linear_combination([(COL_MAP.clock, num_channels)]);
@ -60,29 +61,57 @@ pub fn ctl_filter_logic<F: Field>() -> Column<F> {
Column::sum([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor])
}
pub fn ctl_data_memory<F: Field>(channel: usize) -> Vec<Column<F>> {
debug_assert!(channel < NUM_CHANNELS);
pub const MEM_CODE_CHANNEL_IDX: usize = 0;
pub const MEM_GP_CHANNELS_IDX_START: usize = MEM_CODE_CHANNEL_IDX + 1;
/// 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);
let addend = F::from_canonical_usize(channel);
Column::linear_combination_with_constant([(COL_MAP.clock, scalar)], addend)
}
pub 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
];
// Low limb of the value matches the opcode bits
cols.push(Column::le_bits(COL_MAP.opcode_bits));
// High limbs of the value are all zero.
cols.extend(repeat(Column::constant(F::ZERO)).take(VALUE_LIMBS - 1));
cols.push(mem_time_and_channel(MEM_CODE_CHANNEL_IDX));
cols
}
pub fn ctl_data_gp_memory<F: Field>(channel: usize) -> Vec<Column<F>> {
let channel_map = COL_MAP.mem_channels[channel];
let mut cols: Vec<Column<F>> = Column::singles([
let mut cols: Vec<_> = Column::singles([
channel_map.is_read,
channel_map.addr_context,
channel_map.addr_segment,
channel_map.addr_virtual,
])
.collect_vec();
.collect();
cols.extend(Column::singles(channel_map.value));
let scalar = F::from_canonical_usize(NUM_CHANNELS);
let addend = F::from_canonical_usize(channel);
cols.push(Column::linear_combination_with_constant(
[(COL_MAP.clock, scalar)],
addend,
));
cols.push(mem_time_and_channel(MEM_GP_CHANNELS_IDX_START + channel));
cols
}
pub fn ctl_filter_memory<F: Field>(channel: usize) -> Column<F> {
pub fn ctl_filter_code_memory<F: Field>() -> Column<F> {
Column::single(COL_MAP.is_cpu_cycle)
}
pub fn ctl_filter_gp_memory<F: Field>(channel: usize) -> Column<F> {
Column::single(COL_MAP.mem_channels[channel].used)
}
@ -95,6 +124,7 @@ impl<F: RichField, const D: usize> CpuStark<F, D> {
pub fn generate(&self, local_values: &mut [F; NUM_CPU_COLUMNS]) {
let local_values: &mut CpuColumnsView<_> = local_values.borrow_mut();
decode::generate(local_values);
membus::generate(local_values);
simple_logic::generate(local_values);
stack_bounds::generate(local_values); // Must come after `decode`.
}
@ -117,6 +147,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
control_flow::eval_packed_generic(local_values, next_values, yield_constr);
decode::eval_packed_generic(local_values, yield_constr);
jumps::eval_packed(local_values, next_values, yield_constr);
membus::eval_packed(local_values, yield_constr);
simple_logic::eval_packed(local_values, yield_constr);
stack_bounds::eval_packed(local_values, yield_constr);
syscalls::eval_packed(local_values, next_values, yield_constr);
@ -134,6 +165,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
control_flow::eval_ext_circuit(builder, local_values, next_values, yield_constr);
decode::eval_ext_circuit(builder, local_values, yield_constr);
jumps::eval_ext_circuit(builder, local_values, next_values, yield_constr);
membus::eval_ext_circuit(builder, local_values, yield_constr);
simple_logic::eval_ext_circuit(builder, local_values, yield_constr);
stack_bounds::eval_ext_circuit(builder, local_values, yield_constr);
syscalls::eval_ext_circuit(builder, local_values, next_values, yield_constr);

70
evm/src/cpu/membus.rs Normal file
View File

@ -0,0 +1,70 @@
use plonky2::field::extension::Extendable;
use plonky2::field::packed::PackedField;
use plonky2::field::types::PrimeField64;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::ext_target::ExtensionTarget;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::CpuColumnsView;
/// General-purpose memory channels; they can read and write to all contexts/segments/addresses.
pub const NUM_GP_CHANNELS: usize = 4;
pub mod channel_indices {
use std::ops::Range;
pub const CODE: usize = 0;
pub const GP: Range<usize> = CODE + 1..(CODE + 1) + super::NUM_GP_CHANNELS;
}
/// Total memory channels used by the CPU table. This includes all the `GP_MEM_CHANNELS` as well as
/// all special-purpose memory channels.
///
/// Currently, there is one special-purpose memory channel, which reads the opcode from memory. Its
/// limitations are:
/// - it is enabled by `is_cpu_cycle`,
/// - it always reads and cannot write,
/// - the context is derived from the current context and the `is_kernel_mode` flag,
/// - the segment is hard-wired to the code segment,
/// - the address is `program_counter`,
/// - the value must fit in one byte (in the least-significant position) and its eight bits are
/// found in `opcode_bits`.
/// These limitations save us numerous columns in the CPU table.
pub const NUM_CHANNELS: usize = channel_indices::GP.end;
/// Calculates `lv.stack_len_bounds_aux`. Note that this must be run after decode.
pub fn generate<F: PrimeField64>(lv: &mut CpuColumnsView<F>) {
let cycle_filter = lv.is_cpu_cycle;
if cycle_filter == F::ZERO {
return;
}
assert!(lv.is_kernel_mode.to_canonical_u64() <= 1);
// Set `lv.code_context` to 0 if in kernel mode and to `lv.context` if in user mode.
lv.code_context = (F::ONE - lv.is_kernel_mode) * lv.context;
}
pub fn eval_packed<P: PackedField>(
lv: &CpuColumnsView<P>,
yield_constr: &mut ConstraintConsumer<P>,
) {
// Validate `lv.code_context`. It should be 0 if in kernel mode and `lv.context` if in user
// mode.
yield_constr.constraint(
lv.is_cpu_cycle * (lv.code_context - (P::ONES - lv.is_kernel_mode) * lv.context),
);
}
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>>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
// Validate `lv.code_context`. It should be 0 if in kernel mode and `lv.context` if in user
// mode.
let diff = builder.sub_extension(lv.context, lv.code_context);
let constr = builder.mul_sub_extension(lv.is_kernel_mode, lv.context, diff);
let filtered_constr = builder.mul_extension(lv.is_cpu_cycle, constr);
yield_constr.constraint(builder, filtered_constr);
}

View File

@ -5,6 +5,7 @@ pub mod cpu_stark;
pub(crate) mod decode;
mod jumps;
pub mod kernel;
pub(crate) mod membus;
mod simple_logic;
mod stack_bounds;
mod syscalls;

View File

@ -3,5 +3,5 @@ pub mod memory_stark;
pub mod segments;
// TODO: Move to CPU module, now that channels have been removed from the memory table.
pub(crate) const NUM_CHANNELS: usize = 4;
pub(crate) const NUM_CHANNELS: usize = crate::cpu::membus::NUM_CHANNELS;
pub(crate) const VALUE_LIMBS: usize = 8;