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 less_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 // It can be argued that this should be disallowed since total ordering within a
70 // finite field is not well-defined. But practically, we do compare field-sized things
71 // we just do so over the integers
72 return static_cast<uint256_t>(a) < static_cast<uint256_t>(b);
73 } else {
74 return a < b;
75 }
76 }
77};
78
79struct less_than_equal {
80 template <typename T, typename U> bool operator()(const T& a, const U& b) const
81 {
82 if constexpr (std::is_same_v<T, FF>) {
83 // See less_than for the reasoning.
84 return static_cast<uint256_t>(a) <= static_cast<uint256_t>(b);
85 } else {
86 return a <= b;
87 }
88 }
89};
90
91struct checked_divides {
92 template <typename T> auto operator()(T&& a, T&& b) const
93 {
94 if (b == static_cast<T>(0)) {
95 throw DivisionByZero("Dividing numeric value by zero");
96 }
98 }
99};
100
101template <typename Op>
102constexpr bool is_bitwise_operation_v =
105
106// Helper visitor for binary operations. These assume that the types are the same.
107template <typename Op> struct BinaryOperationVisitor {
108 template <typename T, typename U> TaggedValue::value_type operator()(const T& a, const U& b) const
109 {
110 if constexpr (std::is_same_v<T, U>) {
111 if constexpr (std::is_same_v<T, FF> && is_bitwise_operation_v<Op>) {
112 throw InvalidOperationTag("Bitwise operations not valid for FF");
113 } else {
114 // Note: IDK why this static_cast is needed, but if you remove it, operations seem to go through FF.
115 return static_cast<T>(Op{}(a, b));
116 }
117 } else {
118 throw TagMismatchException("Cannot perform operation between different types: " +
119 std::to_string(tag_for_type<T>()) + " and " + std::to_string(tag_for_type<U>()));
120 }
121 }
122};
123
124// Helper visitor for unary operations
125template <typename Op> struct UnaryOperationVisitor {
126 template <typename T> TaggedValue::value_type operator()(const T& a) const
127 {
128 if constexpr (std::is_same_v<T, FF> && is_bitwise_operation_v<Op>) {
129 throw InvalidOperationTag("Can't do unary bitwise operations on an FF");
130 } else {
131 // Note: IDK why this static_cast is needed, but if you remove it, operations seem to go through FF.
132 return static_cast<T>(Op{}(a));
133 }
134 }
135};
136
137// Helper visitor for comparison operations. The result is a bool
138template <typename Op> struct ComparisonOperationVisitor {
139 template <typename T, typename U> bool operator()(const T& a, const U& b) const
140 {
141 if constexpr (std::is_same_v<T, U>) {
142 return Op{}(a, b);
143 } else {
144 return false;
145 }
146 }
147};
148
149} // namespace
150
152{
153 switch (tag) {
154 case ValueTag::U1:
155 return 1;
156 case ValueTag::U8:
157 return 8;
158 case ValueTag::U16:
159 return 16;
160 case ValueTag::U32:
161 return 32;
162 case ValueTag::U64:
163 return 64;
164 case ValueTag::U128:
165 return 128;
166 case ValueTag::FF:
167 return 0; // It is more useful for this to be 0 in the circuit
168 }
169
170 assert(false && "Invalid tag");
171 return 0;
172}
173
175{
176 switch (tag) {
177 case ValueTag::U1:
178 return 1;
179 case ValueTag::U8:
180 case ValueTag::U16:
181 case ValueTag::U32:
182 case ValueTag::U64:
183 case ValueTag::U128:
184 return get_tag_bits(tag) / 8;
185 case ValueTag::FF:
186 return 0; // It is more useful for this to be 0 in the circuit
187 }
188
189 assert(false && "Invalid tag");
190 return 0;
191}
192
194{
195 switch (tag) {
196 case ValueTag::U1:
197 case ValueTag::U8:
198 case ValueTag::U16:
199 case ValueTag::U32:
200 case ValueTag::U64:
201 case ValueTag::U128:
202 return (uint256_t(1) << get_tag_bits(tag)) - 1;
203 case ValueTag::FF:
204 return FF::modulus - 1;
205 }
206
207 assert(false && "Invalid tag");
208 return 0;
209}
210
211// Constructor
215
217{
218 auto assert_bounds = [](const FF& value, uint8_t bits) {
219 if (static_cast<uint256_t>(value).get_msb() >= bits) {
220 throw std::runtime_error("Value out of bounds");
221 }
222 };
223
224 // Check bounds first.
225 switch (tag) {
226 case ValueTag::U1:
227 assert_bounds(value, 1);
228 break;
229 case ValueTag::U8:
230 assert_bounds(value, 8);
231 break;
232 case ValueTag::U16:
233 assert_bounds(value, 16);
234 break;
235 case ValueTag::U32:
236 assert_bounds(value, 32);
237 break;
238 case ValueTag::U64:
239 assert_bounds(value, 64);
240 break;
241 case ValueTag::U128:
242 assert_bounds(value, 128);
243 break;
244 case ValueTag::FF:
245 break;
246 }
247
248 return from_tag_truncating(tag, value);
249}
250
252{
253 switch (tag) {
254 case ValueTag::U1:
255 return TaggedValue(static_cast<uint1_t>(static_cast<uint8_t>(value) % 2));
256 case ValueTag::U8:
257 return TaggedValue(static_cast<uint8_t>(value));
258 case ValueTag::U16:
259 return TaggedValue(static_cast<uint16_t>(value));
260 case ValueTag::U32:
261 return TaggedValue(static_cast<uint32_t>(value));
262 case ValueTag::U64:
263 return TaggedValue(static_cast<uint64_t>(value));
264 case ValueTag::U128:
265 return TaggedValue(static_cast<uint128_t>(value));
266 case ValueTag::FF:
267 return TaggedValue(value);
268 default:
269 throw std::runtime_error("Invalid tag");
270 }
271}
272
273// Arithmetic operators
275{
276 return std::visit(BinaryOperationVisitor<std::plus<>>(), value, other.value);
277}
278
280{
281 return std::visit(BinaryOperationVisitor<std::minus<>>(), value, other.value);
282}
283
285{
286 return std::visit(BinaryOperationVisitor<std::multiplies<>>(), value, other.value);
287}
288
290{
291 return std::visit(BinaryOperationVisitor<checked_divides>(), value, other.value);
292}
293
294// Bitwise operators
296{
297 return std::visit(BinaryOperationVisitor<std::bit_and<>>(), value, other.value);
298}
299
301{
302 return std::visit(BinaryOperationVisitor<std::bit_or<>>(), value, other.value);
303}
304
306{
307 return std::visit(BinaryOperationVisitor<std::bit_xor<>>(), value, other.value);
308}
309
311{
312 return std::visit(UnaryOperationVisitor<std::bit_not<>>(), value);
313}
314
315// Shift Operations
317{
318 return std::visit(BinaryOperationVisitor<shift_left>(), value, other.value);
319}
320
322{
323 return std::visit(BinaryOperationVisitor<shift_right>(), value, other.value);
324}
325
326// Comparison Operators
327bool TaggedValue::operator<(const TaggedValue& other) const
328{
329 // Cannot use std::less<> here because we need to handle FF specially.
330 return std::visit(ComparisonOperationVisitor<less_than>(), value, other.value);
331}
332
333bool TaggedValue::operator<=(const TaggedValue& other) const
334{
335 // Cannot use std::less_equal<> here because we need to handle FF specially.
336 return std::visit(ComparisonOperationVisitor<less_than_equal>(), value, other.value);
337}
338
339bool TaggedValue::operator==(const TaggedValue& other) const
340{
341 // We can use std::equal_to here because it is defined for all types.
342 return std::visit(ComparisonOperationVisitor<std::equal_to<>>(), value, other.value);
343}
344
345bool TaggedValue::operator!=(const TaggedValue& other) const
346{
347 // We can use std::not_equal_to here because it is defined for all types.
348 return std::visit(ComparisonOperationVisitor<std::not_equal_to<>>(), value, other.value);
349}
350
352{
353 const auto visitor = overloads{ [](FF val) -> FF { return val; },
354 [](uint1_t val) -> FF { return val.value(); },
355 [](uint128_t val) -> FF { return uint256_t::from_uint128(val); },
356 [](auto&& val) -> FF { return val; } };
357
358 return std::visit(visitor, value);
359}
360
362{
363 // The tag is implicit in the type.
365 return ValueTag::U8;
367 return ValueTag::U1;
369 return ValueTag::U16;
371 return ValueTag::U32;
373 return ValueTag::U64;
375 return ValueTag::U128;
376 } else if (std::holds_alternative<FF>(value)) {
377 return ValueTag::FF;
378 } else {
379 throw std::runtime_error("Unknown value type");
380 }
381
382 assert(false && "This should never happen.");
383 return ValueTag::FF; // Only to make the compiler happy.
384}
385
386std::string TaggedValue::to_string() const
387{
388 std::string v = std::visit(
389 overloads{ [](const FF& val) -> std::string { return field_to_string(val); },
390 [](const uint128_t& val) -> std::string { return field_to_string(uint256_t::from_uint128(val)); },
391 [](const uint1_t& val) -> std::string { return val.value() == 0 ? "0" : "1"; },
392 [](auto&& val) -> std::string { return std::to_string(val); } },
393 value);
394 return std::to_string(get_tag()) + "(" + v + ")";
395}
396
397} // namespace bb::avm2
398
400{
401 using namespace bb::avm2;
402 switch (tag) {
403 case ValueTag::U1:
404 return "U1";
405 case ValueTag::U8:
406 return "U8";
407 case ValueTag::U16:
408 return "U16";
409 case ValueTag::U32:
410 return "U32";
411 case ValueTag::U64:
412 return "U64";
413 case ValueTag::U128:
414 return "U128";
415 case ValueTag::FF:
416 return "FF";
417 default:
418 return "Unknown";
419 }
420}
421
423{
424 return value.to_string();
425}
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
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
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