Skip to content

Commit 720fd88

Browse files
jeanmonAztecBot
authored and
AztecBot
committed
feat(avm): vm2 bitwise subtrace (#11473)
1 parent 2b718de commit 720fd88

33 files changed

+1282
-114
lines changed

cpp/pil/vm2/bitwise.pil

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
namespace bitwise;
2+
3+
// Trace example for a AND b == c of type u32
4+
// a = 0x52488425
5+
// b = 0xC684486C (We omit acc_ib and ic_byte as it follows the very same behavior as for a and c)
6+
// c = 0x42000024
7+
//
8+
// ctr sel start last acc_ia acc_ic ia_byte ic_byte
9+
// 4 1 1 0 0x52488425 0x42000024 0x25 0x24
10+
// 3 1 0 0 0x524884 0x420000 0x84 0x00
11+
// 2 1 0 0 0x5248 0x4200 0x48 0x00
12+
// 1 1 0 1 0x52 0x42 0x52 0x42
13+
14+
// Selector for Bitwise Operation
15+
pol commit sel;
16+
sel * (1 - sel) = 0;
17+
18+
// No relations will be checked if this identity is satisfied.
19+
#[skippable_if]
20+
sel + last = 0; // They are both boolean so it corresponds to sel == 0 AND last == 0.
21+
22+
pol commit start; // Identifies when we want to capture the output to the main trace.
23+
// No need to constrain start to be a boolean, we only need it to select
24+
// the row to pass values with execution trace.
25+
26+
// To support dynamically sized memory operands we use a counter against a lookup
27+
// This decrementing counter goes from [TAG_LEN, 0] where TAG_LEN is the number of bytes in the
28+
// corresponding integer. i.e. TAG_LEN is between 1 (U1/U8) and 16 (U128).
29+
// Consistency can be achieved with a lookup table between the tag and precomputed.integral_tag_length
30+
pol commit ctr;
31+
32+
// last is a boolean which serves to mark the end of the computation (end of latch)
33+
pol commit last;
34+
last * (1 - last) = 0;
35+
36+
// This is the tag {1,2,3,4,5,6} (restricted to not be a field)
37+
// Operations over FF are not supported, it is assumed this exclusion is handled
38+
// outside of this subtrace.
39+
// Constraints come from equiv to main_trace
40+
pol commit tag;
41+
42+
// Byte recomposition column, the value in these columns are part of the equivalence
43+
// check to main wherever Start is set to 1.
44+
pol commit acc_ia;
45+
pol commit acc_ib;
46+
pol commit acc_ic;
47+
48+
// Little Endian bitwise decomposition of accumulators (which are processed top-down),
49+
// constrained to be U8 given by the lookup to the byte_lookup
50+
pol commit ia_byte;
51+
pol commit ib_byte;
52+
pol commit ic_byte;
53+
54+
// Selectors for bitwise operations, correctness checked by permutation to the main trace.
55+
// Op Id is restricted to be the same during the same computation (i.e. between Starts)
56+
pol commit op_id;
57+
58+
#[BITW_OP_ID_REL]
59+
(op_id' - op_id) * (1 - last) = 0;
60+
61+
#[BITW_CTR_DECREMENT]
62+
// Note: sel factor is required for an empty row to satisfy this relation
63+
sel * (ctr' - ctr + 1) * (1 - last) = 0;
64+
65+
// sel is set to 1 if and only if ctr != 0. (and sel == 0 <==> ctr == 0)
66+
// sel is a boolean that is set to 1 if ctr != 0.
67+
// This is checked by following relation and utilising inverse of ctr: ctr_inv
68+
pol commit ctr_inv;
69+
70+
#[BITW_SEL_CTR_NON_ZERO]
71+
ctr * ((1 - sel) * (1 - ctr_inv) + ctr_inv) - sel = 0;
72+
73+
// Similarly, we prove that last == 1 <==> ctr - 1 == 0 <==> ctr == 1
74+
// Note: sel factor is required for an empty row to satisfy this relation
75+
pol commit ctr_min_one_inv;
76+
#[BITW_LAST_FOR_CTR_ONE]
77+
sel * ((ctr - 1) * (last * (1 - ctr_min_one_inv) + ctr_min_one_inv) + last - 1) = 0;
78+
79+
// Forces accumulator to initialize with ia_byte, ib_byte, and ic_byte
80+
#[BITW_INIT_A]
81+
last * (acc_ia - ia_byte) = 0;
82+
#[BITW_INIT_B]
83+
last * (acc_ib - ib_byte) = 0;
84+
#[BITW_INIT_C]
85+
last * (acc_ic - ic_byte) = 0;
86+
87+
#[BITW_ACC_REL_A]
88+
(acc_ia - ia_byte - 256 * acc_ia') * (1 - last) = 0;
89+
#[BITW_ACC_REL_B]
90+
(acc_ib - ib_byte - 256 * acc_ib') * (1 - last) = 0;
91+
#[BITW_ACC_REL_C]
92+
(acc_ic - ic_byte - 256 * acc_ic') * (1 - last) = 0;
93+
94+
#[LOOKUP_BITW_BYTE_LENGTHS]
95+
start {tag, ctr}
96+
in
97+
precomputed.sel_integral_tag {precomputed.clk, precomputed.integral_tag_length};
98+
99+
#[LOOKUP_BITW_BYTE_OPERATIONS]
100+
sel {op_id, ia_byte, ib_byte, ic_byte}
101+
in
102+
precomputed.sel_bitwise {precomputed.bitwise_op_id, precomputed.bitwise_input_a, precomputed.bitwise_input_b, precomputed.bitwise_output};
103+
104+
// TODOs: See two following paragraphs
105+
106+
// ################################################
107+
// Alternative implementation as potential speed-up
108+
// ################################################
109+
//
110+
// In vm1, we had an approach which requires one extra row per bitwise operation but
111+
// had 2 less columns and #[BITW_CTR_DECREMENT] would have degree 0 and the degree 4 relation
112+
// #[BITW_LAST_FOR_CTR_ONE] is not present.
113+
// The main difference is that we decrement ctr down to zero (extra line) and impose an initialization
114+
// condition for acc_ia, acc_ib, acc_ic to be zero on this last row.
115+
// Column last can be removed and sel is used instead of (1 - last).
116+
// Note that sel == 0 on last row of each operation, but the skippable condition
117+
// remains valid as the last row will be empty with our witness generator.
118+
//
119+
// It might be worth to measure the difference among both approaches.
120+
121+
122+
// ################################################
123+
// Recycling of bitwise operations of prefixes
124+
// ################################################
125+
//
126+
// Observation: If two inputs are prefixes of other inputs which are already present in the
127+
// trace, then we could retrieve the result as a truncated trace of the larger.
128+
//
129+
// For instance, re-using example at the top, we consider the U16 and computation over
130+
// a = 0x5248
131+
// b = 0xC684
132+
// c = 0x4200
133+
// Then, we should activate the start selector where ctr == 2, and the following rows
134+
// represent a valid trace for this computation.
135+
// It is not clear if this would lead to some speed-up in practice.

cpp/pil/vm2/execution.pil

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ include "bc_hashing.pil";
55
include "bc_retrieval.pil";
66
include "instr_fetching.pil";
77
include "range_check.pil";
8+
include "bitwise.pil";
89
include "precomputed.pil";
910
include "sha256.pil";
1011

cpp/pil/vm2/precomputed.pil

+11-1
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,14 @@ pol constant sha256_compression_round_constant;
3737
// 3 -> 111
3838
// You get it. It can be extended up to 254 bits if needed.
3939
pol constant sel_unary;
40-
pol constant as_unary;
40+
pol constant as_unary;
41+
42+
// A mapping between a non-FF MemoryTag value and their respective byte lengths:
43+
// {U1: 1, U8: 1, U16: 2, ... , U128: 16}
44+
// The enum values of MemoryTag are present in column clk.
45+
pol constant sel_integral_tag; // Toggle row with clk == U1,U8,U16,...,U128
46+
pol constant integral_tag_length; // Columns for the byte length 1,1,2,...,16;
47+
48+
// Remark: A potential optimization may consist in using sel_bitwise instead of sel_integral_mem_tag.
49+
// However, it would extend this lookup table with pairs such as (0,0), (7,0), (8,0) which is
50+
// not without any danger.

cpp/src/barretenberg/relations/relation_types.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ template <typename RelationImpl> class Relation : public RelationImpl {
188188
NUM_KEYS - 1>;
189189
using SumcheckTupleOfUnivariatesOverSubrelations =
190190
TupleOfUnivariates<FF, RelationImpl::SUBRELATION_PARTIAL_LENGTHS>;
191-
// The containter constructor for sumcheck univariates corresponding to each subrelation in ZK Flavor's relations
191+
// The container constructor for sumcheck univariates corresponding to each subrelation in ZK Flavor's relations
192192
using ZKSumcheckTupleOfUnivariatesOverSubrelations =
193193
TupleOfUnivariates<FF, compute_zk_partial_subrelation_lengths<RelationImpl>()>;
194194

cpp/src/barretenberg/vm2/common/constants.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,11 @@ namespace bb::avm2 {
66

77
constexpr uint32_t CIRCUIT_SUBGROUP_SIZE = 1 << 21;
88

9+
// Also used for op_id in the circuit trace
10+
enum class BitwiseOperation : uint8_t {
11+
AND = 0,
12+
OR = 1,
13+
XOR = 2,
14+
};
15+
916
} // namespace bb::avm2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include "barretenberg/vm2/common/memory_types.hpp"
2+
3+
namespace bb::avm2 {
4+
5+
uint8_t integral_tag_length(MemoryTag tag)
6+
{
7+
switch (tag) {
8+
case MemoryTag::U1:
9+
case MemoryTag::U8:
10+
return 1;
11+
case MemoryTag::U16:
12+
return 2;
13+
case MemoryTag::U32:
14+
return 4;
15+
case MemoryTag::U64:
16+
return 8;
17+
case MemoryTag::U128:
18+
return 16;
19+
case MemoryTag::FF:
20+
throw std::runtime_error("FF is not an integral tag");
21+
}
22+
23+
assert(false && "This should not happen");
24+
return 0; // Should never happen. To please the compiler.
25+
}
26+
27+
} // namespace bb::avm2

cpp/src/barretenberg/vm2/common/memory_types.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ using MemoryAddress = uint32_t;
2020
using MemoryValue = FF;
2121
constexpr auto MemoryAddressTag = MemoryTag::U32;
2222

23+
uint8_t integral_tag_length(MemoryTag tag);
24+
2325
} // namespace bb::avm2

0 commit comments

Comments
 (0)