Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.

Commit cb77caf

Browse files
author
Aurélien Nicolas
committed
memory_opt_opcodes: constraints for MLOAD/MSTORE/MSTORE8
1 parent 84d2a70 commit cb77caf

File tree

7 files changed

+341
-141
lines changed

7 files changed

+341
-141
lines changed

bus-mapping/src/circuit_input_builder/input_state_ref.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ impl<'a> CircuitInputStateRef<'a> {
276276
step: &mut ExecStep,
277277
address: MemoryAddress, //Caution: make sure this address = slot passing
278278
value: Word,
279+
// TODO: take value_prev as parameter.
279280
) -> Result<(), Error> {
280281
let call_id = self.call()?.call_id;
281282
self.push_op(step, RW::WRITE, MemoryWordOp::new(call_id, address, value));

bus-mapping/src/evm/opcodes/mload.rs

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,57 +28,35 @@ impl Opcode for Mload {
2828
// Manage first stack read at latest stack position
2929
state.stack_read(&mut exec_step, stack_position, stack_value_read)?;
3030

31-
// Read the memory
32-
let mut mem_read_addr: MemoryAddress = stack_value_read.try_into()?;
33-
// Accesses to memory that hasn't been initialized are valid, and return
34-
// 0.
31+
// Read the memory value from the next step of the trace.
3532
let mem_read_value = geth_steps[1].stack.last()?;
3633

37-
// TODO: get two memory words (slot, slot + 32) at address if offset != 0, otherwise get one
38-
// word at slot.
39-
let mut memory = state.call_ctx_mut()?.memory.clone();
40-
println!("before mload memory length is {}", memory.0.len());
41-
4234
let offset = stack_value_read.as_u64();
43-
// expand to offset + 64 to enusre addr_right_Word without out of boundary
44-
let minimal_length = offset + 64;
45-
46-
memory.extend_at_least(minimal_length as usize);
47-
4835
let shift = offset % 32;
4936
let slot = offset - shift;
50-
println!(
51-
"minimal_length {} , slot {}, shift {}, memory_length {}",
52-
minimal_length,
53-
slot,
54-
shift,
55-
memory.0.len()
56-
);
37+
println!("shift {}, slot {}", shift, slot);
5738

58-
let mut slot_bytes: [u8; 32] = [0; 32];
59-
slot_bytes.clone_from_slice(&memory.0[(slot as usize)..(slot as usize + 32)]);
39+
let (left_word, right_word) = {
40+
// Get the memory chunk that contains the word, starting at an aligned slot address.
41+
let slots_content = state.call_ctx()?.memory.read_chunk(slot.into(), 64.into());
6042

61-
let addr_left_Word = Word::from_big_endian(&slot_bytes);
62-
// TODO: edge case: if shift = 0, skip to read right word ?
63-
let mut word_right_bytes: [u8; 32] = [0; 32];
64-
slot_bytes.clone_from_slice(&memory.0[(slot + 32) as usize..(slot + 64) as usize]);
65-
66-
let addr_right_Word = Word::from_little_endian(&word_right_bytes);
43+
(
44+
Word::from_little_endian(&slots_content[..32]),
45+
Word::from_little_endian(&slots_content[32..64]),
46+
)
47+
};
6748

6849
// First stack write
69-
//
7050
state.stack_write(&mut exec_step, stack_position, mem_read_value)?;
7151

72-
// First mem read -> 32 MemoryOp generated.
73-
state.memory_read_word(&mut exec_step, slot.into(), addr_left_Word)?;
74-
state.memory_read_word(&mut exec_step, (slot + 32).into(), addr_right_Word)?;
52+
state.memory_read_word(&mut exec_step, slot.into(), left_word)?;
53+
state.memory_read_word(&mut exec_step, (slot + 32).into(), right_word)?;
7554

76-
// reconstruction
77-
// "minimal_length - 32" subtract 32 as actual expansion size
55+
// Expand memory if needed.
7856
state
7957
.call_ctx_mut()?
8058
.memory
81-
.extend_at_least((minimal_length - 32) as usize);
59+
.extend_at_least((offset + 32) as usize);
8260

8361
Ok(vec![exec_step])
8462
}

bus-mapping/src/evm/opcodes/mstore.rs

Lines changed: 31 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -30,102 +30,57 @@ impl<const IS_MSTORE8: bool> Opcode for Mstore<IS_MSTORE8> {
3030
let value_pos = geth_step.stack.nth_last_filled(1);
3131
state.stack_read(&mut exec_step, value_pos, value)?;
3232

33-
// First mem write -> 32 MemoryOp generated.
34-
let offset_addr: MemoryAddress = offset.try_into()?;
35-
// TODO: get two memory words (slot, slot + 32) at address if offset != 0, otherwise get one
36-
// word at slot.
37-
38-
let mut memory = state.call_ctx_mut()?.memory.clone();
39-
// expand memory size + 64 as slot
40-
let minimal_length = offset_addr.0
41-
+ if IS_MSTORE8 {
42-
1
43-
} else {
44-
64 /* 32 */
45-
};
46-
println!(
47-
"minimal_length {} , IS_MSTORE8 {}, memory_length {}, value {}",
48-
minimal_length,
49-
IS_MSTORE8,
50-
memory.0.len(),
51-
value
52-
);
53-
54-
memory.extend_at_least(minimal_length);
55-
56-
let offset_u64 = offset.as_u64();
33+
let offset_u64 = offset.as_u64() as usize;
5734
let shift = offset_u64 % 32;
5835
let slot = offset_u64 - shift;
5936
println!("shift {}, slot {}", shift, slot);
60-
// reconstruct memory with value
61-
match IS_MSTORE8 {
62-
true => {
63-
//let bytes= *value.to_le_bytes().to_vec();
64-
let val = *value.to_le_bytes().first().unwrap();
65-
let word_v8 = Word::from(val as u64);
66-
memory.0[offset_u64 as usize] = val;
67-
}
68-
false => {
69-
let bytes = value.to_be_bytes();
70-
memory[offset_u64 as usize..offset_u64 as usize + 32].copy_from_slice(&bytes);
71-
}
72-
}
7337

74-
// after memory construction, we can get slot_Word(left_Word), right_word to fill buss
75-
// mapping.
76-
let mut slot_bytes: [u8; 32] = [0; 32];
77-
let mut addr_left_Word = Word::zero();
38+
let (left_word, right_word) = {
39+
// Get the memory chunk that contains the word, starting at an aligned slot address.
40+
let mut slots_content = state.call_ctx()?.memory.read_chunk(slot.into(), 64.into());
41+
42+
// reconstruct memory with value
43+
match IS_MSTORE8 {
44+
true => {
45+
let val = *value.to_le_bytes().first().unwrap();
46+
slots_content[shift] = val;
47+
}
48+
false => {
49+
let bytes = value.to_be_bytes();
50+
slots_content[shift..shift + 32].copy_from_slice(&bytes);
51+
}
52+
}
7853

79-
if IS_MSTORE8 {
80-
let byte = *value.to_le_bytes().first().unwrap();
81-
addr_left_Word = Word::from(byte as u64);
82-
} else {
83-
slot_bytes.clone_from_slice(&memory.0[(slot as usize)..(slot as usize + 32)]);
84-
addr_left_Word = Word::from_big_endian(&slot_bytes);
85-
}
54+
// after memory construction, we can get the left and right words to fill bus mapping.
55+
(
56+
Word::from_little_endian(&slots_content[..32]),
57+
Word::from_little_endian(&slots_content[32..64]),
58+
)
59+
};
8660

87-
// TODO: edge case: if shift = 0, no need to right word
88-
let mut word_right_bytes: [u8; 32] = [0; 32];
89-
let cur_memory_size = memory.0.len();
90-
// when is_msotre8, skip word_right as mstore8 only affect one word.
61+
// memory write left word for mstore8 and mstore.
62+
state.memory_write_word(&mut exec_step, slot.into(), left_word)?;
9163

92-
// construct right word
93-
let addr_right_Word = Word::from_big_endian(&word_right_bytes);
64+
if !IS_MSTORE8 {
65+
// memory write right word for mstore
66+
state.memory_write_word(&mut exec_step, (slot + 32).into(), right_word)?;
9467

95-
// address = 100, slot = 96 shift = 4
96-
// value = address + 32 = 132
97-
// left word = slot ( 96...96+32 bytes) slot = 128...128+32 word(fill 0)
98-
match IS_MSTORE8 {
99-
true => {
100-
// memory write operation for mstore8
101-
state.memory_write_word(&mut exec_step, slot.into(), addr_left_Word)?;
102-
}
103-
false => {
104-
// lookup left word
105-
state.memory_write_word(&mut exec_step, slot.into(), addr_left_Word)?;
106-
// look up right word
107-
state.memory_write_word(&mut exec_step, (slot + 32).into(), addr_right_Word)?;
108-
}
68+
// TODO: edge case: if shift = 0, we could skip the right word?
10969
}
11070

11171
// reconstruction
112-
let offset = geth_step.stack.nth_last(0)?;
113-
let value = geth_step.stack.nth_last(1)?;
114-
let offset_addr: MemoryAddress = offset.try_into()?;
11572

116-
let minimal_length = offset_addr.0 + if IS_MSTORE8 { 1 } else { 32 };
73+
let minimal_length = offset_u64 + if IS_MSTORE8 { 1 } else { 32 };
11774
state.call_ctx_mut()?.memory.extend_at_least(minimal_length);
11875

119-
let mem_starts = offset_addr.0;
120-
12176
match IS_MSTORE8 {
12277
true => {
12378
let val = *value.to_le_bytes().first().unwrap();
124-
state.call_ctx_mut()?.memory.0[mem_starts] = val;
79+
state.call_ctx_mut()?.memory.0[offset_u64] = val;
12580
}
12681
false => {
12782
let bytes = value.to_be_bytes();
128-
state.call_ctx_mut()?.memory[mem_starts..mem_starts + 32].copy_from_slice(&bytes);
83+
state.call_ctx_mut()?.memory[offset_u64..offset_u64 + 32].copy_from_slice(&bytes);
12984
}
13085
}
13186

zkevm-circuits/src/evm_circuit/execution/memory.rs

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
evm_circuit::{
33
execution::ExecutionGadget,
4-
param::{N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE},
4+
param::{N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_CHUNK, N_BYTES_MEMORY_WORD_SIZE},
55
step::ExecutionState,
66
util::{
77
common_gadget::SameContextGadget,
@@ -11,13 +11,14 @@ use crate::{
1111
},
1212
from_bytes,
1313
math_gadget::IsEqualGadget,
14-
memory_gadget::{MemoryExpansionGadget, MemoryWordAddress},
14+
memory_gadget::{MemoryExpansionGadget, MemoryMask, MemoryWordAddress},
1515
not, CachedRegion, Cell, MemoryAddress, Word,
1616
},
1717
witness::{Block, Call, ExecStep, Transaction},
1818
},
1919
util::Expr,
2020
};
21+
use array_init::array_init;
2122
use eth_types::{evm_types::OpcodeId, Field, ToLittleEndian, U256};
2223
use halo2_proofs::{circuit::Value, plonk::Error};
2324

@@ -31,14 +32,20 @@ pub(crate) struct MemoryGadget<F> {
3132
//address: MemoryAddress<F>,
3233
address: MemoryWordAddress<F>,
3334
// consider move to MemoryWordAddress ?
35+
/// The value poped from or pushed to the stack.
3436
value: Word<F>,
37+
/// The left memory word read or written.
3538
value_left: Word<F>,
39+
/// The left memory word before the write.
40+
value_left_prev: Word<F>,
41+
/// The right memory word read or written.
3642
value_right: Word<F>,
43+
/// The right memory word before the write.
44+
value_right_prev: Word<F>,
3745
memory_expansion: MemoryExpansionGadget<F, 1, N_BYTES_MEMORY_WORD_SIZE>,
3846
is_mload: IsEqualGadget<F>,
3947
is_mstore8: IsEqualGadget<F>,
40-
// TODO: add mask
41-
// mask: [Cell<F>, 5]
48+
mask: MemoryMask<F>,
4249
}
4350

4451
impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
@@ -52,9 +59,12 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
5259
// In successful case the address must be in 5 bytes
5360
let address = cb.query_word_rlc();
5461
let address_word = MemoryWordAddress::construct(cb, address.clone());
62+
// TODO: we do not need the 32 bytes of the value, only the RLC.
5563
let value = cb.query_word_rlc();
5664
let value_left = cb.query_word_rlc();
65+
let value_left_prev = cb.query_word_rlc();
5766
let value_right = cb.query_word_rlc();
67+
let value_right_prev = cb.query_word_rlc();
5868

5969
// Check if this is an MLOAD
6070
let is_mload = IsEqualGadget::construct(cb, opcode.expr(), OpcodeId::MLOAD.expr());
@@ -72,6 +82,33 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
7282
[from_bytes::expr(&address.cells) + 1.expr() + (is_not_mstore8.clone() * 31.expr())],
7383
);
7484

85+
let shift_bits = address_word.shift_bits();
86+
87+
let mask = MemoryMask::construct(cb, &shift_bits, is_mstore8.expr());
88+
89+
// Extract word parts. Example with shift=4:
90+
// Left = Aaaa Bbbbbbbbbbbbbbbbbbbbbbbbbbbb
91+
// Right = Cccc Dddddddddddddddddddddddddddd
92+
let a = mask.get_left(cb, &value_left);
93+
let a_prev = mask.get_left(cb, &value_left_prev);
94+
let b = mask.get_right(cb, &value_left);
95+
let c = mask.get_left(cb, &value_right);
96+
let d = mask.get_right(cb, &value_right);
97+
let d_prev = mask.get_right(cb, &value_right_prev);
98+
99+
cb.require_equal("unchanged left bytes: L' & M == L & M", a, a_prev);
100+
101+
cb.require_equal("unchanged right bytes: R' & !M == R & !M", d, d_prev);
102+
103+
// Compute powers of the RLC challenge. These are used to shift bytes in equations below.
104+
let x = cb.challenges().evm_word();
105+
// X**32
106+
let x32 = MemoryMask::make_x32(x.clone());
107+
// X**(31-shift)
108+
let x31_shift = MemoryMask::make_x31_off(x.clone(), &shift_bits);
109+
// X**(32-shift)
110+
let x32_shift = x * x31_shift.clone();
111+
75112
// Stack operations
76113
// Pop the address from the stack
77114
cb.stack_pop(address.expr());
@@ -83,8 +120,12 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
83120
value.expr(),
84121
);
85122

