Gas constraints (#880)

* Gas constraints

* Bugfix

* make test pass post rebase
This commit is contained in:
Jacqueline Nabaglo 2023-02-14 22:30:19 -08:00 committed by GitHub
parent ac40bd5f5d
commit f3946f75bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 358 additions and 75 deletions

View File

@ -62,6 +62,9 @@ pub struct CpuColumnsView<T: Copy> {
/// If CPU cycle: We're in kernel (privileged) mode.
pub is_kernel_mode: T,
/// If CPU cycle: Gas counter.
pub gas: T,
/// If CPU cycle: flags for EVM instructions (a few cannot be shared; see the comments in
/// `OpsColumnsView`).
pub op: OpsColumnsView<T>,

View File

@ -40,7 +40,6 @@ pub struct OpsColumnsView<T: Copy> {
pub jump: T, // Note: This column must be 0 when is_cpu_cycle = 0.
pub jumpi: T, // Note: This column must be 0 when is_cpu_cycle = 0.
pub pc: T,
pub gas: T,
pub jumpdest: T,
pub push: T,
pub dup: T,
@ -48,13 +47,12 @@ pub struct OpsColumnsView<T: Copy> {
// TODO: combine GET_CONTEXT and SET_CONTEXT into one flag
pub get_context: T,
pub set_context: T,
pub consume_gas: T,
pub exit_kernel: T,
// TODO: combine MLOAD_GENERAL and MSTORE_GENERAL into one flag
pub mload_general: T,
pub mstore_general: T,
pub syscall: T,
pub syscall: T, // Note: This column must be 0 when is_cpu_cycle = 0.
}
// `u8` is guaranteed to have a `size_of` of 1.

View File

@ -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; 33] = [
const NATIVE_INSTRUCTIONS: [usize; 31] = [
COL_MAP.op.add,
COL_MAP.op.mul,
COL_MAP.op.sub,
@ -35,14 +35,12 @@ const NATIVE_INSTRUCTIONS: [usize; 33] = [
// not JUMP (need to jump)
// not JUMPI (possible need to jump)
COL_MAP.op.pc,
COL_MAP.op.gas,
COL_MAP.op.jumpdest,
// not PUSH (need to increment by more than 1)
COL_MAP.op.dup,
COL_MAP.op.swap,
COL_MAP.op.get_context,
COL_MAP.op.set_context,
COL_MAP.op.consume_gas,
// not EXIT_KERNEL (performs a jump)
COL_MAP.op.mload_general,
COL_MAP.op.mstore_general,

View File

@ -12,8 +12,8 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer
use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS};
use crate::cpu::membus::NUM_GP_CHANNELS;
use crate::cpu::{
bootstrap_kernel, contextops, control_flow, decode, dup_swap, jumps, membus, memio, modfp254,
pc, shift, simple_logic, stack, stack_bounds, syscalls,
bootstrap_kernel, contextops, control_flow, decode, dup_swap, gas, jumps, membus, memio,
modfp254, pc, shift, simple_logic, stack, stack_bounds, syscalls,
};
use crate::cross_table_lookup::Column;
use crate::memory::segments::Segment;
@ -148,6 +148,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, &mut dummy_yield_constr);
dup_swap::eval_packed(local_values, yield_constr);
gas::eval_packed(local_values, next_values, yield_constr);
jumps::eval_packed(local_values, next_values, yield_constr);
membus::eval_packed(local_values, yield_constr);
memio::eval_packed(local_values, yield_constr);
@ -177,6 +178,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, &mut dummy_yield_constr);
dup_swap::eval_ext_circuit(builder, local_values, yield_constr);
gas::eval_ext_circuit(builder, local_values, next_values, yield_constr);
jumps::eval_ext_circuit(builder, local_values, next_values, yield_constr);
membus::eval_ext_circuit(builder, local_values, yield_constr);
memio::eval_ext_circuit(builder, local_values, yield_constr);

View File

@ -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); 38] = [
const OPCODES: [(u8, usize, bool, usize); 36] = [
// (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),
@ -51,14 +51,12 @@ const OPCODES: [(u8, usize, bool, usize); 38] = [
(0x56, 0, false, COL_MAP.op.jump),
(0x57, 0, false, COL_MAP.op.jumpi),
(0x58, 0, false, COL_MAP.op.pc),
(0x5a, 0, false, COL_MAP.op.gas),
(0x5b, 0, false, COL_MAP.op.jumpdest),
(0x60, 5, false, COL_MAP.op.push), // 0x60-0x7f
(0x80, 4, false, COL_MAP.op.dup), // 0x80-0x8f
(0x90, 4, false, COL_MAP.op.swap), // 0x90-0x9f
(0xf6, 0, true, COL_MAP.op.get_context),
(0xf7, 0, true, COL_MAP.op.set_context),
(0xf8, 0, true, COL_MAP.op.consume_gas),
(0xf9, 0, true, COL_MAP.op.exit_kernel),
(0xfb, 0, true, COL_MAP.op.mload_general),
(0xfc, 0, true, COL_MAP.op.mstore_general),

195
evm/src/cpu/gas.rs Normal file
View File

@ -0,0 +1,195 @@
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;
const KERNEL_ONLY_INSTR: Option<u32> = Some(0);
const G_JUMPDEST: Option<u32> = Some(1);
const G_BASE: Option<u32> = Some(2);
const G_VERYLOW: Option<u32> = Some(3);
const G_LOW: Option<u32> = Some(5);
const G_MID: Option<u32> = Some(8);
const G_HIGH: Option<u32> = Some(10);
const SIMPLE_OPCODES: OpsColumnsView<Option<u32>> = OpsColumnsView {
add: G_VERYLOW,
mul: G_LOW,
sub: G_VERYLOW,
div: G_LOW,
mod_: G_LOW,
addmod: G_MID,
mulmod: G_MID,
addfp254: KERNEL_ONLY_INSTR,
mulfp254: KERNEL_ONLY_INSTR,
subfp254: KERNEL_ONLY_INSTR,
submod: KERNEL_ONLY_INSTR,
lt: G_VERYLOW,
gt: G_VERYLOW,
eq: G_VERYLOW,
iszero: G_VERYLOW,
and: G_VERYLOW,
or: G_VERYLOW,
xor: G_VERYLOW,
not: G_VERYLOW,
byte: G_VERYLOW,
shl: G_VERYLOW,
shr: G_VERYLOW,
keccak_general: KERNEL_ONLY_INSTR,
prover_input: KERNEL_ONLY_INSTR,
pop: G_BASE,
jump: G_MID,
jumpi: G_HIGH,
pc: G_BASE,
jumpdest: G_JUMPDEST,
push: G_VERYLOW,
dup: G_VERYLOW,
swap: G_VERYLOW,
get_context: KERNEL_ONLY_INSTR,
set_context: KERNEL_ONLY_INSTR,
exit_kernel: None,
mload_general: KERNEL_ONLY_INSTR,
mstore_general: KERNEL_ONLY_INSTR,
syscall: None,
};
fn eval_packed_accumulate<P: PackedField>(
lv: &CpuColumnsView<P>,
nv: &CpuColumnsView<P>,
yield_constr: &mut ConstraintConsumer<P>,
) {
// Is it an instruction that we constrain here?
// I.e., does it always cost a constant amount of gas?
let is_simple_instr: P = SIMPLE_OPCODES
.into_iter()
.enumerate()
.filter_map(|(i, maybe_cost)| {
// Add flag `lv.op[i]` to the sum if `SIMPLE_OPCODES[i]` is `Some`.
maybe_cost.map(|_| lv.op[i])
})
.sum();
let filter = lv.is_cpu_cycle * is_simple_instr;
// How much gas did we use?
let gas_used: P = SIMPLE_OPCODES
.into_iter()
.enumerate()
.filter_map(|(i, maybe_cost)| {
maybe_cost.map(|cost| P::Scalar::from_canonical_u32(cost) * lv.op[i])
})
.sum();
let constr = nv.gas - (lv.gas + gas_used);
yield_constr.constraint_transition(filter * constr);
for (maybe_cost, op_flag) in izip!(SIMPLE_OPCODES.into_iter(), lv.op.into_iter()) {
if let Some(cost) = maybe_cost {
let cost = P::Scalar::from_canonical_u32(cost);
yield_constr
.constraint_transition(lv.is_cpu_cycle * op_flag * (nv.gas - lv.gas - cost));
}
}
}
fn eval_packed_init<P: PackedField>(
lv: &CpuColumnsView<P>,
nv: &CpuColumnsView<P>,
yield_constr: &mut ConstraintConsumer<P>,
) {
// `nv` is the first row that executes an instruction.
let filter = (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle;
// Set initial gas to zero.
yield_constr.constraint_transition(filter * nv.gas);
}
pub fn eval_packed<P: PackedField>(
lv: &CpuColumnsView<P>,
nv: &CpuColumnsView<P>,
yield_constr: &mut ConstraintConsumer<P>,
) {
eval_packed_accumulate(lv, nv, yield_constr);
eval_packed_init(lv, nv, yield_constr);
}
fn eval_ext_circuit_accumulate<F: RichField + Extendable<D>, const D: usize>(
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
lv: &CpuColumnsView<ExtensionTarget<D>>,
nv: &CpuColumnsView<ExtensionTarget<D>>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
// Is it an instruction that we constrain here?
// I.e., does it always cost a constant amount of gas?
let is_simple_instr = SIMPLE_OPCODES.into_iter().enumerate().fold(
builder.zero_extension(),
|cumul, (i, maybe_cost)| {
// Add flag `lv.op[i]` to the sum if `SIMPLE_OPCODES[i]` is `Some`.
match maybe_cost {
None => cumul,
Some(_) => builder.add_extension(lv.op[i], cumul),
}
},
);
let filter = builder.mul_extension(lv.is_cpu_cycle, is_simple_instr);
// How much gas did we use?
let gas_used = SIMPLE_OPCODES.into_iter().enumerate().fold(
builder.zero_extension(),
|cumul, (i, maybe_cost)| match maybe_cost {
None => cumul,
Some(cost) => {
let cost_ext = builder.constant_extension(F::from_canonical_u32(cost).into());
builder.mul_add_extension(lv.op[i], cost_ext, cumul)
}
},
);
let constr = {
let t = builder.add_extension(lv.gas, gas_used);
builder.sub_extension(nv.gas, t)
};
let filtered_constr = builder.mul_extension(filter, constr);
yield_constr.constraint_transition(builder, filtered_constr);
for (maybe_cost, op_flag) in izip!(SIMPLE_OPCODES.into_iter(), lv.op.into_iter()) {
if let Some(cost) = maybe_cost {
let filter = builder.mul_extension(lv.is_cpu_cycle, op_flag);
let nv_lv_diff = builder.sub_extension(nv.gas, lv.gas);
let constr = builder.arithmetic_extension(
F::ONE,
-F::from_canonical_u32(cost),
filter,
nv_lv_diff,
filter,
);
yield_constr.constraint_transition(builder, constr);
}
}
}
fn eval_ext_circuit_init<F: RichField + Extendable<D>, const D: usize>(
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
lv: &CpuColumnsView<ExtensionTarget<D>>,
nv: &CpuColumnsView<ExtensionTarget<D>>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
// `nv` is the first row that executes an instruction.
let filter = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle);
// Set initial gas to zero.
let constr = builder.mul_extension(filter, nv.gas);
yield_constr.constraint_transition(builder, constr);
}
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>>,
nv: &CpuColumnsView<ExtensionTarget<D>>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
eval_ext_circuit_accumulate(builder, lv, nv, yield_constr);
eval_ext_circuit_init(builder, lv, nv, yield_constr);
}

View File

@ -15,16 +15,16 @@ pub fn eval_packed_exit_kernel<P: PackedField>(
yield_constr: &mut ConstraintConsumer<P>,
) {
let input = lv.mem_channels[0].value;
let filter = lv.is_cpu_cycle * lv.op.exit_kernel;
// If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode
// flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the
// kernel to set them to zero).
yield_constr.constraint_transition(
lv.is_cpu_cycle * lv.op.exit_kernel * (input[0] - nv.program_counter),
);
yield_constr.constraint_transition(
lv.is_cpu_cycle * lv.op.exit_kernel * (input[1] - nv.is_kernel_mode),
);
// If we are executing `EXIT_KERNEL` then we simply restore the program counter, kernel mode
// flag, and gas counter. The middle 4 (32-bit) limbs are ignored (this is not part of the spec,
// but we trust the kernel to set them to zero).
yield_constr.constraint_transition(filter * (input[0] - nv.program_counter));
yield_constr.constraint_transition(filter * (input[1] - nv.is_kernel_mode));
yield_constr.constraint_transition(filter * (input[6] - nv.gas));
// High limb of gas must be 0 for convenient detection of overflow.
yield_constr.constraint(filter * input[7]);
}
pub fn eval_ext_circuit_exit_kernel<F: RichField + Extendable<D>, const D: usize>(
@ -47,6 +47,17 @@ pub fn eval_ext_circuit_exit_kernel<F: RichField + Extendable<D>, const D: usize
let kernel_constr = builder.sub_extension(input[1], nv.is_kernel_mode);
let kernel_constr = builder.mul_extension(filter, kernel_constr);
yield_constr.constraint_transition(builder, kernel_constr);
{
let diff = builder.sub_extension(input[6], nv.gas);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint_transition(builder, constr);
}
{
// High limb of gas must be 0 for convenient detection of overflow.
let constr = builder.mul_extension(filter, input[7]);
yield_constr.constraint(builder, constr);
}
}
pub fn eval_packed_jump_jumpi<P: PackedField>(

View File

@ -84,7 +84,7 @@ global syscall_jumptable:
JUMPTABLE panic // jumpi is implemented natively
JUMPTABLE panic // pc is implemented natively
JUMPTABLE sys_msize
JUMPTABLE panic // gas is implemented natively
JUMPTABLE sys_gas
JUMPTABLE panic // jumpdest is implemented natively
JUMPTABLE panic // 0x5c is an invalid opcode
JUMPTABLE panic // 0x5d is an invalid opcode

View File

@ -35,6 +35,7 @@ global sys_basefee:
global sys_sload:
global sys_sstore:
global sys_msize:
global sys_gas:
global sys_log0:
global sys_log1:
global sys_log2:

View File

@ -345,3 +345,24 @@
%endrep
// stack: a || b || c || d
%endmacro
// Charge gas.
// Arguments:
// stack[0]: gas to be charged
// stack[1]: syscall info
// Returns:
// new syscall info
%macro charge_gas
%shl_const(192)
ADD
%endmacro
// Charge gas and exit kernel code.
// Arguments:
// stack[0]: gas to be charged
// stack[1]: syscall info
// Returns: nothing
%macro charge_gas_and_exit
%charge_gas
EXIT_KERNEL
%endmacro

View File

@ -5,6 +5,7 @@ pub(crate) mod control_flow;
pub mod cpu_stark;
pub(crate) mod decode;
mod dup_swap;
mod gas;
mod jumps;
pub mod kernel;
pub(crate) mod membus;

View File

@ -80,7 +80,6 @@ const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsColumnsView {
pushes: true,
disable_other_channels: true,
}),
gas: None, // TODO
jumpdest: Some(StackBehavior {
num_pops: 0,
pushes: false,
@ -95,7 +94,6 @@ const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsColumnsView {
disable_other_channels: true,
}),
set_context: None, // SET_CONTEXT is special since it involves the old and the new stack.
consume_gas: None, // TODO
exit_kernel: Some(StackBehavior {
num_pops: 1,
pushes: false,

View File

@ -24,7 +24,7 @@ pub fn eval_packed<P: PackedField>(
nv: &CpuColumnsView<P>,
yield_constr: &mut ConstraintConsumer<P>,
) {
let filter = lv.is_cpu_cycle * lv.op.syscall;
let filter = lv.op.syscall;
// Look up the handler in memory
let code_segment = P::Scalar::from_canonical_usize(Segment::Code as usize);
@ -69,15 +69,20 @@ pub fn eval_packed<P: PackedField>(
yield_constr.constraint_transition(filter * (nv.is_kernel_mode - P::ONES));
// Maintain current context
yield_constr.constraint_transition(filter * (nv.context - lv.context));
// Reset gas counter to zero.
yield_constr.constraint_transition(filter * nv.gas);
// This memory channel is constrained in `stack.rs`.
let output = lv.mem_channels[NUM_GP_CHANNELS - 1].value;
// Push current PC + 1 to stack
// Push to stack: current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7).
yield_constr.constraint(filter * (output[0] - (lv.program_counter + P::ONES)));
// Push current kernel flag to stack (share register with PC)
yield_constr.constraint(filter * (output[1] - lv.is_kernel_mode));
yield_constr.constraint(filter * (output[6] - lv.gas));
// TODO: Range check `output[6]`.
yield_constr.constraint(filter * output[7]); // High limb of gas is zero.
// Zero the rest of that register
for &limb in &output[2..] {
for &limb in &output[2..6] {
yield_constr.constraint(filter * limb);
}
}
@ -88,7 +93,7 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
nv: &CpuColumnsView<ExtensionTarget<D>>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
let filter = builder.mul_extension(lv.is_cpu_cycle, lv.op.syscall);
let filter = lv.op.syscall;
// Look up the handler in memory
let code_segment = F::from_canonical_usize(Segment::Code as usize);
@ -177,24 +182,40 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint_transition(builder, constr);
}
// Reset gas counter to zero.
{
let constr = builder.mul_extension(filter, nv.gas);
yield_constr.constraint_transition(builder, constr);
}
// This memory channel is constrained in `stack.rs`.
let output = lv.mem_channels[NUM_GP_CHANNELS - 1].value;
// Push current PC + 1 to stack
// Push to stack: current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7).
{
let pc_plus_1 = builder.add_const_extension(lv.program_counter, F::ONE);
let diff = builder.sub_extension(output[0], pc_plus_1);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint(builder, constr);
}
// Push current kernel flag to stack (share register with PC)
{
let diff = builder.sub_extension(output[1], lv.is_kernel_mode);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint(builder, constr);
}
{
let diff = builder.sub_extension(output[6], lv.gas);
let constr = builder.mul_extension(filter, diff);
yield_constr.constraint(builder, constr);
}
// TODO: Range check `output[6]`.
{
// High limb of gas is zero.
let constr = builder.mul_extension(filter, output[7]);
yield_constr.constraint(builder, constr);
}
// Zero the rest of that register
for &limb in &output[2..] {
for &limb in &output[2..6] {
let constr = builder.mul_extension(filter, limb);
yield_constr.constraint(builder, constr);
}

View File

@ -1,5 +1,6 @@
use std::marker::PhantomData;
use ethereum_types::U256;
use itertools::Itertools;
use plonky2::field::extension::{Extendable, FieldExtension};
use plonky2::field::packed::PackedField;
@ -175,14 +176,14 @@ impl<F: RichField + Extendable<D>, const D: usize> MemoryStark<F, D> {
while next.address.virt - curr.address.virt - 1 > max_rc {
let mut dummy_address = curr.address;
dummy_address.virt += max_rc + 1;
let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0);
let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0, U256::zero());
memory_ops.push(dummy_read);
curr = dummy_read;
}
} else {
while next.timestamp - curr.timestamp > max_rc {
let dummy_read =
MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc);
MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc, curr.value);
memory_ops.push(dummy_read);
curr = dummy_read;
}
@ -311,8 +312,9 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
// Enumerate purportedly-ordered log.
for i in 0..8 {
yield_constr
.constraint(next_is_read * address_unchanged * (next_values[i] - values[i]));
yield_constr.constraint_transition(
next_is_read * address_unchanged * (next_values[i] - values[i]),
);
}
eval_lookups(vars, yield_constr, RANGE_CHECK_PERMUTED, COUNTER_PERMUTED)
@ -433,7 +435,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
let value_diff = builder.sub_extension(next_values[i], values[i]);
let zero_if_read = builder.mul_extension(address_unchanged, value_diff);
let read_constraint = builder.mul_extension(next_is_read, zero_if_read);
yield_constr.constraint(builder, read_constraint);
yield_constr.constraint_transition(builder, read_constraint);
}
eval_lookups_circuit(

50
evm/src/witness/gas.rs Normal file
View File

@ -0,0 +1,50 @@
use crate::witness::operation::Operation;
const KERNEL_ONLY_INSTR: u64 = 0;
const G_JUMPDEST: u64 = 1;
const G_BASE: u64 = 2;
const G_VERYLOW: u64 = 3;
const G_LOW: u64 = 5;
const G_MID: u64 = 8;
const G_HIGH: u64 = 10;
pub(crate) fn gas_to_charge(op: Operation) -> u64 {
use crate::arithmetic::BinaryOperator::*;
use crate::witness::operation::Operation::*;
match op {
Iszero => G_VERYLOW,
Not => G_VERYLOW,
Byte => G_VERYLOW,
Syscall(_) => KERNEL_ONLY_INSTR,
Eq => G_VERYLOW,
BinaryLogic(_) => G_VERYLOW,
BinaryArithmetic(Add) => G_VERYLOW,
BinaryArithmetic(Mul) => G_LOW,
BinaryArithmetic(Sub) => G_VERYLOW,
BinaryArithmetic(Div) => G_LOW,
BinaryArithmetic(Mod) => G_LOW,
BinaryArithmetic(Lt) => G_VERYLOW,
BinaryArithmetic(Gt) => G_VERYLOW,
Shl => G_VERYLOW,
Shr => G_VERYLOW,
BinaryArithmetic(AddFp254) => KERNEL_ONLY_INSTR,
BinaryArithmetic(MulFp254) => KERNEL_ONLY_INSTR,
BinaryArithmetic(SubFp254) => KERNEL_ONLY_INSTR,
TernaryArithmetic(_) => G_MID,
KeccakGeneral => KERNEL_ONLY_INSTR,
ProverInput => KERNEL_ONLY_INSTR,
Pop => G_BASE,
Jump => G_MID,
Jumpi => G_HIGH,
Pc => G_BASE,
Jumpdest => G_JUMPDEST,
Push(_) => G_VERYLOW,
Dup(_) => G_VERYLOW,
Swap(_) => G_VERYLOW,
GetContext => KERNEL_ONLY_INSTR,
SetContext => KERNEL_ONLY_INSTR,
ExitKernel => KERNEL_ONLY_INSTR,
MloadGeneral => KERNEL_ONLY_INSTR,
MstoreGeneral => KERNEL_ONLY_INSTR,
}
}

View File

@ -94,13 +94,13 @@ impl MemoryOp {
}
}
pub(crate) fn new_dummy_read(address: MemoryAddress, timestamp: usize) -> Self {
pub(crate) fn new_dummy_read(address: MemoryAddress, timestamp: usize, value: U256) -> Self {
Self {
filter: false,
timestamp,
address,
kind: MemoryOpKind::Read,
value: U256::zero(),
value,
}
}

View File

@ -1,4 +1,5 @@
mod errors;
mod gas;
pub(crate) mod memory;
mod operation;
pub(crate) mod state;

View File

@ -14,8 +14,8 @@ use crate::memory::segments::Segment;
use crate::witness::errors::ProgramError;
use crate::witness::memory::{MemoryAddress, MemoryOp};
use crate::witness::util::{
keccak_sponge_log, mem_read_code_with_log_and_fill, mem_read_gp_with_log_and_fill,
mem_write_gp_log_and_fill, stack_pop_with_log_and_fill, stack_push_log_and_fill,
keccak_sponge_log, mem_read_gp_with_log_and_fill, mem_write_gp_log_and_fill,
stack_pop_with_log_and_fill, stack_push_log_and_fill,
};
use crate::{arithmetic, logic};
@ -37,14 +37,12 @@ pub(crate) enum Operation {
Jump,
Jumpi,
Pc,
Gas,
Jumpdest,
Push(u8),
Dup(u8),
Swap(u8),
GetContext,
SetContext,
ConsumeGas,
ExitKernel,
MloadGeneral,
MstoreGeneral,
@ -323,8 +321,6 @@ pub(crate) fn generate_push<F: Field>(
let code_context = state.registers.code_context();
let num_bytes = n as usize + 1;
let initial_offset = state.registers.program_counter + 1;
let offsets = initial_offset..initial_offset + num_bytes;
let mut addrs = offsets.map(|offset| MemoryAddress::new(code_context, Segment::Code, offset));
// First read val without going through `mem_read_with_log` type methods, so we can pass it
// to stack_push_log_and_fill.
@ -344,33 +340,9 @@ pub(crate) fn generate_push<F: Field>(
let val = U256::from_big_endian(&bytes);
let write = stack_push_log_and_fill(state, &mut row, val)?;
// In the first cycle, we read up to NUM_GP_CHANNELS - 1 bytes, leaving the last GP channel
// to push the result.
for (i, addr) in (&mut addrs).take(NUM_GP_CHANNELS - 1).enumerate() {
let (_, read) = mem_read_gp_with_log_and_fill(i, addr, state, &mut row);
state.traces.push_memory(read);
}
state.traces.push_memory(write);
state.traces.push_cpu(row);
// In any subsequent cycles, we read up to 1 + NUM_GP_CHANNELS bytes.
for mut addrs_chunk in &addrs.chunks(1 + NUM_GP_CHANNELS) {
let mut row = CpuColumnsView::default();
row.is_cpu_cycle = F::ONE;
row.op.push = F::ONE;
let first_addr = addrs_chunk.next().unwrap();
let (_, first_read) = mem_read_code_with_log_and_fill(first_addr, state, &mut row);
state.traces.push_memory(first_read);
for (i, addr) in addrs_chunk.enumerate() {
let (_, read) = mem_read_gp_with_log_and_fill(i, addr, state, &mut row);
state.traces.push_memory(read);
}
state.traces.push_cpu(row);
}
Ok(())
}
@ -530,6 +502,10 @@ pub(crate) fn generate_syscall<F: Field>(
state: &mut GenerationState<F>,
mut row: CpuColumnsView<F>,
) -> Result<(), ProgramError> {
if TryInto::<u32>::try_into(state.registers.gas_used).is_err() {
panic!();
}
let handler_jumptable_addr = KERNEL.global_labels["syscall_jumptable"];
let handler_addr_addr =
handler_jumptable_addr + (opcode as usize) * (BYTES_PER_OFFSET as usize);
@ -557,12 +533,14 @@ pub(crate) fn generate_syscall<F: Field>(
let new_program_counter = handler_addr.as_usize();
let syscall_info = U256::from(state.registers.program_counter + 1)
+ (U256::from(u64::from(state.registers.is_kernel)) << 32);
+ (U256::from(u64::from(state.registers.is_kernel)) << 32)
+ (U256::from(state.registers.gas_used) << 192);
let log_out = stack_push_log_and_fill(state, &mut row, syscall_info)?;
state.registers.program_counter = new_program_counter;
log::debug!("Syscall to {}", KERNEL.offset_name(new_program_counter));
state.registers.is_kernel = true;
state.registers.gas_used = 0;
state.traces.push_memory(log_in0);
state.traces.push_memory(log_in1);
@ -601,9 +579,14 @@ pub(crate) fn generate_exit_kernel<F: Field>(
let is_kernel_mode_val = (kexit_info_u64 >> 32) as u32;
assert!(is_kernel_mode_val == 0 || is_kernel_mode_val == 1);
let is_kernel_mode = is_kernel_mode_val != 0;
let gas_used_val = kexit_info.0[3];
if TryInto::<u32>::try_into(gas_used_val).is_err() {
panic!();
}
state.registers.program_counter = program_counter;
state.registers.is_kernel = is_kernel_mode;
state.registers.gas_used = gas_used_val;
log::debug!(
"Exiting to {}, is_kernel={}",
KERNEL.offset_name(program_counter),

View File

@ -8,6 +8,7 @@ pub struct RegistersState {
pub is_kernel: bool,
pub stack_len: usize,
pub context: usize,
pub gas_used: u64,
}
impl RegistersState {
@ -27,6 +28,7 @@ impl Default for RegistersState {
is_kernel: true,
stack_len: 0,
context: 0,
gas_used: 0,
}
}
}

View File

@ -6,6 +6,7 @@ use crate::cpu::kernel::aggregator::KERNEL;
use crate::generation::state::GenerationState;
use crate::memory::segments::Segment;
use crate::witness::errors::ProgramError;
use crate::witness::gas::gas_to_charge;
use crate::witness::memory::MemoryAddress;
use crate::witness::operation::*;
use crate::witness::state::RegistersState;
@ -106,7 +107,6 @@ fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramErr
(0x57, _) => Ok(Operation::Jumpi),
(0x58, _) => Ok(Operation::Pc),
(0x59, _) => Ok(Operation::Syscall(opcode)),
(0x5a, _) => Ok(Operation::Gas),
(0x5b, _) => Ok(Operation::Jumpdest),
(0x60..=0x7f, _) => Ok(Operation::Push(opcode & 0x1f)),
(0x80..=0x8f, _) => Ok(Operation::Dup(opcode & 0xf)),
@ -128,7 +128,6 @@ fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramErr
(0xf5, _) => Ok(Operation::Syscall(opcode)),
(0xf6, true) => Ok(Operation::GetContext),
(0xf7, true) => Ok(Operation::SetContext),
(0xf8, true) => Ok(Operation::ConsumeGas),
(0xf9, true) => Ok(Operation::ExitKernel),
(0xfa, _) => Ok(Operation::Syscall(opcode)),
(0xfb, true) => Ok(Operation::MloadGeneral),
@ -177,11 +176,9 @@ fn fill_op_flag<F: Field>(op: Operation, row: &mut CpuColumnsView<F>) {
Operation::Jump => &mut flags.jump,
Operation::Jumpi => &mut flags.jumpi,
Operation::Pc => &mut flags.pc,
Operation::Gas => &mut flags.gas,
Operation::Jumpdest => &mut flags.jumpdest,
Operation::GetContext => &mut flags.get_context,
Operation::SetContext => &mut flags.set_context,
Operation::ConsumeGas => &mut flags.consume_gas,
Operation::ExitKernel => &mut flags.exit_kernel,
Operation::MloadGeneral => &mut flags.mload_general,
Operation::MstoreGeneral => &mut flags.mstore_general,
@ -215,11 +212,9 @@ fn perform_op<F: Field>(
Operation::Jump => generate_jump(state, row)?,
Operation::Jumpi => generate_jumpi(state, row)?,
Operation::Pc => generate_pc(state, row)?,
Operation::Gas => todo!(),
Operation::Jumpdest => generate_jumpdest(state, row)?,
Operation::GetContext => generate_get_context(state, row)?,
Operation::SetContext => generate_set_context(state, row)?,
Operation::ConsumeGas => todo!(),
Operation::ExitKernel => generate_exit_kernel(state, row)?,
Operation::MloadGeneral => generate_mload_general(state, row)?,
Operation::MstoreGeneral => generate_mstore_general(state, row)?,
@ -232,6 +227,8 @@ fn perform_op<F: Field>(
_ => 1,
};
state.registers.gas_used += gas_to_charge(op);
Ok(())
}
@ -242,6 +239,7 @@ fn try_perform_instruction<F: Field>(state: &mut GenerationState<F>) -> Result<(
row.context = F::from_canonical_usize(state.registers.context);
row.program_counter = F::from_canonical_usize(state.registers.program_counter);
row.is_kernel_mode = F::from_bool(state.registers.is_kernel);
row.gas = F::from_canonical_u64(state.registers.gas_used);
row.stack_len = F::from_canonical_usize(state.registers.stack_len);
let opcode = read_code_memory(state, &mut row);