Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
alu.cpp
Go to the documentation of this file.
2
3#include <cstdint>
4#include <stdexcept>
5#include <string>
6
13
14namespace bb::avm2::simulation {
15
25{
26 try {
27 MemoryValue c = a + b; // This will throw if the tags do not match.
28 events.emit({ .operation = AluOperation::ADD, .a = a, .b = b, .c = c });
29 return c;
30 } catch (const TagMismatchException& e) {
31 events.emit({ .operation = AluOperation::ADD, .a = a, .b = b, .error = true });
32 throw AluException("ADD, " + std::string(e.what()));
33 }
34}
35
45{
46 try {
47 MemoryValue c = a - b; // This will throw if the tags do not match.
48 events.emit({ .operation = AluOperation::SUB, .a = a, .b = b, .c = c });
49 return c;
50 } catch (const TagMismatchException& e) {
51 events.emit({ .operation = AluOperation::SUB, .a = a, .b = b, .error = true });
52 throw AluException("SUB, " + std::string(e.what()));
53 }
54}
55
65{
66 try {
67 MemoryValue c = a * b; // This will throw if the tags do not match.
68 uint256_t a_int = static_cast<uint256_t>(a.as_ff());
69 uint256_t b_int = static_cast<uint256_t>(b.as_ff());
70 MemoryTag tag = a.get_tag();
71 uint256_t c_hi = 0;
72 if (tag == MemoryTag::U128) {
73 // For u128, we decompose a and b into 64-bit chunks and discard the highest bits given by the product:
74 auto a_decomp = decompose_128(static_cast<uint128_t>(a.as_ff()));
75 auto b_decomp = decompose_128(static_cast<uint128_t>(b.as_ff()));
76 range_check.assert_range(a_decomp.lo, 64);
77 range_check.assert_range(a_decomp.hi, 64);
78 range_check.assert_range(b_decomp.lo, 64);
79 range_check.assert_range(b_decomp.hi, 64);
80 uint256_t hi_operand = static_cast<uint256_t>(a_decomp.hi) * static_cast<uint256_t>(b_decomp.hi);
81 // c_hi = (old_c_hi - a_hi * b_hi) % 2^64
82 // Make use of x % pow_of_two = x & (pow_of_two - 1)
83 c_hi = (((a_int * b_int) >> 128) - hi_operand) & static_cast<uint256_t>(MASK_64);
84 } else if (tag != MemoryTag::FF) {
85 c_hi = (a_int * b_int) >> static_cast<uint256_t>(get_tag_bits(tag));
86 }
87
88 range_check.assert_range(static_cast<uint128_t>(c_hi), 64);
89 events.emit({ .operation = AluOperation::MUL, .a = a, .b = b, .c = c });
90 return c;
91 } catch (const TagMismatchException& e) {
92 events.emit({ .operation = AluOperation::MUL, .a = a, .b = b, .error = true });
93 throw AluException("MUL, " + std::string(e.what()));
94 }
95}
96
109{
110 try {
111 MemoryValue c = a / b; // This will throw if the tags do not match or if we divide by 0.
112 MemoryValue remainder = a - c * b;
113 MemoryTag tag = a.get_tag();
114
115 if (tag == MemoryTag::FF) {
116 // DIV on a field is not a valid operation, but should be recoverable.
117 // It comes under the umbrella of tag errors (like NOT) but MemoryValue c = a / b does
118 // not throw.
119 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .error = true });
120 throw AluException("DIV, Cannot perform integer division on a field element");
121 }
122
123 // Check remainder < b:
124 greater_than.gt(b, remainder);
125 if (tag == MemoryTag::U128) {
126 // For u128, we decompose c and b into 64 bit chunks and discard the highest bits given by the product:
127 auto c_decomp = decompose_128(static_cast<uint128_t>(c.as_ff()));
128 auto b_decomp = decompose_128(static_cast<uint128_t>(b.as_ff()));
129 range_check.assert_range(c_decomp.lo, 64);
130 range_check.assert_range(c_decomp.hi, 64);
131 range_check.assert_range(b_decomp.lo, 64);
132 range_check.assert_range(b_decomp.hi, 64);
133 }
134 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .c = c });
135 return c;
136 } catch (const TagMismatchException& e) {
137 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .error = true });
138 throw AluException("DIV, " + std::string(e.what()));
139 } catch (const DivisionByZero& e) {
140 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .error = true });
141 throw AluException("DIV, " + std::string(e.what()));
142 }
143}
144
157{
158 try {
159 MemoryValue c = a / b; // This will throw if the tags do not match or if we divide by 0.
160
161 if (a.get_tag() != MemoryTag::FF) {
162 // We cannot reach this case from execution because the tags are forced to be FF (see below*).
163 // It comes under the umbrella of tag errors (like NOT) but MemoryValue c = a / b does
164 // not throw.
165 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .error = true });
166 throw AluException("FDIV, Cannot perform field division on an integer");
167 }
168
169 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .c = c });
170 return c;
171 } catch (const TagMismatchException& e) {
172 // *This is unreachable from execution and exists to manage and test tag errors:
173 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .error = true });
174 throw AluException("FDIV, " + std::string(e.what()));
175 } catch (const DivisionByZero& e) {
176 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .error = true });
177 throw AluException("FDIV, " + std::string(e.what()));
178 }
179}
180
190{
191 // Brillig semantic enforces that tags match for EQ.
192 if (a.get_tag() != b.get_tag()) {
193 events.emit({ .operation = AluOperation::EQ, .a = a, .b = b, .error = true });
194 throw AluException("EQ, Tag mismatch between operands.");
195 }
196
197 MemoryValue c = MemoryValue::from<uint1_t>(a == b ? 1 : 0);
198 events.emit({ .operation = AluOperation::EQ, .a = a, .b = b, .c = c });
199 return c;
200}
201
211{
212 // Brillig semantic enforces that tags match for LT.
213 // This is special cased because comparison operators do not throw on tag mismatch.
214 if (a.get_tag() != b.get_tag()) {
215 events.emit({ .operation = AluOperation::LT, .a = a, .b = b, .error = true });
216 throw AluException("LT, Tag mismatch between operands.");
217 }
218 // Use the greater_than interface to check if b > a, which is the same as a < b.
219 bool res = greater_than.gt(b, a);
220 MemoryValue c = MemoryValue::from<uint1_t>(res);
221 events.emit({ .operation = AluOperation::LT, .a = a, .b = b, .c = c });
222 return c;
223}
224
234{
235 // Brillig semantic enforces that tags match for LTE.
236 if (a.get_tag() != b.get_tag()) {
237 events.emit({ .operation = AluOperation::LTE, .a = a, .b = b, .error = true });
238 throw AluException("LT, Tag mismatch between operands.");
239 }
240 // Note: the result of LTE is the opposite of GT
241 // Use the greater_than interface to check if a > b
242 bool res = greater_than.gt(a, b);
243 // The result of LTE is the opposite of the result of GT
244 MemoryValue c = MemoryValue::from<uint1_t>(!res);
245 events.emit({ .operation = AluOperation::LTE, .a = a, .b = b, .c = c });
246 return c;
247}
248
257{
258 try {
259 MemoryValue b = ~a; // Throws if the tag is not compatible with NOT operation (i.e. it is an FF type).
260 events.emit({ .operation = AluOperation::NOT, .a = a, .b = b });
261 return b;
262 } catch (const InvalidOperationTag& e) {
263 events.emit({ .operation = AluOperation::NOT, .a = a, .error = true });
264 throw AluException("NOT, " + std::string(e.what()));
265 }
266}
267
279{
280 try {
281 MemoryValue c = a << b; // This will throw if the tags do not match or are FF.
282 uint128_t a_num = static_cast<uint128_t>(a.as_ff());
283 uint128_t b_num = static_cast<uint128_t>(b.as_ff());
284 uint128_t max_bits = static_cast<uint128_t>(get_tag_bits(a.get_tag()));
285
286 bool overflow = b_num > max_bits;
287 uint128_t a_lo_bits = overflow ? max_bits : max_bits - b_num;
288 // We cast to uint256_t to be sure that the shift 1 << a_lo_bits has a defined behaviour.
289 // 1 << 128 is undefined behavior on uint128_t.
290 const uint128_t mask =
291 static_cast<uint128_t>((static_cast<uint256_t>(1) << uint256_t::from_uint128(a_lo_bits)) - 1);
292 // Make use of x % pow_of_two = x & (pow_of_two - 1)
293 uint128_t a_lo = overflow ? b_num - max_bits : a_num & mask;
294 uint128_t a_hi = a_lo_bits >= 128 ? 0 : a_num >> a_lo_bits; // 128-bit shift undefined behaviour guard.
295 uint128_t a_hi_bits = overflow ? max_bits : b_num;
296
297 range_check.assert_range(a_lo, static_cast<uint8_t>(a_lo_bits));
298 range_check.assert_range(a_hi, static_cast<uint8_t>(a_hi_bits));
299 events.emit({ .operation = AluOperation::SHL, .a = a, .b = b, .c = c });
300 return c;
301 } catch (const TagMismatchException& e) {
302 events.emit({ .operation = AluOperation::SHL, .a = a, .b = b, .error = true });
303 throw AluException("SHL, " + std::string(e.what()));
304 } catch (const InvalidOperationTag& e) {
305 events.emit({ .operation = AluOperation::SHL, .a = a, .b = b, .error = true });
306 throw AluException("SHL, " + std::string(e.what()));
307 }
308}
309
321{
322 try {
323 MemoryValue c = a >> b; // This will throw if the tags do not match or are FF.
324 uint128_t a_num = static_cast<uint128_t>(a.as_ff());
325 uint128_t b_num = static_cast<uint128_t>(b.as_ff());
326 uint128_t max_bits = static_cast<uint128_t>(get_tag_bits(a.get_tag()));
327
328 bool overflow = b_num > max_bits;
329 uint128_t a_lo_bits = overflow ? max_bits : b_num;
330 // We cast to uint256_t to be sure that the shift 1 << a_lo_bits has a defined behaviour.
331 // 1 << 128 is undefined behavior on uint128_t.
332 const uint128_t mask =
333 static_cast<uint128_t>((static_cast<uint256_t>(1) << uint256_t::from_uint128(a_lo_bits)) - 1);
334 // Make use of x % pow_of_two = x & (pow_of_two - 1)
335 uint128_t a_lo = overflow ? b_num - max_bits : a_num & mask;
336 uint128_t a_hi = a_lo_bits >= 128 ? 0 : a_num >> a_lo_bits; // 128-bit shift undefined behaviour guard.
337 uint128_t a_hi_bits = overflow ? max_bits : max_bits - b_num;
338
339 range_check.assert_range(a_lo, static_cast<uint8_t>(a_lo_bits));
340 range_check.assert_range(a_hi, static_cast<uint8_t>(a_hi_bits));
341 events.emit({ .operation = AluOperation::SHR, .a = a, .b = b, .c = c });
342 return c;
343 } catch (const TagMismatchException& e) {
344 events.emit({ .operation = AluOperation::SHR, .a = a, .b = b, .error = true });
345 throw AluException("SHR, " + std::string(e.what()));
346 } catch (const InvalidOperationTag& e) {
347 events.emit({ .operation = AluOperation::SHR, .a = a, .b = b, .error = true });
348 throw AluException("SHR, " + std::string(e.what()));
349 }
350}
351
360{
362
363 // Circuit leakage: range check for `mid` value defined by a = ic + mid * 2^dst_tag_bits + hi_128 * 2^128 and
364 // mid is (128 - dst_tag_bits) bits.
365 bool is_trivial = dst_tag == MemoryTag::FF || static_cast<uint256_t>(a) <= get_tag_max_value(dst_tag);
366 if (!is_trivial) {
367 uint128_t a_lo = 0;
368 if (static_cast<uint256_t>(a) >= (static_cast<uint256_t>(1) << 128)) {
369 // Canonical decomposition
370 U256Decomposition decomposition = field_gt.canon_dec(a);
371 a_lo = decomposition.lo;
372 } else {
373 a_lo = static_cast<uint128_t>(a);
374 }
375
376 // cpp standard on bitwise shifts:
377 // "If the value of rhs is negative or is not less than the number of bits in lhs, the behavior is undefined."
378 // For this reason, we handle the case where dst_tag is U128 separately as shifting of 128 bits is undefined.
379 const uint128_t mid = dst_tag == MemoryTag::U128 ? 0 : a_lo >> get_tag_bits(dst_tag);
380 range_check.assert_range(mid, 128 - get_tag_bits(dst_tag));
381 }
382
383 // We put dst_tag in b to have correct deduplication and also to encode it in the event.
384 // Note however that in alu subtrace, dst_tag will be set in ia_tag.
385 events.emit({ .operation = AluOperation::TRUNCATE,
387 .b = MemoryValue::from_tag(MemoryTag::FF, static_cast<uint8_t>(dst_tag)),
388 .c = c });
389 return c;
390}
391
392} // namespace bb::avm2::simulation
MemoryTag dst_tag
static TaggedValue from_tag_truncating(ValueTag tag, FF value)
static TaggedValue from_tag(ValueTag tag, FF value)
MemoryValue lte(const MemoryValue &a, const MemoryValue &b) override
Check if the first memory value is less than or equal to the second and emit an event of type AluEven...
Definition alu.cpp:233
MemoryValue fdiv(const MemoryValue &a, const MemoryValue &b) override
Perform field division on two memory values and emit an event of type AluEvent.
Definition alu.cpp:156
MemoryValue truncate(const FF &a, MemoryTag dst_tag) override
Truncate a field element to a specific memory tag and emit an event of type AluEvent.
Definition alu.cpp:359
MemoryValue eq(const MemoryValue &a, const MemoryValue &b) override
Check if two memory values are equal and emit an event of type AluEvent.
Definition alu.cpp:189
MemoryValue lt(const MemoryValue &a, const MemoryValue &b) override
Check if the first memory value is less than the second and emit an event of type AluEvent.
Definition alu.cpp:210
MemoryValue shl(const MemoryValue &a, const MemoryValue &b) override
Perform left shift operation on a memory value and emit an event of type AluEvent.
Definition alu.cpp:278
MemoryValue add(const MemoryValue &a, const MemoryValue &b) override
Add two memory values and emit an event of type AluEvent.
Definition alu.cpp:24
MemoryValue sub(const MemoryValue &a, const MemoryValue &b) override
Subtract two memory values and emit an event of type AluEvent.
Definition alu.cpp:44
FieldGreaterThanInterface & field_gt
Definition alu.hpp:40
MemoryValue mul(const MemoryValue &a, const MemoryValue &b) override
Multiply two memory values and emit an event of type AluEvent.
Definition alu.cpp:64
MemoryValue shr(const MemoryValue &a, const MemoryValue &b) override
Perform right shift operation on a memory value and emit an event of type AluEvent.
Definition alu.cpp:320
EventEmitterInterface< AluEvent > & events
Definition alu.hpp:42
MemoryValue op_not(const MemoryValue &a) override
Perform bitwise NOT operation on a memory value and emit an event of type AluEvent.
Definition alu.cpp:256
GreaterThanInterface & greater_than
Definition alu.hpp:39
MemoryValue div(const MemoryValue &a, const MemoryValue &b) override
Divide two memory values and emit an event of type AluEvent.
Definition alu.cpp:108
virtual U256Decomposition canon_dec(const FF &a)=0
virtual bool gt(const FF &a, const FF &b)=0
static constexpr uint256_t from_uint128(const uint128_t a) noexcept
Definition uint256.hpp:94
FF a
FF b
U128Decomposition decompose_128(const uint128_t &x)
constexpr uint128_t MASK_64
Definition constants.hpp:23
uint8_t get_tag_bits(ValueTag tag)
uint256_t get_tag_max_value(ValueTag tag)
AvmFlavorSettings::FF FF
Definition field.hpp:10
unsigned __int128 uint128_t
Definition serialize.hpp:44