86-
// TODO: replace with value_left = instruction.memory_lookup(RW.Write, addr_left)
87123
cb.condition(is_mstore8.expr(), |cb| {
124+
cb.require_equal(
125+
"W[0] * X³¹⁻ˢʰⁱᶠᵗ = B(X)",
126+
value.cells[0].expr() * x31_shift.clone(),
127+
b.clone(),
128+
);
88129
cb.memory_lookup_word(1.expr(), address_word.addr_left(), value_left.expr(), None);
89130
});
90131

@@ -95,6 +136,12 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
95136
// RW.Write if is_store == FQ(1) else RW.Read, addr_right
96137
// )
97138
cb.condition(is_not_mstore8, |cb| {
139+
cb.require_equal(
140+
"W(X) * X³²⁻ˢʰⁱᶠᵗ = B(X) * X³² + C(X)",
141+
value.expr() * x32_shift,
142+
b * x32 + c,
143+
);
144+
98145
cb.memory_lookup_word(
99146
is_store.clone(),
100147
address_word.addr_left(),
@@ -132,10 +179,13 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
132179
address: address_word,
133180
value,
134181
value_left,
182+
value_left_prev,
135183
value_right,
184+
value_right_prev,
136185
memory_expansion,
137186
is_mload,
138187
is_mstore8,
188+
mask,
139189
}
140190
}
141191

@@ -167,11 +217,6 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
167217
self.value
168218
.assign(region, offset, Some(value.to_le_bytes()))?;
169219

170-
// TODO: assign value_right
171-
let address_u64 = address.as_u64();
172-
let shift = address_u64 % 32;
173-
let slot = address_u64 - shift;
174-
println!("slot {}, shift {}", slot, shift);
175220
// Check if this is an MLOAD
176221
self.is_mload.assign(
177222
region,
@@ -187,6 +232,10 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
187232
F::from(OpcodeId::MSTORE8.as_u64()),
188233
)?;
189234

235+
let shift = address.as_u64() % 32;
236+
self.mask
237+
.assign(region, offset, shift, is_mstore8 == F::one())?;
238+
190239
// Memory expansion
191240
self.memory_expansion.assign(
192241
region,
@@ -203,11 +252,22 @@ impl<F: Field> ExecutionGadget<F> for MemoryGadget<F> {
203252
block.rws[step.rw_indices[3]].memory_word_value()
204253
};
205254

255+
// TODO: rm.
256+
println!("address: {:?}", address);
257+
println!("value (LE): {:?}", value.to_le_bytes());
258+
println!("value_left: {:?}", value_left.to_le_bytes());
259+
println!("value_right: {:?}", value_right.to_le_bytes());
260+
261+
// TODO: updated values for MSTORE.
206262
self.value_left
207263
.assign(region, offset, Some(value_left.to_le_bytes()))?;
264+
self.value_left_prev
265+
.assign(region, offset, Some(value_left.to_le_bytes()))?;
208266

209267
self.value_right
210268
.assign(region, offset, Some(value_right.to_le_bytes()))?;
269+
self.value_right_prev
270+
.assign(region, offset, Some(value_right.to_le_bytes()))?;
211271
Ok(())
212272
}
213273
}

0 commit comments

Comments
 (0)