Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
tagged_value.cpp
Go to the documentation of this file.
2
3#include <cassert>
4#include <functional>
5#include <stdexcept>
6#include <variant>
7
13
14namespace bb::avm2 {
15
16namespace {
17
18// Helper type for ad-hoc visitors. See https://en.cppreference.com/w/cpp/utility/variant/visit2.
19template <class... Ts> struct overloads : Ts... {
20 using Ts::operator()...;
21};
22// This is a deduction guide. Apparently not needed in C++20, but we somehow still need it.
23template <class... Ts> overloads(Ts...) -> overloads<Ts...>;
24
25template <std::integral T> T safe_shift_left(T a, T b)
26{
27 constexpr size_t bits = sizeof(T) * 8;
28 if (b >= bits) {
29 return static_cast<T>(0);
30 }
31 return static_cast<T>(a << b);
32}
33
34struct shift_left {
35 template <typename T> T operator()(const T& a, const T& b) const
36 {
37 if constexpr (std::is_same_v<T, uint1_t>) {
38 return static_cast<T>(b == uint1_t(0) ? a : uint1_t(0));
39 } else {
40 return safe_shift_left<T>(a, b);
41 }
42 }
43};
44
45template <std::integral T> T safe_shift_right(T a, T b)
46{
47 constexpr size_t bits = sizeof(T) * 8;
48 if (b >= bits) {
49 return static_cast<T>(0);
50 }
51 return static_cast<T>(a >> b);
52}
53
54struct shift_right {
55 template <typename T> T operator()(const T& a, const T& b) const
56 {
57 if constexpr (std::is_same_v<T, uint1_t>) {
58 return static_cast<T>(b == uint1_t(0) ? a : uint1_t(0));
59 } else {
60 return safe_shift_right<T>(a, b);
61 }
62 }
63};
64
65struct greater_than {
66 template <typename T, typename U> bool operator()(const T& a, const U& b) const
67 {
68 if constexpr (std::is_same_v<T, FF>) {
69 return static_cast<uint256_t>(a) > static_cast<uint256_t>(b);
70 } else {
71 return a > b;
72 }
73 }
74};
75
76struct less_than {
77 template <typename T, typename U> bool operator()(const T& a, const U& b) const
78 {
79 if constexpr (std::is_same_v<T, FF>) {
80 // It can be argued that this should be disallowed since total ordering within a
81 // finite field is not well-defined. But practically, we do compare field-sized things
82 // we just do so over the integers
83 return static_cast<uint256_t>(a) < static_cast<uint256_t>(b);
84 } else {
85 return a < b;
86 }
87 }
88};
89
90struct less_than_equal {
91 template <typename T, typename U> bool operator()(const T& a, const U& b) const
92 {
93 if constexpr (std::is_same_v<T, FF>) {
94 // See less_than for the reasoning.
95 return static_cast<uint256_t>(a) <= static_cast<uint256_t>(b);
96 } else {
97 return a <= b;
98 }
99 }
100};
101
102struct checked_divides {
103 template <typename T> auto operator()(T&& a, T&& b) const
104 {
105 if (b == static_cast<T>(0)) {
106 throw DivisionByZero("Dividing numeric value by zero");
107 }
109 }
110};
111
112template <typename Op>
113constexpr bool is_bitwise_operation_v =
116
117// Helper visitor for binary operations. These assume that the types are the same.
118template <typename Op> struct BinaryOperationVisitor {
119 template <typename T, typename U> TaggedValue::value_type operator()(const T& a, const U& b) const
120 {
121 if constexpr (std::is_same_v<T, U>) {
122 if constexpr (std::is_same_v<T, FF> && is_bitwise_operation_v<Op>) {
123 throw InvalidOperationTag("Bitwise operations not valid for FF");
124 } else {
125 // Note: IDK why this static_cast is needed, but if you remove it, operations seem to go through FF.
126 return static_cast<T>(Op{}(a, b));
127 }
128 } else {
129 throw TagMismatchException("Cannot perform operation between different types: " +
130 std::to_string(tag_for_type<T>()) + " and " + std::to_string(tag_for_type<U>()));
131 }
132 }
133};
134
135// Helper visitor for unary operations
136template <typename Op> struct UnaryOperationVisitor {
137 template <typename T> TaggedValue::value_type operator()(const T& a) const
138 {
139 if constexpr (std::is_same_v<T, FF> && is_bitwise_operation_v<Op>) {
140 throw InvalidOperationTag("Can't do unary bitwise operations on an FF");
141 } else {
142 // Note: IDK why this static_cast is needed, but if you remove it, operations seem to go through FF.
143 return static_cast<T>(Op{}(a));
144 }
145 }
146};
147
148// Helper visitor for comparison operations. The result is a bool
149template <typename Op> struct ComparisonOperationVisitor {
150 template <typename T, typename U> bool operator()(const T& a, const U& b) const
151 {
152 if constexpr (std::is_same_v<T, U>) {
153 return Op{}(a, b);
154 } else {
155 return false;
156 }
157 }
158};
159
160} // namespace
161
163{
164 switch (tag) {
165 case ValueTag::U1:
166 return 1;
167 case ValueTag::U8:
168 return 8;
169 case ValueTag::U16:
170 return 16;
171 case ValueTag::U32:
172 return 32;
173 case ValueTag::U64:
174 return 64;
175 case ValueTag::U128:
176 return 128;
177 case ValueTag::FF:
178 return 0; // It is more useful for this to be 0 in the circuit
179 }
180
181 assert(false && "Invalid tag");
182 return 0;
183}
184
186{
187 switch (tag) {
188 case ValueTag::U1:
189 return 1;
190 case ValueTag::U8:
191 case ValueTag::U16:
192 case ValueTag::U32:
193 case ValueTag::U64:
194 case ValueTag::U128:
195 return get_tag_bits(tag) / 8;
196 case ValueTag::FF:
197 return 0; // It is more useful for this to be 0 in the circuit
198 }
199
200 assert(false && "Invalid tag");
201 return 0;
202}
203
205{
206 switch (tag) {
207 case ValueTag::U1:
208 case ValueTag::U8:
209 case ValueTag::U16:
210 case ValueTag::U32:
211 case ValueTag::U64:
212 case ValueTag::U128:
213 return (uint256_t(1) << get_tag_bits(tag)) - 1;
214 case ValueTag::FF:
215 return FF::modulus - 1;
216 }
217
218 assert(false && "Invalid tag");
219 return 0;
220}
221
222// Constructor
226
228{
229 auto assert_bounds = [](const FF& value, uint8_t bits) {
230 if (static_cast<uint256_t>(value).get_msb() >= bits) {
231 throw std::runtime_error("Value out of bounds");
232 }
233 };
234
235 // Check bounds first.
236 switch (tag) {
237 case ValueTag::U1:
238 assert_bounds(value, 1);
239 break;
240 case ValueTag::U8:
241 assert_bounds(value, 8);
242 break;
243 case ValueTag::U16:
244 assert_bounds(value, 16);
245 break;
246 case ValueTag::U32:
247 assert_bounds(value, 32);
248 break;
249 case ValueTag::U64:
250 assert_bounds(value, 64);
251 break;
252 case ValueTag::U128:
253 assert_bounds(value, 128);
254 break;
255 case ValueTag::FF:
256 break;
257 }
258
259 return from_tag_truncating(tag, value);
260}
261
263{
264 switch (tag) {
265 case ValueTag::U1:
266 return TaggedValue(static_cast<uint1_t>(static_cast<uint8_t>(value) % 2));
267 case ValueTag::U8:
268 return TaggedValue(static_cast<uint8_t>(value));
269 case ValueTag::U16:
270 return TaggedValue(static_cast<uint16_t>(value));
271 case ValueTag::U32:
272 return TaggedValue(static_cast<uint32_t>(value));
273 case ValueTag::U64:
274 return TaggedValue(static_cast<uint64_t>(value));
275 case ValueTag::U128:
276 return TaggedValue(static_cast<uint128_t>(value));
277 case ValueTag::FF:
278 return TaggedValue(value);
279 default:
280 throw std::runtime_error("Invalid tag");
281 }
282}
283
284// Arithmetic operators
286{
287 return std::visit(BinaryOperationVisitor<std::plus<>>(), value, other.value);
288}
289
291{
292 return std::visit(BinaryOperationVisitor<std::minus<>>(), value, other.value);
293}
294
296{
297 return std::visit(BinaryOperationVisitor<std::multiplies<>>(), value, other.value);
298}
299
301{
302 return std::visit(BinaryOperationVisitor<checked_divides>(), value, other.value);
303}
304
305// Bitwise operators
307{
308 return std::visit(BinaryOperationVisitor<std::bit_and<>>(), value, other.value);
309}
310
312{
313 return std::visit(BinaryOperationVisitor<std::bit_or<>>(), value, other.value);
314}
315
317{
318 return std::visit(BinaryOperationVisitor<std::bit_xor<>>(), value, other.value);
319}
320
322{
323 return std::visit(UnaryOperationVisitor<std::bit_not<>>(), value);
324}
325
326// Shift Operations
328{
329 return std::visit(BinaryOperationVisitor<shift_left>(), value, other.value);
330}
331
333{
334 return std::visit(BinaryOperationVisitor<shift_right>(), value, other.value);
335}
336
337// Comparison Operators
338bool TaggedValue::operator<(const TaggedValue& other) const
339{
340 // Cannot use std::less<> here because we need to handle FF specially.
341 return std::visit(ComparisonOperationVisitor<less_than>(), value, other.value);
342}
343
344bool TaggedValue::operator>(const TaggedValue& other) const
345{
346 // Cannot use std::greater<> here because we need to handle FF specially.
347 return std::visit(ComparisonOperationVisitor<greater_than>(), value, other.value);
348}
349
350bool TaggedValue::operator<=(const TaggedValue& other) const
351{
352 // Cannot use std::less_equal<> here because we need to handle FF specially.
353 return std::visit(ComparisonOperationVisitor<less_than_equal>(), value, other.value);
354}
355
356bool TaggedValue::operator==(const TaggedValue& other) const
357{
358 // We can use std::equal_to here because it is defined for all types.
359 return std::visit(ComparisonOperationVisitor<std::equal_to<>>(), value, other.value);
360}
361
362bool TaggedValue::operator!=(const TaggedValue& other) const
363{
364 // We can use std::not_equal_to here because it is defined for all types.
365 return std::visit(ComparisonOperationVisitor<std::not_equal_to<>>(), value, other.value);
366}
367
369{
370 const auto visitor = overloads{ [](FF val) -> FF { return val; },
371 [](uint1_t val) -> FF { return val.value(); },
372 [](uint128_t val) -> FF { return uint256_t::from_uint128(val); },
373 [](auto&& val) -> FF { return val; } };
374
375 return std::visit(visitor, value);
376}
377
379{
380 // Converts the index of the variant to the tag.
381 static constexpr std::array<ValueTag, 7> index_to_tag = { ValueTag::U8, ValueTag::U1, ValueTag::U16,
383 ValueTag::FF };
384 return index_to_tag[value.index()];
385}
386
387std::string TaggedValue::to_string() const
388{
389 std::string v = std::visit(
390 overloads{ [](const FF& val) -> std::string { return field_to_string(val); },
391 [](const uint128_t& val) -> std::string { return field_to_string(uint256_t::from_uint128(val)); },
392 [](const uint1_t& val) -> std::string { return val.value() == 0 ? "0" : "1"; },
393 [](auto&& val) -> std::string { return std::to_string(val); } },
394 value);
395 return std::to_string(get_tag()) + "(" + v + ")";
396}
397
398} // namespace bb::avm2
399
401{
402 using namespace bb::avm2;
403 switch (tag) {
404 case ValueTag::U1:
405 return "U1";
406 case ValueTag::U8:
407 return "U8";
408 case ValueTag::U16:
409 return "U16";
410 case ValueTag::U32:
411 return "U32";
412 case ValueTag::U64:
413 return "U64";
414 case ValueTag::U128:
415 return "U128";
416 case ValueTag::FF:
417 return "FF";
418 default:
419 return "Unknown";
420 }
421}
422
424{
425 return value.to_string();
426}
TaggedValue operator>>(const TaggedValue &other) const
TaggedValue operator+(const TaggedValue &other) const
bool operator<=(const TaggedValue &other) const
TaggedValue operator~() const
std::variant< uint8_t, uint1_t, uint16_t, uint32_t, uint64_t, uint128_t, FF > value_type
bool operator>(const TaggedValue &other) const
TaggedValue operator/(const TaggedValue &other) const
static TaggedValue from_tag_truncating(ValueTag tag, FF value)
TaggedValue operator-(const TaggedValue &other) const
bool operator<(const TaggedValue &other) const
TaggedValue operator<<(const TaggedValue &other) const
bool operator==(const TaggedValue &other) const
TaggedValue operator|(const TaggedValue &other) const
static TaggedValue from_tag(ValueTag tag, FF value)
TaggedValue operator^(const TaggedValue &other) const
TaggedValue operator*(const TaggedValue &other) const
TaggedValue operator&(const TaggedValue &other) const
ValueTag get_tag() const
std::string to_string() const
bool operator!=(const TaggedValue &other) const
A 1-bit unsigned integer type.
Definition uint1.hpp:15
static constexpr uint256_t from_uint128(const uint128_t a) noexcept
Definition uint256.hpp:94
constexpr uint64_t get_msb() const
FF a
FF b
testing::StrictMock< MockGreaterThan > greater_than
uint8_t get_tag_bits(ValueTag tag)
uint256_t get_tag_max_value(ValueTag tag)
std::string field_to_string(const FF &ff)
Definition stringify.cpp:5
AvmFlavorSettings::FF FF
Definition field.hpp:10
uint8_t get_tag_bytes(ValueTag tag)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
unsigned __int128 uint128_t
Definition serialize.hpp:44
static constexpr uint256_t modulus