diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.cpp b/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.cpp index ddca3f725f72..d2853ad3d277 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.cpp @@ -1,5 +1,8 @@ #include "barretenberg/vm2/common/memory_types.hpp" +#include +#include + namespace bb::avm2 { uint8_t integral_tag_length(MemoryTag tag) @@ -24,4 +27,4 @@ uint8_t integral_tag_length(MemoryTag tag) return 0; // Should never happen. To please the compiler. } -} // namespace bb::avm2 \ No newline at end of file +} // namespace bb::avm2 diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.hpp b/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.hpp index fadbd178f28e..bb80d2a5a0c6 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/common/memory_types.hpp @@ -2,25 +2,15 @@ #include -#include "barretenberg/vm2/common/field.hpp" +#include "barretenberg/vm2/common/tagged_value.hpp" namespace bb::avm2 { -enum class MemoryTag { - FF, - U1, - U8, - U16, - U32, - U64, - U128, - MAX = U128, -}; - +using MemoryTag = ValueTag; +using MemoryValue = TaggedValue; using MemoryAddress = uint32_t; -using MemoryValue = FF; constexpr auto MemoryAddressTag = MemoryTag::U32; uint8_t integral_tag_length(MemoryTag tag); -} // namespace bb::avm2 \ No newline at end of file +} // namespace bb::avm2 diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.cpp b/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.cpp new file mode 100644 index 000000000000..9ea8f9265146 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.cpp @@ -0,0 +1,284 @@ +#include "barretenberg/vm2/common/tagged_value.hpp" + +#include +#include +#include +#include + +#include "barretenberg/numeric/bitop/get_msb.hpp" +#include "barretenberg/numeric/uint128/uint128.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/vm2/common/stringify.hpp" +#include "barretenberg/vm2/common/uint1.hpp" + +namespace bb::avm2 { + +namespace { + +// Helper type for ad-hoc visitors. See https://en.cppreference.com/w/cpp/utility/variant/visit2. +template struct overloads : Ts... { + using Ts::operator()...; +}; +// This is a deduction guide. Apparently not needed in C++20, but we somehow still need it. +template overloads(Ts...) -> overloads; + +struct shift_left { + template T operator()(const T& a, const U& b) const + { + if constexpr (std::is_same_v) { + return static_cast(a.operator<<(b)); + } else { + return static_cast(a << b); + } + } +}; + +struct shift_right { + template T operator()(const T& a, const U& b) const + { + if constexpr (std::is_same_v) { + return static_cast(a.operator>>(b)); + } else { + return static_cast(a >> b); + } + } +}; + +template +constexpr bool is_bitwise_operation_v = + std::is_same_v> || std::is_same_v> || std::is_same_v> || + std::is_same_v> || std::is_same_v || std::is_same_v; + +// Helper visitor for binary operations. These assume that the types are the same. +template struct BinaryOperationVisitor { + template TaggedValue::value_type operator()(const T& a, const U& b) const + { + if constexpr (std::is_same_v) { + if constexpr (std::is_same_v && is_bitwise_operation_v) { + throw std::runtime_error("Bitwise operations not valid for FF"); + } else { + // Note: IDK why this static_cast is needed, but if you remove it, operations seem to go through FF. + return static_cast(Op{}(a, b)); + } + } else { + throw std::runtime_error("Type mismatch in BinaryOperationVisitor!"); + } + } +}; + +// Helper visitor for shift operations. The right hand side is a different type. +template struct ShiftOperationVisitor { + template TaggedValue::value_type operator()(const T& a, const U& b) const + { + if constexpr (std::is_same_v || std::is_same_v) { + throw std::runtime_error("Bitwise operations not valid for FF"); + } else { + return static_cast(Op{}(a, b)); + } + } +}; + +// Helper visitor for unary operations +template struct UnaryOperationVisitor { + template TaggedValue::value_type operator()(const T& a) const + { + if constexpr (std::is_same_v && is_bitwise_operation_v) { + throw std::runtime_error("Can't do unary bitwise operations on an FF"); + } else { + // Note: IDK why this static_cast is needed, but if you remove it, operations seem to go through FF. + return static_cast(Op{}(a)); + } + } +}; + +} // namespace + +// Constructor +TaggedValue::TaggedValue(TaggedValue::value_type value_) + : value(value_) +{} + +TaggedValue TaggedValue::from_tag(ValueTag tag, FF value) +{ + auto assert_bounds = [](const FF& value, uint8_t bits) { + if (static_cast(value).get_msb() >= bits) { + throw std::runtime_error("Value out of bounds"); + } + }; + + // Check bounds first. + switch (tag) { + case ValueTag::U1: + assert_bounds(value, 1); + break; + case ValueTag::U8: + assert_bounds(value, 8); + break; + case ValueTag::U16: + assert_bounds(value, 16); + break; + case ValueTag::U32: + assert_bounds(value, 32); + break; + case ValueTag::U64: + assert_bounds(value, 64); + break; + case ValueTag::U128: + assert_bounds(value, 128); + break; + case ValueTag::FF: + break; + } + + return from_tag_truncating(tag, value); +} + +TaggedValue TaggedValue::from_tag_truncating(ValueTag tag, FF value) +{ + switch (tag) { + case ValueTag::U1: + return TaggedValue(static_cast(static_cast(value) % 2)); + case ValueTag::U8: + return TaggedValue(static_cast(value)); + case ValueTag::U16: + return TaggedValue(static_cast(value)); + case ValueTag::U32: + return TaggedValue(static_cast(value)); + case ValueTag::U64: + return TaggedValue(static_cast(value)); + case ValueTag::U128: + return TaggedValue(static_cast(value)); + case ValueTag::FF: + return TaggedValue(value); + default: + throw std::runtime_error("Invalid tag"); + } +} + +// Arithmetic operators +TaggedValue TaggedValue::operator+(const TaggedValue& other) const +{ + return std::visit(BinaryOperationVisitor>(), value, other.value); +} + +TaggedValue TaggedValue::operator-(const TaggedValue& other) const +{ + return std::visit(BinaryOperationVisitor>(), value, other.value); +} + +TaggedValue TaggedValue::operator*(const TaggedValue& other) const +{ + return std::visit(BinaryOperationVisitor>(), value, other.value); +} + +TaggedValue TaggedValue::operator/(const TaggedValue& other) const +{ + return std::visit(BinaryOperationVisitor>(), value, other.value); +} + +// Bitwise operators +TaggedValue TaggedValue::operator&(const TaggedValue& other) const +{ + return std::visit(BinaryOperationVisitor>(), value, other.value); +} + +TaggedValue TaggedValue::operator|(const TaggedValue& other) const +{ + return std::visit(BinaryOperationVisitor>(), value, other.value); +} + +TaggedValue TaggedValue::operator^(const TaggedValue& other) const +{ + return std::visit(BinaryOperationVisitor>(), value, other.value); +} + +TaggedValue TaggedValue::operator<<(const TaggedValue& other) const +{ + return std::visit(ShiftOperationVisitor(), value, other.value); +} + +TaggedValue TaggedValue::operator>>(const TaggedValue& other) const +{ + return std::visit(ShiftOperationVisitor(), value, other.value); +} + +TaggedValue TaggedValue::operator~() const +{ + return std::visit(UnaryOperationVisitor>(), value); +} + +FF TaggedValue::as_ff() const +{ + const auto visitor = overloads{ [](FF val) -> FF { return val; }, + [](uint1_t val) -> FF { return val.value(); }, + [](uint128_t val) -> FF { return uint256_t::from_uint128(val); }, + [](auto&& val) -> FF { return val; } }; + + return std::visit(visitor, value); +} + +ValueTag TaggedValue::get_tag() const +{ + // The tag is implicit in the type. + if (std::holds_alternative(value)) { + return ValueTag::U8; + } else if (std::holds_alternative(value)) { + return ValueTag::U1; + } else if (std::holds_alternative(value)) { + return ValueTag::U16; + } else if (std::holds_alternative(value)) { + return ValueTag::U32; + } else if (std::holds_alternative(value)) { + return ValueTag::U64; + } else if (std::holds_alternative(value)) { + return ValueTag::U128; + } else if (std::holds_alternative(value)) { + return ValueTag::FF; + } else { + throw std::runtime_error("Unknown value type"); + } + + assert(false && "This should never happen."); + return ValueTag::FF; // Only to make the compiler happy. +} + +std::string TaggedValue::to_string() const +{ + std::string v = std::visit( + overloads{ [](const FF& val) -> std::string { return field_to_string(val); }, + [](const uint128_t& val) -> std::string { return field_to_string(uint256_t::from_uint128(val)); }, + [](const uint1_t& val) -> std::string { return val.value() == 0 ? "0" : "1"; }, + [](auto&& val) -> std::string { return std::to_string(val); } }, + value); + return "TaggedValue(" + v + ", " + std::to_string(get_tag()) + ")"; +} + +} // namespace bb::avm2 + +std::string std::to_string(bb::avm2::ValueTag tag) +{ + using namespace bb::avm2; + switch (tag) { + case ValueTag::U1: + return "U1"; + case ValueTag::U8: + return "U8"; + case ValueTag::U16: + return "U16"; + case ValueTag::U32: + return "U32"; + case ValueTag::U64: + return "U64"; + case ValueTag::U128: + return "U128"; + case ValueTag::FF: + return "FF"; + default: + return "Unknown"; + } +} + +std::string std::to_string(const bb::avm2::TaggedValue& value) +{ + return value.to_string(); +} diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.hpp b/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.hpp new file mode 100644 index 000000000000..247d5e00ef95 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include + +#include "barretenberg/numeric/uint128/uint128.hpp" +#include "barretenberg/vm2/common/field.hpp" +#include "barretenberg/vm2/common/uint1.hpp" + +namespace bb::avm2 { + +enum class ValueTag { + FF, + U1, + U8, + U16, + U32, + U64, + U128, + MAX = U128, +}; + +class TaggedValue { + public: + // We are using variant to avoid heap allocations at the cost of a bigger memory footprint. + // Do not use this type outside of this class, except for the visitors. + // NOTE: Order matters, "a default-constructed variant holds a value of its first alternative". + // See https://en.cppreference.com/w/cpp/utility/variant. + using value_type = std::variant; + + // Default constructor. Useful for events. Note that this will be an U8. + TaggedValue() = default; + + // Using this constructor you can specify the type explicitly via the template. + template static TaggedValue from(T value) { return TaggedValue(value); } + // Constructs from a tag and value. Throws if the value is out of bounds for the tag. + static TaggedValue from_tag(ValueTag tag, FF value); + // Constructs from a tag and value. Truncates the value if it is out of bounds for the tag. + // The truncation is equivalent to bit masking. It does not saturate to the max value of the tag. + static TaggedValue from_tag_truncating(ValueTag tag, FF value); + + // Arithmetic operators. + TaggedValue operator+(const TaggedValue& other) const; + TaggedValue operator-(const TaggedValue& other) const; + TaggedValue operator*(const TaggedValue& other) const; + TaggedValue operator/(const TaggedValue& other) const; + // Bitwise operations not valid for FF. They will throw. + TaggedValue operator&(const TaggedValue& other) const; + TaggedValue operator|(const TaggedValue& other) const; + TaggedValue operator^(const TaggedValue& other) const; + TaggedValue operator~() const; + // Shift operations not valid for FF. They will throw. + TaggedValue operator<<(const TaggedValue& other) const; + TaggedValue operator>>(const TaggedValue& other) const; + + bool operator==(const TaggedValue& other) const = default; + bool operator!=(const TaggedValue& other) const = default; + + // Converts any type to FF. + FF as_ff() const; + operator FF() const { return as_ff(); } + ValueTag get_tag() const; + std::string to_string() const; + + // Use sparingly. The held type must match. + template T as() const + { + if (std::holds_alternative(value)) { + return std::get(value); + } + throw std::runtime_error("TaggedValue::as(): type mismatch"); + } + + std::size_t hash() const noexcept { return std::hash{}(as_ff()); } + + private: + TaggedValue(value_type value); + value_type value; +}; + +} // namespace bb::avm2 + +namespace std { + +std::string to_string(bb::avm2::ValueTag tag); +std::string to_string(const bb::avm2::TaggedValue& val); + +} // namespace std diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.test.cpp new file mode 100644 index 000000000000..1473ee1c8cb8 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm2/common/tagged_value.test.cpp @@ -0,0 +1,625 @@ +#include +#include + +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/vm2/common/field.hpp" +#include "barretenberg/vm2/common/tagged_value.hpp" +#include "barretenberg/vm2/common/uint1.hpp" + +namespace bb::avm2 { +namespace { + +// Test constructor and basic properties for each supported type +TEST(TaggedValueTest, ConstructorAndTypeProperties) +{ + // Test uint1_t + auto val_u1 = TaggedValue::from(true); + EXPECT_EQ(val_u1.get_tag(), ValueTag::U1); + EXPECT_EQ(val_u1.as().value(), 1); + + // Test uint8_t + auto val_u8 = TaggedValue::from(42); + EXPECT_EQ(val_u8.get_tag(), ValueTag::U8); + EXPECT_EQ(val_u8.as(), 42); + + // Test uint16_t + auto val_u16 = TaggedValue::from(1000); + EXPECT_EQ(val_u16.get_tag(), ValueTag::U16); + EXPECT_EQ(val_u16.as(), 1000); + + // Test uint32_t + auto val_u32 = TaggedValue::from(100000); + EXPECT_EQ(val_u32.get_tag(), ValueTag::U32); + EXPECT_EQ(val_u32.as(), 100000); + + // Test uint64_t + auto val_u64 = TaggedValue::from(1ULL << 40); + EXPECT_EQ(val_u64.get_tag(), ValueTag::U64); + EXPECT_EQ(val_u64.as(), 1ULL << 40); + + // Test uint128_t + auto val_u128 = TaggedValue::from(uint128_t(1) << 100); + EXPECT_EQ(val_u128.get_tag(), ValueTag::U128); + EXPECT_EQ(val_u128.as(), uint128_t(1) << 100); + + // Test FF + auto val_ff = TaggedValue::from(123); + EXPECT_EQ(val_ff.get_tag(), ValueTag::FF); + EXPECT_EQ(val_ff.as(), FF(123)); +} + +// Test from_tag method +TEST(TaggedValueTest, FromTag) +{ + FF value = 42; + + auto val_u1 = TaggedValue::from_tag(ValueTag::U1, 1); + EXPECT_EQ(val_u1.get_tag(), ValueTag::U1); + EXPECT_EQ(val_u1.as().value(), 1); + + auto val_u1_zero = TaggedValue::from_tag(ValueTag::U1, 0); + EXPECT_EQ(val_u1_zero.get_tag(), ValueTag::U1); + EXPECT_EQ(val_u1_zero.as().value(), 0); + + auto val_u8 = TaggedValue::from_tag(ValueTag::U8, value); + EXPECT_EQ(val_u8.get_tag(), ValueTag::U8); + EXPECT_EQ(val_u8.as(), 42); + + auto val_u16 = TaggedValue::from_tag(ValueTag::U16, value); + EXPECT_EQ(val_u16.get_tag(), ValueTag::U16); + EXPECT_EQ(val_u16.as(), 42); + + auto val_u32 = TaggedValue::from_tag(ValueTag::U32, value); + EXPECT_EQ(val_u32.get_tag(), ValueTag::U32); + EXPECT_EQ(val_u32.as(), 42); + + auto val_u64 = TaggedValue::from_tag(ValueTag::U64, value); + EXPECT_EQ(val_u64.get_tag(), ValueTag::U64); + EXPECT_EQ(val_u64.as(), 42); + + auto val_u128 = TaggedValue::from_tag(ValueTag::U128, value); + EXPECT_EQ(val_u128.get_tag(), ValueTag::U128); + EXPECT_EQ(val_u128.as(), 42); + + auto val_ff = TaggedValue::from_tag(ValueTag::FF, value); + EXPECT_EQ(val_ff.get_tag(), ValueTag::FF); + EXPECT_EQ(val_ff.as(), value); +} + +// Test from_tag_truncating method +TEST(TaggedValueTest, FromTagTruncating) +{ + // U1 - truncates to 1 bit + auto val_u1 = TaggedValue::from_tag_truncating(ValueTag::U1, 3); // 3 = 1 mod 2 + EXPECT_EQ(val_u1.get_tag(), ValueTag::U1); + EXPECT_EQ(val_u1.as().value(), 1); + + auto val_u1_zero = TaggedValue::from_tag_truncating(ValueTag::U1, 0); + EXPECT_EQ(val_u1_zero.get_tag(), ValueTag::U1); + EXPECT_EQ(val_u1_zero.as().value(), 0); + + // U8 - truncates to 8 bits + auto val_u8 = TaggedValue::from_tag_truncating(ValueTag::U8, 42); + EXPECT_EQ(val_u8.get_tag(), ValueTag::U8); + EXPECT_EQ(val_u8.as(), 42); + + auto val_u8_truncated = TaggedValue::from_tag_truncating(ValueTag::U8, 300); // 300 = 44 mod 256 + EXPECT_EQ(val_u8_truncated.get_tag(), ValueTag::U8); + EXPECT_EQ(val_u8_truncated.as(), 44); + + // U16 - truncates to 16 bits + auto val_u16 = TaggedValue::from_tag_truncating(ValueTag::U16, 1000); + EXPECT_EQ(val_u16.get_tag(), ValueTag::U16); + EXPECT_EQ(val_u16.as(), 1000); + + auto val_u16_truncated = TaggedValue::from_tag_truncating(ValueTag::U16, 70000); // 70000 = 4464 mod 65536 + EXPECT_EQ(val_u16_truncated.get_tag(), ValueTag::U16); + EXPECT_EQ(val_u16_truncated.as(), 4464); + + // U32 - truncates to 32 bits + auto val_u32 = TaggedValue::from_tag_truncating(ValueTag::U32, 100000); + EXPECT_EQ(val_u32.get_tag(), ValueTag::U32); + EXPECT_EQ(val_u32.as(), 100000); + + FF large_u32 = FF(uint256_t(1) << 33) + FF(42); // 2^33 + 42 = 42 mod 2^32 + auto val_u32_truncated = TaggedValue::from_tag_truncating(ValueTag::U32, large_u32); + EXPECT_EQ(val_u32_truncated.get_tag(), ValueTag::U32); + EXPECT_EQ(val_u32_truncated.as(), 42); + + // U64 - truncates to 64 bits + auto val_u64 = TaggedValue::from_tag_truncating(ValueTag::U64, 1ULL << 40); + EXPECT_EQ(val_u64.get_tag(), ValueTag::U64); + EXPECT_EQ(val_u64.as(), 1ULL << 40); + + FF large_u64 = FF(uint256_t(1) << 65) + FF(123); // 2^65 + 123 = 123 mod 2^64 + auto val_u64_truncated = TaggedValue::from_tag_truncating(ValueTag::U64, large_u64); + EXPECT_EQ(val_u64_truncated.get_tag(), ValueTag::U64); + EXPECT_EQ(val_u64_truncated.as(), 123); + + // U128 - truncates to 128 bits + auto val_u128 = TaggedValue::from_tag_truncating(ValueTag::U128, uint256_t::from_uint128(uint128_t(1) << 100)); + EXPECT_EQ(val_u128.get_tag(), ValueTag::U128); + EXPECT_EQ(val_u128.as(), uint128_t(1) << 100); + + FF large_u128 = (uint256_t(1) << 129) + 456; // 2^129 + 456 = 456 mod 2^128 + auto val_u128_truncated = TaggedValue::from_tag_truncating(ValueTag::U128, large_u128); + EXPECT_EQ(val_u128_truncated.get_tag(), ValueTag::U128); + EXPECT_EQ(val_u128_truncated.as(), 456); + + // FF - no truncation + FF large_ff = FF::random_element(); + auto val_ff = TaggedValue::from_tag_truncating(ValueTag::FF, large_ff); + EXPECT_EQ(val_ff.get_tag(), ValueTag::FF); + EXPECT_EQ(val_ff.as(), large_ff); +} + +// Test from_tag method with out of bounds values +TEST(TaggedValueTest, FromTagOutOfBounds) +{ + // U8 - max value is 255 + EXPECT_NO_THROW(TaggedValue::from_tag(ValueTag::U8, FF(255))); + EXPECT_THROW(TaggedValue::from_tag(ValueTag::U8, FF(256)), std::runtime_error); + + // U16 - max value is 65535 + EXPECT_NO_THROW(TaggedValue::from_tag(ValueTag::U16, FF(65535))); + EXPECT_THROW(TaggedValue::from_tag(ValueTag::U16, FF(65536)), std::runtime_error); + + // U32 - max value is 2^32-1 + EXPECT_NO_THROW(TaggedValue::from_tag(ValueTag::U32, FF((1ULL << 32) - 1))); + EXPECT_THROW(TaggedValue::from_tag(ValueTag::U32, FF(1ULL << 32)), std::runtime_error); + + // U64 - max value is 2^64-1 + FF max_u64 = FF(uint256_t((1ULL << 63)) * 2 - 1); + EXPECT_NO_THROW(TaggedValue::from_tag(ValueTag::U64, max_u64)); + EXPECT_THROW(TaggedValue::from_tag(ValueTag::U64, max_u64 + FF(1)), std::runtime_error); + + // U128 - max value is 2^128-1 + FF max_u128 = FF(uint256_t(1) << 128) - FF(1); + EXPECT_NO_THROW(TaggedValue::from_tag(ValueTag::U128, max_u128)); + EXPECT_THROW(TaggedValue::from_tag(ValueTag::U128, max_u128 + FF(1)), std::runtime_error); + + // Invalid tag + EXPECT_THROW(TaggedValue::from_tag(static_cast(100), FF(1)), std::runtime_error); +} + +// Test as_ff method +TEST(TaggedValueTest, AsFF) +{ + // Test conversion to FF from each type + auto val_u1 = TaggedValue::from(1); + EXPECT_EQ(val_u1.as_ff(), FF(1)); + + auto val_u8 = TaggedValue::from(42); + EXPECT_EQ(val_u8.as_ff(), FF(42)); + + auto val_u16 = TaggedValue::from(1000); + EXPECT_EQ(val_u16.as_ff(), FF(1000)); + + auto val_u32 = TaggedValue::from(100000); + EXPECT_EQ(val_u32.as_ff(), FF(100000)); + + auto val_u64 = TaggedValue::from(1ULL << 40); + EXPECT_EQ(val_u64.as_ff(), FF(1ULL << 40)); + + // uint128 to FF + auto val_u128 = TaggedValue::from(123); + EXPECT_EQ(val_u128.as_ff(), FF(123)); + + // FF to FF should be identity + FF field_val(123); + auto val_ff = TaggedValue::from(field_val); + EXPECT_EQ(val_ff.as_ff(), field_val); +} + +// Test arithmetic operations for each type +TEST(TaggedValueTest, ArithmeticOperations) +{ + // Test uint1_t operations + auto u1_val0 = TaggedValue::from(0); + auto u1_val1 = TaggedValue::from(1); + + auto u1_add = u1_val1 + u1_val0; + EXPECT_EQ(u1_add.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_add.as().value(), 1); + + auto u1_sub = u1_val1 - u1_val0; + EXPECT_EQ(u1_sub.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_sub.as().value(), 1); + + auto u1_mul = u1_val1 * u1_val0; + EXPECT_EQ(u1_mul.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_mul.as().value(), 0); + + // Division by zero would throw, so we'll test with non-zero + auto u1_div = u1_val1 / u1_val1; + EXPECT_EQ(u1_div.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_div.as().value(), 1); + + // Test uint8_t operations + auto u8_val1 = TaggedValue::from(40); + auto u8_val2 = TaggedValue::from(2); + + auto u8_add = u8_val1 + u8_val2; + EXPECT_EQ(u8_add.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_add.as(), 42); + + auto u8_sub = u8_val1 - u8_val2; + EXPECT_EQ(u8_sub.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_sub.as(), 38); + + auto u8_mul = u8_val1 * u8_val2; + EXPECT_EQ(u8_mul.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_mul.as(), 80); + + auto u8_div = u8_val1 / u8_val2; + EXPECT_EQ(u8_div.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_div.as(), 20); + + // Test uint16_t operations + auto u16_val1 = TaggedValue::from(1000); + auto u16_val2 = TaggedValue::from(10); + + auto u16_add = u16_val1 + u16_val2; + EXPECT_EQ(u16_add.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_add.as(), 1010); + + auto u16_sub = u16_val1 - u16_val2; + EXPECT_EQ(u16_sub.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_sub.as(), 990); + + auto u16_mul = u16_val1 * u16_val2; + EXPECT_EQ(u16_mul.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_mul.as(), 10000); + + auto u16_div = u16_val1 / u16_val2; + EXPECT_EQ(u16_div.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_div.as(), 100); + + // Test uint32_t operations + auto u32_val1 = TaggedValue::from(100000); + auto u32_val2 = TaggedValue::from(25); + + auto u32_add = u32_val1 + u32_val2; + EXPECT_EQ(u32_add.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_add.as(), 100025); + + auto u32_sub = u32_val1 - u32_val2; + EXPECT_EQ(u32_sub.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_sub.as(), 99975); + + auto u32_mul = u32_val1 * u32_val2; + EXPECT_EQ(u32_mul.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_mul.as(), 2500000); + + auto u32_div = u32_val1 / u32_val2; + EXPECT_EQ(u32_div.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_div.as(), 4000); + + // Test uint64_t operations + auto u64_val1 = TaggedValue::from(1ULL << 32); + auto u64_val2 = TaggedValue::from(5); + + auto u64_add = u64_val1 + u64_val2; + EXPECT_EQ(u64_add.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_add.as(), (1ULL << 32) + 5); + + auto u64_sub = u64_val1 - u64_val2; + EXPECT_EQ(u64_sub.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_sub.as(), (1ULL << 32) - 5); + + auto u64_mul = u64_val1 * u64_val2; + EXPECT_EQ(u64_mul.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_mul.as(), (1ULL << 32) * 5); + + auto u64_div = u64_val1 / u64_val2; + EXPECT_EQ(u64_div.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_div.as(), (1ULL << 32) / 5); + + // Test uint128_t operations + auto u128_val1 = TaggedValue::from(1000000000000ULL); + auto u128_val2 = TaggedValue::from(7); + + auto u128_add = u128_val1 + u128_val2; + EXPECT_EQ(u128_add.get_tag(), ValueTag::U128); + EXPECT_EQ(u128_add.as(), uint128_t(1000000000000ULL) + uint128_t(7)); + + auto u128_sub = u128_val1 - u128_val2; + EXPECT_EQ(u128_sub.get_tag(), ValueTag::U128); + EXPECT_EQ(u128_sub.as(), uint128_t(1000000000000ULL) - uint128_t(7)); + + auto u128_mul = u128_val1 * u128_val2; + EXPECT_EQ(u128_mul.get_tag(), ValueTag::U128); + EXPECT_EQ(u128_mul.as(), uint128_t(1000000000000ULL) * uint128_t(7)); + + auto u128_div = u128_val1 / u128_val2; + EXPECT_EQ(u128_div.get_tag(), ValueTag::U128); + EXPECT_EQ(u128_div.as(), uint128_t(1000000000000ULL) / uint128_t(7)); + + // Test arithmetic operations with FF + auto ff_val1 = TaggedValue::from(100); + auto ff_val2 = TaggedValue::from(5); + + auto ff_add = ff_val1 + ff_val2; + EXPECT_EQ(ff_add.get_tag(), ValueTag::FF); + EXPECT_EQ(ff_add.as(), FF(105)); + + auto ff_sub = ff_val1 - ff_val2; + EXPECT_EQ(ff_sub.get_tag(), ValueTag::FF); + EXPECT_EQ(ff_sub.as(), FF(95)); + + auto ff_mul = ff_val1 * ff_val2; + EXPECT_EQ(ff_mul.get_tag(), ValueTag::FF); + EXPECT_EQ(ff_mul.as(), FF(500)); + + auto ff_div = ff_val1 / ff_val2; + EXPECT_EQ(ff_div.get_tag(), ValueTag::FF); + EXPECT_EQ(ff_div.as(), FF(20)); +} + +// Test bitwise operations +TEST(TaggedValueTest, BitwiseOperations) +{ + // Test bitwise AND + auto u8_val1 = TaggedValue::from(0b1010); + auto u8_val2 = TaggedValue::from(0b1100); + auto u8_and = u8_val1 & u8_val2; + EXPECT_EQ(u8_and.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_and.as(), 0b1000); + + // Test bitwise OR + auto u16_val1 = TaggedValue::from(0b1010); + auto u16_val2 = TaggedValue::from(0b1100); + auto u16_or = u16_val1 | u16_val2; + EXPECT_EQ(u16_or.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_or.as(), 0b1110); + + // Test bitwise XOR + auto u32_val1 = TaggedValue::from(0b1010); + auto u32_val2 = TaggedValue::from(0b1100); + auto u32_xor = u32_val1 ^ u32_val2; + EXPECT_EQ(u32_xor.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_xor.as(), 0b0110); + + // Test bitwise NOT + auto u64_val = TaggedValue::from(0xFFFFFFFFFFFFFFF0ULL); + auto u64_not = ~u64_val; + EXPECT_EQ(u64_not.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_not.as(), 0x000000000000000FULL); + + // Test shift operations + auto u8_shift = TaggedValue::from(0b00000001); + auto u8_amount = TaggedValue::from(2); + + auto u8_shl = u8_shift << u8_amount; + EXPECT_EQ(u8_shl.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_shl.as(), 0b00000100); + + auto u8_shift_high = TaggedValue::from(0b10000000); + auto u8_shr = u8_shift_high >> u8_amount; + EXPECT_EQ(u8_shr.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_shr.as(), 0b00100000); +} + +// Test unary operations for all types +TEST(TaggedValueTest, UnaryOperations) +{ + // Test unary bit negation. + auto u1_val = TaggedValue::from(1); + auto u1_not = ~u1_val; + EXPECT_EQ(u1_not.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_not.as().value(), 0); + + auto u8_val = TaggedValue::from(0xAA); // 10101010 + auto u8_not = ~u8_val; + EXPECT_EQ(u8_not.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_not.as(), 0x55); // 01010101 + + auto u16_val = TaggedValue::from(0xAAAA); // 1010101010101010 + auto u16_not = ~u16_val; + EXPECT_EQ(u16_not.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_not.as(), 0x5555); // 0101010101010101 + + auto u32_val = TaggedValue::from(0xAAAAAAAA); // 10101010... + auto u32_not = ~u32_val; + EXPECT_EQ(u32_not.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_not.as(), 0x55555555); // 01010101... + + auto u64_val = TaggedValue::from(0xAAAAAAAAAAAAAAAAULL); + auto u64_not = ~u64_val; + EXPECT_EQ(u64_not.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_not.as(), 0x5555555555555555ULL); + + uint128_t u128_input = (uint128_t(0xAAAAAAAAAAAAAAAAULL) << 64) | uint128_t(0xAAAAAAAAAAAAAAAAULL); + auto u128_val = TaggedValue::from(u128_input); + auto u128_not = ~u128_val; + EXPECT_EQ(u128_not.get_tag(), ValueTag::U128); + uint128_t expected_u128 = (uint128_t(0x5555555555555555ULL) << 64) | uint128_t(0x5555555555555555ULL); + EXPECT_EQ(u128_not.as(), expected_u128); + + // Test that unary bitwise operations on FF throw exceptions + auto ff_val = TaggedValue::from(123); + EXPECT_THROW(~ff_val, std::runtime_error); +} + +// Test edge cases with uint1_t +TEST(TaggedValueTest, Uint1EdgeCases) +{ + // Test uint1_t operations + auto u1_val0 = TaggedValue::from(0); + auto u1_val1 = TaggedValue::from(1); + + // Bitwise operations + auto u1_and = u1_val1 & u1_val1; + EXPECT_EQ(u1_and.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_and.as().value(), 1); + + auto u1_or = u1_val0 | u1_val1; + EXPECT_EQ(u1_or.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_or.as().value(), 1); + + auto u1_xor = u1_val1 ^ u1_val1; + EXPECT_EQ(u1_xor.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_xor.as().value(), 0); + + auto u1_not = ~u1_val1; + EXPECT_EQ(u1_not.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_not.as().value(), 0); +} + +// Test error cases +TEST(TaggedValueTest, ErrorCases) +{ + // Test type mismatch + auto u8_val = TaggedValue::from(42); + EXPECT_THROW(u8_val.as(), std::runtime_error); + + // Test bitwise operations on FF (should throw) + auto ff_val1 = TaggedValue::from(10); + auto ff_val2 = TaggedValue::from(5); + + EXPECT_THROW(ff_val1 & ff_val2, std::runtime_error); + EXPECT_THROW(ff_val1 | ff_val2, std::runtime_error); + EXPECT_THROW(ff_val1 ^ ff_val2, std::runtime_error); + EXPECT_THROW(~ff_val1, std::runtime_error); + + // Test mixed type operations + auto u8_val1 = TaggedValue::from(10); + auto u16_val = TaggedValue::from(5); + + // Binary operations with different types should throw + EXPECT_THROW(u8_val1 + u16_val, std::runtime_error); + EXPECT_THROW(u8_val1 - u16_val, std::runtime_error); + EXPECT_THROW(u8_val1 * u16_val, std::runtime_error); + EXPECT_THROW(u8_val1 / u16_val, std::runtime_error); + EXPECT_THROW(u8_val1 & u16_val, std::runtime_error); + EXPECT_THROW(u8_val1 | u16_val, std::runtime_error); + EXPECT_THROW(u8_val1 ^ u16_val, std::runtime_error); +} + +// Test shift operations with different right-side types +TEST(TaggedValueTest, ShiftOperationsWithDifferentTypes) +{ + auto u32_val = TaggedValue::from(1); + + // Shift with uint8_t + auto u8_amount = TaggedValue::from(3); + auto result_shl_u8 = u32_val << u8_amount; + EXPECT_EQ(result_shl_u8.get_tag(), ValueTag::U32); + EXPECT_EQ(result_shl_u8.as(), 1 << 3); + + // Shift with uint16_t + auto u16_amount = TaggedValue::from(4); + auto result_shl_u16 = u32_val << u16_amount; + EXPECT_EQ(result_shl_u16.get_tag(), ValueTag::U32); + EXPECT_EQ(result_shl_u16.as(), 1 << 4); + + // Shift with uint1_t + auto u1_amount = TaggedValue::from(1); + auto result_shl_u1 = u32_val << u1_amount; + EXPECT_EQ(result_shl_u1.get_tag(), ValueTag::U32); + EXPECT_EQ(result_shl_u1.as(), 2); +} + +// Test boundary cases for all types +TEST(TaggedValueTest, BoundaryCases) +{ + // Test uint1_t overflow + auto u1_max = TaggedValue::from(true); + auto u1_one = TaggedValue::from(true); + auto u1_overflow = u1_max + u1_one; + EXPECT_EQ(u1_overflow.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_overflow.as().value(), 0); // 1+1=0 with overflow + + // Test uint8_t overflow + auto u8_max = TaggedValue::from(255); + auto u8_one = TaggedValue::from(1); + auto u8_overflow = u8_max + u8_one; + EXPECT_EQ(u8_overflow.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_overflow.as(), 0); // Overflow wraps around + + // Test uint16_t overflow + auto u16_max = TaggedValue::from(65535); + auto u16_one = TaggedValue::from(1); + auto u16_overflow = u16_max + u16_one; + EXPECT_EQ(u16_overflow.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_overflow.as(), 0); // Overflow wraps around + + // Test uint32_t overflow + auto u32_max = TaggedValue::from(4294967295U); + auto u32_one = TaggedValue::from(1); + auto u32_overflow = u32_max + u32_one; + EXPECT_EQ(u32_overflow.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_overflow.as(), 0); // Overflow wraps around + + // Test uint64_t overflow + auto u64_max = TaggedValue::from(std::numeric_limits::max()); + auto u64_one = TaggedValue::from(1); + auto u64_overflow = u64_max + u64_one; + EXPECT_EQ(u64_overflow.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_overflow.as(), 0); // Overflow wraps around + + // Test uint128_t overflow + auto u128_max = TaggedValue::from(~uint128_t(0)); + auto u128_one = TaggedValue::from(1); + auto u128_overflow = u128_max + u128_one; + EXPECT_EQ(u128_overflow.get_tag(), ValueTag::U128); + EXPECT_EQ(u128_overflow.as(), 0); // Overflow wraps around + + // Test underflow for all types + auto u1_zero = TaggedValue::from(0); + auto u1_underflow = u1_zero - u1_one; + EXPECT_EQ(u1_underflow.get_tag(), ValueTag::U1); + EXPECT_EQ(u1_underflow.as().value(), 1); // 0-1=1 with underflow + + auto u8_zero = TaggedValue::from(0); + auto u8_underflow = u8_zero - u8_one; + EXPECT_EQ(u8_underflow.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_underflow.as(), 255); // Underflow wraps around + + auto u16_zero = TaggedValue::from(0); + auto u16_underflow = u16_zero - u16_one; + EXPECT_EQ(u16_underflow.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_underflow.as(), 65535); // Underflow wraps around + + auto u32_zero = TaggedValue::from(0); + auto u32_underflow = u32_zero - u32_one; + EXPECT_EQ(u32_underflow.get_tag(), ValueTag::U32); + EXPECT_EQ(u32_underflow.as(), 4294967295U); // Underflow wraps around + + auto u64_zero = TaggedValue::from(0); + auto u64_underflow = u64_zero - u64_one; + EXPECT_EQ(u64_underflow.get_tag(), ValueTag::U64); + EXPECT_EQ(u64_underflow.as(), std::numeric_limits::max()); // Underflow wraps around + + auto u128_zero = TaggedValue::from(0); + auto u128_underflow = u128_zero - u128_one; + EXPECT_EQ(u128_underflow.get_tag(), ValueTag::U128); + EXPECT_EQ(u128_underflow.as(), ~uint128_t(0)); // Underflow wraps around + + // Test multiplication overflow + auto u8_large = TaggedValue::from(128); + auto u8_mul_overflow = u8_large * u8_large; + EXPECT_EQ(u8_mul_overflow.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_mul_overflow.as(), 0); // 128*128=16384, which is 0 mod 256 + + auto u16_large = TaggedValue::from(256); + auto u16_mul_overflow = u16_large * u16_large; + EXPECT_EQ(u16_mul_overflow.get_tag(), ValueTag::U16); + EXPECT_EQ(u16_mul_overflow.as(), 0); // 256*256=65536, which is 0 mod 65536 + + // Test shift overflow + auto u8_shift = TaggedValue::from(1); + auto u8_shift_amount = TaggedValue::from(8); + auto u8_shift_overflow = u8_shift << u8_shift_amount; + EXPECT_EQ(u8_shift_overflow.get_tag(), ValueTag::U8); + EXPECT_EQ(u8_shift_overflow.as(), 0); // 1<<8=256, which is 0 mod 256 + + auto ff_large = TaggedValue::from(FF::modulus - FF(1)); + auto ff_one = TaggedValue::from(1); + auto ff_wrap = ff_large + ff_one; + EXPECT_EQ(ff_wrap.get_tag(), ValueTag::FF); + EXPECT_EQ(ff_wrap.as(), FF(0)); // Modular arithmetic wraps naturally +} + +} // namespace +} // namespace bb::avm2 diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/uint1.hpp b/barretenberg/cpp/src/barretenberg/vm2/common/uint1.hpp new file mode 100644 index 000000000000..84a2be6eef55 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm2/common/uint1.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +namespace bb::avm2 { + +/** + * @brief A 1-bit unsigned integer type. + * + * This type is used to represent boolean values in a way that's compatible + * with the TaggedValue system. It behaves like a regular integer type but + * only allows values 0 and 1. + */ +class uint1_t { + public: + uint1_t(const uint1_t& other) = default; + uint1_t& operator=(const uint1_t& other) = default; + + // Default constructor initializes to 0. + constexpr uint1_t() noexcept + : value_(false) + {} + + // Constructor from bool. + constexpr uint1_t(bool b) noexcept + : value_(b) + {} + + // Constructor from integral types. Zero becomes 0, non-zero becomes 1. + template >> + constexpr uint1_t(T v) noexcept + : value_(v != 0) + {} + + // Arithmetic operators. + constexpr uint1_t operator+(const uint1_t& other) const noexcept { return *this ^ other; } + constexpr uint1_t operator-(const uint1_t& other) const noexcept { return *this + other; } + constexpr uint1_t operator*(const uint1_t& other) const noexcept { return *this & other; } + constexpr uint1_t operator/(const uint1_t& other) const noexcept { return uint1_t(value() / other.value()); } + constexpr uint1_t operator-() const noexcept { return uint1_t(!value_); } + + // Bitwise operators. + constexpr uint1_t operator&(const uint1_t& other) const noexcept { return uint1_t(value_ && other.value_); } + constexpr uint1_t operator|(const uint1_t& other) const noexcept { return uint1_t(value_ || other.value_); } + constexpr uint1_t operator^(const uint1_t& other) const noexcept { return uint1_t(value_ != other.value_); } + constexpr uint1_t operator~() const noexcept { return uint1_t(!value_); } + + // Shifts are a bit special. + constexpr uint1_t operator<<(const uint1_t& other) const noexcept { return (*this - (*this * other)); } + constexpr uint1_t operator>>(const uint1_t& other) const noexcept { return (*this - (*this * other)); } + + // Comparison operators. + constexpr bool operator==(const uint1_t& other) const noexcept = default; + constexpr bool operator!=(const uint1_t& other) const noexcept = default; + constexpr bool operator<(const uint1_t& other) const noexcept { return value_ < other.value_; } + constexpr bool operator<=(const uint1_t& other) const noexcept { return value_ <= other.value_; } + constexpr bool operator>(const uint1_t& other) const noexcept { return value_ > other.value_; } + constexpr bool operator>=(const uint1_t& other) const noexcept { return value_ >= other.value_; } + + // Get the raw value. Guaranteed to be 0 or 1. + constexpr uint8_t value() const noexcept { return value_ ? 1 : 0; } + constexpr operator uint8_t() const noexcept { return value(); } + + private: + bool value_; +}; + +} // namespace bb::avm2 diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/uint1.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/common/uint1.test.cpp new file mode 100644 index 000000000000..58e9921e0319 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm2/common/uint1.test.cpp @@ -0,0 +1,203 @@ +#include +#include + +#include "barretenberg/vm2/common/uint1.hpp" + +namespace bb::avm2 { +namespace { + +// Test constructors +TEST(Uint1Test, Constructors) +{ + // Default constructor should initialize to 0 + uint1_t default_val; + EXPECT_EQ(default_val.value(), 0); + + // Constructor from bool + uint1_t true_val(true); + EXPECT_EQ(true_val.value(), 1); + + uint1_t false_val(false); + EXPECT_EQ(false_val.value(), 0); + + // Constructor from integers + uint1_t from_int_zero(0); + EXPECT_EQ(from_int_zero.value(), 0); + + uint1_t from_int_one(1); + EXPECT_EQ(from_int_one.value(), 1); + + uint1_t from_int_two(2); + EXPECT_EQ(from_int_two.value(), 1); // Any non-zero value becomes 1 + + uint1_t from_int_negative(-1); + EXPECT_EQ(from_int_negative.value(), 1); // Any non-zero value becomes 1 +} + +// Test arithmetic operators +TEST(Uint1Test, ArithmeticOperators) +{ + uint1_t zero(0); + uint1_t one(1); + + // Addition + EXPECT_EQ((zero + zero).value(), 0); + EXPECT_EQ((zero + one).value(), 1); + EXPECT_EQ((one + zero).value(), 1); + EXPECT_EQ((one + one).value(), 0); // 1+1=0 with overflow (XOR behavior) + + // Subtraction + EXPECT_EQ((zero - zero).value(), 0); + EXPECT_EQ((zero - one).value(), 1); // 0-1=1 with underflow + EXPECT_EQ((one - zero).value(), 1); + EXPECT_EQ((one - one).value(), 0); + + // Multiplication + EXPECT_EQ((zero * zero).value(), 0); + EXPECT_EQ((zero * one).value(), 0); + EXPECT_EQ((one * zero).value(), 0); + EXPECT_EQ((one * one).value(), 1); + + // Division + EXPECT_EQ((zero / one).value(), 0); + EXPECT_EQ((one / one).value(), 1); + // Division by zero isn't tested as it's undefined behavior + + // Unary negation + EXPECT_EQ((-zero).value(), 1); + EXPECT_EQ((-one).value(), 0); +} + +// Test bitwise operators +TEST(Uint1Test, BitwiseOperators) +{ + uint1_t zero(0); + uint1_t one(1); + + // Bitwise AND + EXPECT_EQ((zero & zero).value(), 0); + EXPECT_EQ((zero & one).value(), 0); + EXPECT_EQ((one & zero).value(), 0); + EXPECT_EQ((one & one).value(), 1); + + // Bitwise OR + EXPECT_EQ((zero | zero).value(), 0); + EXPECT_EQ((zero | one).value(), 1); + EXPECT_EQ((one | zero).value(), 1); + EXPECT_EQ((one | one).value(), 1); + + // Bitwise XOR + EXPECT_EQ((zero ^ zero).value(), 0); + EXPECT_EQ((zero ^ one).value(), 1); + EXPECT_EQ((one ^ zero).value(), 1); + EXPECT_EQ((one ^ one).value(), 0); + + // Bitwise NOT + EXPECT_EQ((~zero).value(), 1); + EXPECT_EQ((~one).value(), 0); +} + +// Test shift operators +TEST(Uint1Test, ShiftOperators) +{ + uint1_t zero(0); + uint1_t one(1); + + // Left shift + EXPECT_EQ((zero << zero).value(), 0); + EXPECT_EQ((zero << one).value(), 0); + EXPECT_EQ((one << zero).value(), 1); + EXPECT_EQ((one << one).value(), 0); // 1<<1=2, which becomes 0 in uint1_t + + // Right shift + EXPECT_EQ((zero >> zero).value(), 0); + EXPECT_EQ((zero >> one).value(), 0); + EXPECT_EQ((one >> zero).value(), 1); + EXPECT_EQ((one >> one).value(), 0); +} + +// Test comparison operators +TEST(Uint1Test, ComparisonOperators) +{ + uint1_t zero(0); + uint1_t one(1); + uint1_t also_one(1); + + // Equality + EXPECT_TRUE(zero == zero); + EXPECT_FALSE(zero == one); + EXPECT_FALSE(one == zero); + EXPECT_TRUE(one == also_one); + + // Inequality + EXPECT_FALSE(zero != zero); + EXPECT_TRUE(zero != one); + EXPECT_TRUE(one != zero); + EXPECT_FALSE(one != also_one); + + // Less than + EXPECT_FALSE(zero < zero); + EXPECT_TRUE(zero < one); + EXPECT_FALSE(one < zero); + EXPECT_FALSE(one < also_one); + + // Less than or equal + EXPECT_TRUE(zero <= zero); + EXPECT_TRUE(zero <= one); + EXPECT_FALSE(one <= zero); + EXPECT_TRUE(one <= also_one); + + // Greater than + EXPECT_FALSE(zero > zero); + EXPECT_FALSE(zero > one); + EXPECT_TRUE(one > zero); + EXPECT_FALSE(one > also_one); + + // Greater than or equal + EXPECT_TRUE(zero >= zero); + EXPECT_FALSE(zero >= one); + EXPECT_TRUE(one >= zero); + EXPECT_TRUE(one >= also_one); +} + +// Test uint8_t conversion +TEST(Uint1Test, Conversion) +{ + uint1_t zero(0); + uint1_t one(1); + + // Test explicit conversion through value() + EXPECT_EQ(zero.value(), 0); + EXPECT_EQ(one.value(), 1); + + // Test implicit conversion to uint8_t + uint8_t zero_u8 = zero; + uint8_t one_u8 = one; + EXPECT_EQ(zero_u8, 0); + EXPECT_EQ(one_u8, 1); +} + +// Test behavior in complex expressions +TEST(Uint1Test, ComplexExpressions) +{ + uint1_t zero(0); + uint1_t one(1); + + // Test compound expressions + EXPECT_EQ(((zero | one) & ~zero).value(), 1); + EXPECT_EQ(((zero & one) | zero).value(), 0); + EXPECT_EQ(((zero ^ one) ^ zero).value(), 1); + + // Test with arithmetic and bitwise operations mixed + EXPECT_EQ((zero + one * zero).value(), 0); + EXPECT_EQ((zero | (one & zero)).value(), 0); + EXPECT_EQ((zero + (one | zero)).value(), 1); + + // Test more complex expressions + EXPECT_EQ(((zero | one) + (zero & zero)).value(), 1); + EXPECT_EQ(((zero ^ one) - (one & zero)).value(), 1); + EXPECT_EQ(((~zero & one) * (zero | zero)).value(), 0); +} + +} // namespace +} // namespace bb::avm2 diff --git a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/bitwise.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/bitwise.test.cpp index fde9aad9ccd1..620946595065 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/bitwise.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/bitwise.test.cpp @@ -42,25 +42,35 @@ TEST(BitwiseConstrainingTest, AndWithTracegen) { TestTraceContainer trace; BitwiseTraceBuilder builder; - - builder.process( - { - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U1, .a = 1, .b = 1, .res = 1 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U8, .a = 85, .b = 175, .res = 5 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U16, .a = 5323, .b = 321, .res = 65 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U32, .a = 13793, .b = 10590617, .res = 4481 }, - { .operation = BitwiseOperation::AND, - .tag = MemoryTag::U64, - .a = 0x7bff744e3cdf79LLU, - .b = 0x14ccccccccb6LLU, - .res = 0x14444c0ccc30LLU }, - { .operation = BitwiseOperation::AND, - .tag = MemoryTag::U128, - .a = uint128_t{ 0xb900000000000001 } << 64, - .b = (uint128_t{ 0x1006021301080000 } << 64) + uint128_t{ 0x000000000000001080876844827 }, - .res = uint128_t{ 0x1000000000000000 } << 64 }, - }, - trace); + std::vector events = { + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(uint1_t(1)), + .b = MemoryValue::from(uint1_t(1)), + .res = MemoryValue::from(uint1_t(1)) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(85), + .b = MemoryValue::from(175), + .res = MemoryValue::from(5) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(5323), + .b = MemoryValue::from(321), + .res = MemoryValue::from(65) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(13793), + .b = MemoryValue::from(10590617), + .res = MemoryValue::from(4481) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(0x7bff744e3cdf79LLU), + .b = MemoryValue::from(0x14ccccccccb6LLU), + .res = MemoryValue::from(0x14444c0ccc30LLU) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from((uint128_t{ 0xb900000000000001 } << 64)), + .b = MemoryValue::from((uint128_t{ 0x1006021301080000 } << 64) + + uint128_t{ 0x000000000000001080876844827 }), + .res = MemoryValue::from((uint128_t{ 0x1000000000000000 } << 64)) }, + }; + + builder.process(events, trace); EXPECT_EQ(trace.get_num_rows(), 33); // 33 = 1 + 1 + 1 + 2 + 4 + 8 + 16 (extra_shift_row U1 U8 U16 U32 U64 U128) check_relation(trace); @@ -71,25 +81,36 @@ TEST(BitwiseConstrainingTest, OrWithTracegen) { TestTraceContainer trace; BitwiseTraceBuilder builder; - - builder.process( - { - { .operation = BitwiseOperation::OR, .tag = MemoryTag::U1, .a = 1, .b = 0, .res = 1 }, - { .operation = BitwiseOperation::OR, .tag = MemoryTag::U8, .a = 128, .b = 127, .res = 255 }, - { .operation = BitwiseOperation::OR, .tag = MemoryTag::U16, .a = 5323, .b = 321, .res = 5579 }, - { .operation = BitwiseOperation::OR, .tag = MemoryTag::U32, .a = 13793, .b = 10590617, .res = 10599929 }, - { .operation = BitwiseOperation::OR, - .tag = MemoryTag::U64, - .a = 0x7bff744e3cdf79LLU, - .b = 0x14ccccccccb6LLU, - .res = 0x7bfffccefcdfffLLU }, - { .operation = BitwiseOperation::OR, - .tag = MemoryTag::U128, - .a = uint128_t{ 0xb900000000000000 } << 64, - .b = (uint128_t{ 0x1006021301080000 } << 64) + uint128_t{ 0x000000000000001080876844827 }, - .res = (uint128_t{ 0xb906021301080000 } << 64) + uint128_t{ 0x0001080876844827 } }, - }, - trace); + std::vector events = { + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from(uint1_t(1)), + .b = MemoryValue::from(uint1_t(0)), + .res = MemoryValue::from(uint1_t(1)) }, + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from(128), + .b = MemoryValue::from(127), + .res = MemoryValue::from(255) }, + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from(5323), + .b = MemoryValue::from(321), + .res = MemoryValue::from(5579) }, + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from(13793), + .b = MemoryValue::from(10590617), + .res = MemoryValue::from(10599929) }, + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from(0x7bff744e3cdf79LLU), + .b = MemoryValue::from(0x14ccccccccb6LLU), + .res = MemoryValue::from(0x7bfffccefcdfffLLU) }, + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from((uint128_t{ 0xb900000000000000 } << 64)), + .b = MemoryValue::from((uint128_t{ 0x1006021301080000 } << 64) + + uint128_t{ 0x000000000000001080876844827 }), + .res = + MemoryValue::from((uint128_t{ 0xb906021301080000 } << 64) + uint128_t{ 0x0001080876844827 }) }, + }; + + builder.process(events, trace); EXPECT_EQ(trace.get_num_rows(), 33); // 33 = 1 + 1 + 1 + 2 + 4 + 8 + 16 (extra_shift_row U1 U8 U16 U32 U64 U128) check_relation(trace); @@ -101,26 +122,36 @@ TEST(BitwiseConstrainingTest, XorWithTracegen) TestTraceContainer trace; BitwiseTraceBuilder builder; - builder.process( - { - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U1, .a = 1, .b = 1, .res = 0 }, - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U8, .a = 85, .b = 175, .res = 250 }, - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U16, .a = 5323, .b = 321, .res = 5514 }, - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U32, .a = 13793, .b = 10590617, .res = 10595448 }, - { .operation = BitwiseOperation::XOR, - .tag = MemoryTag::U64, - .a = 0x7bff744e3cdf79LLU, - .b = 0x14ccccccccb6LLU, - .res = 0x7bebb882f013cfLLU }, - { - .operation = BitwiseOperation::XOR, - .tag = MemoryTag::U128, - .a = uint128_t{ 0xb900000000000001 } << 64, - .b = (uint128_t{ 0x1006021301080000 } << 64) + uint128_t{ 0x000000000000001080876844827 }, - .res = (uint128_t{ 0xa906021301080001 } << 64) + uint128_t{ 0x0001080876844827 }, - }, - }, - trace); + std::vector events = { + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(uint1_t(1)), + .b = MemoryValue::from(uint1_t(1)), + .res = MemoryValue::from(uint1_t(0)) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(85), + .b = MemoryValue::from(175), + .res = MemoryValue::from(250) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(5323), + .b = MemoryValue::from(321), + .res = MemoryValue::from(5514) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(13793), + .b = MemoryValue::from(10590617), + .res = MemoryValue::from(10595448) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(0x7bff744e3cdf79LLU), + .b = MemoryValue::from(0x14ccccccccb6LLU), + .res = MemoryValue::from(0x7bebb882f013cfLLU) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from((uint128_t{ 0xb900000000000001 } << 64)), + .b = MemoryValue::from((uint128_t{ 0x1006021301080000 } << 64) + + uint128_t{ 0x000000000000001080876844827 }), + .res = + MemoryValue::from((uint128_t{ 0xa906021301080001 } << 64) + uint128_t{ 0x0001080876844827 }) }, + }; + + builder.process(events, trace); EXPECT_EQ(trace.get_num_rows(), 33); // 33 = 1 + 1 + 1 + 2 + 4 + 8 + 16 (extra_shift_row U1 U8 U16 U32 U64 U128) check_relation(trace); @@ -130,17 +161,34 @@ TEST(BitwiseConstrainingTest, MixedOperationsWithTracegen) { TestTraceContainer trace; BitwiseTraceBuilder builder; - - builder.process( - { - { .operation = BitwiseOperation::OR, .tag = MemoryTag::U1, .a = 1, .b = 0, .res = 1 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U32, .a = 13793, .b = 10590617, .res = 4481 }, - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U16, .a = 5323, .b = 321, .res = 5514 }, - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U32, .a = 13793, .b = 10590617, .res = 10595448 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U8, .a = 85, .b = 175, .res = 5 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U8, .a = 85, .b = 175, .res = 5 }, - }, - trace); + std::vector events = { + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from(uint1_t(1)), + .b = MemoryValue::from(uint1_t(0)), + .res = MemoryValue::from(uint1_t(1)) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(13793), + .b = MemoryValue::from(10590617), + .res = MemoryValue::from(4481) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(5323), + .b = MemoryValue::from(321), + .res = MemoryValue::from(5514) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(13793), + .b = MemoryValue::from(10590617), + .res = MemoryValue::from(10595448) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(85), + .b = MemoryValue::from(175), + .res = MemoryValue::from(5) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(85), + .b = MemoryValue::from(175), + .res = MemoryValue::from(5) }, + }; + + builder.process(events, trace); EXPECT_EQ(trace.get_num_rows(), 14); // 14 = 1 + 3 * 1 + 1 * 2 + 2 * 4 (extra_shift_row + 2U1 + 1U8 + 1U16 + 2U32) check_relation(trace); @@ -330,17 +378,34 @@ TEST(BitwiseConstrainingTest, MixedOperationsInteractions) TestTraceContainer trace; BitwiseTraceBuilder builder; PrecomputedTraceBuilder precomputed_builder; - - builder.process( - { - { .operation = BitwiseOperation::OR, .tag = MemoryTag::U1, .a = 1, .b = 0, .res = 1 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U32, .a = 13793, .b = 10590617, .res = 4481 }, - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U16, .a = 5323, .b = 321, .res = 5514 }, - { .operation = BitwiseOperation::XOR, .tag = MemoryTag::U32, .a = 13793, .b = 10590617, .res = 10595448 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U8, .a = 85, .b = 175, .res = 5 }, - { .operation = BitwiseOperation::AND, .tag = MemoryTag::U8, .a = 85, .b = 175, .res = 5 }, - }, - trace); + std::vector events = { + { .operation = BitwiseOperation::OR, + .a = MemoryValue::from(uint1_t(1)), + .b = MemoryValue::from(uint1_t(0)), + .res = MemoryValue::from(uint1_t(1)) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(13793), + .b = MemoryValue::from(10590617), + .res = MemoryValue::from(4481) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(5323), + .b = MemoryValue::from(321), + .res = MemoryValue::from(5514) }, + { .operation = BitwiseOperation::XOR, + .a = MemoryValue::from(13793), + .b = MemoryValue::from(10590617), + .res = MemoryValue::from(10595448) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(85), + .b = MemoryValue::from(175), + .res = MemoryValue::from(5) }, + { .operation = BitwiseOperation::AND, + .a = MemoryValue::from(85), + .b = MemoryValue::from(175), + .res = MemoryValue::from(5) }, + }; + + builder.process(events, trace); precomputed_builder.process_misc(trace, 256 * 256 * 3); precomputed_builder.process_bitwise(trace); diff --git a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/instr_fetching.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/instr_fetching.test.cpp index 716e59a61c3d..c49395b7c059 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/instr_fetching.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/instr_fetching.test.cpp @@ -68,7 +68,7 @@ TEST(InstrFetchingConstrainingTest, Add8WithTraceGen) Instruction add_8_instruction = { .opcode = WireOpCode::ADD_8, .indirect = 3, - .operands = { Operand::u8(0x34), Operand::u8(0x35), Operand::u8(0x36) }, + .operands = { Operand::from(0x34), Operand::from(0x35), Operand::from(0x36) }, }; std::vector bytecode = add_8_instruction.serialize(); @@ -95,13 +95,13 @@ TEST(InstrFetchingConstrainingTest, EcaddWithTraceGen) Instruction ecadd_instruction = { .opcode = WireOpCode::ECADD, .indirect = 0x1f1f, - .operands = { Operand::u16(0x1279), - Operand::u16(0x127a), - Operand::u16(0x127b), - Operand::u16(0x127c), - Operand::u16(0x127d), - Operand::u16(0x127e), - Operand::u16(0x127f) }, + .operands = { Operand::from(0x1279), + Operand::from(0x127a), + Operand::from(0x127b), + Operand::from(0x127c), + Operand::from(0x127d), + Operand::from(0x127e), + Operand::from(0x127f) }, }; std::vector bytecode = ecadd_instruction.serialize(); @@ -378,7 +378,7 @@ TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRange) Instruction add_8_instruction = { .opcode = WireOpCode::ADD_8, .indirect = 3, - .operands = { Operand::u8(0x34), Operand::u8(0x35), Operand::u8(0x36) }, + .operands = { Operand::from(0x34), Operand::from(0x35), Operand::from(0x36) }, }; std::vector bytecode = add_8_instruction.serialize(); @@ -413,9 +413,9 @@ TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRangeSplitOperand) Instruction set_ff_instruction = { .opcode = WireOpCode::SET_FF, .indirect = 0x01, - .operands = { Operand::u16(0x1279), - Operand::u8(static_cast(MemoryTag::FF)), - Operand::ff(FF::modulus_minus_two) }, + .operands = { Operand::from(0x1279), + Operand::from(static_cast(MemoryTag::FF)), + Operand::from(FF::modulus_minus_two) }, }; std::vector bytecode = set_ff_instruction.serialize(); @@ -447,7 +447,7 @@ TEST(InstrFetchingConstrainingTest, SingleInstructionPcOutOfRange) Instruction add_8_instruction = { .opcode = WireOpCode::SUB_8, .indirect = 3, - .operands = { Operand::u8(0x34), Operand::u8(0x35), Operand::u8(0x36) }, + .operands = { Operand::from(0x34), Operand::from(0x35), Operand::from(0x36) }, }; std::vector bytecode = add_8_instruction.serialize(); @@ -487,9 +487,9 @@ TEST(InstrFetchingConstrainingTest, SingleInstructionOpcodeOutOfRange) Instruction set_128_instruction = { .opcode = WireOpCode::SET_128, .indirect = 0, - .operands = { Operand::u16(0x1234), - Operand::u8(static_cast(MemoryTag::U128)), - Operand::u128(static_cast(0xFF) << 120) }, + .operands = { Operand::from(0x1234), + Operand::from(static_cast(MemoryTag::U128)), + Operand::from(static_cast(0xFF) << 120) }, }; std::vector bytecode = set_128_instruction.serialize(); @@ -528,7 +528,7 @@ TEST(InstrFetchingConstrainingTest, SingleInstructionTagOutOfRange) Instruction set_16_instruction = { .opcode = WireOpCode::SET_16, .indirect = 0, - .operands = { Operand::u16(0x1234), Operand::u8(12), Operand::u16(0x5678) }, + .operands = { Operand::from(0x1234), Operand::from(12), Operand::from(0x5678) }, }; std::vector bytecode = set_16_instruction.serialize(); diff --git a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/sha256.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/sha256.test.cpp index fb3af74d690a..87c7ea1d1ed2 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/sha256.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/sha256.test.cpp @@ -64,13 +64,13 @@ TEST(Sha256ConstrainingTest, Basic) std::array state = { 0, 1, 2, 3, 4, 5, 6, 7 }; MemoryAddress state_addr = 0; for (uint32_t i = 0; i < 8; ++i) { - mem.set(state_addr + i, state[i], MemoryTag::U32); + mem.set(state_addr + i, MemoryValue::from(state[i])); } std::array input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; MemoryAddress input_addr = 8; for (uint32_t i = 0; i < 16; ++i) { - mem.set(input_addr + i, input[i], MemoryTag::U32); + mem.set(input_addr + i, MemoryValue::from(input[i])); } MemoryAddress dst_addr = 25; @@ -100,13 +100,13 @@ TEST(Sha256ConstrainingTest, Interaction) std::array state = { 0, 1, 2, 3, 4, 5, 6, 7 }; MemoryAddress state_addr = 0; for (uint32_t i = 0; i < 8; ++i) { - mem.set(state_addr + i, state[i], MemoryTag::U32); + mem.set(state_addr + i, MemoryValue::from(state[i])); } std::array input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; MemoryAddress input_addr = 8; for (uint32_t i = 0; i < 16; ++i) { - mem.set(input_addr + i, input[i], MemoryTag::U32); + mem.set(input_addr + i, MemoryValue::from(input[i])); } MemoryAddress dst_addr = 25; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/addressing.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/addressing.cpp index 7c0e047a397d..02cfb1b0151d 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/addressing.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/addressing.cpp @@ -33,8 +33,7 @@ std::vector Addressing::resolve(const Instruction& instruction, MemoryI // However, we can't check the value and tag yet! This should be done only if it's used. // This is because the first few instructions might not YET have a valid stack pointer. auto base_address = memory.get(0); - event.base_address_tag = base_address.tag; - event.base_address_val = base_address.value; + event.base_address = base_address; // First process relative addressing for all the addresses. event.after_relative = instruction.operands; @@ -44,9 +43,9 @@ std::vector Addressing::resolve(const Instruction& instruction, MemoryI throw AddressingException(AddressingEventError::BASE_ADDRESS_INVALID_ADDRESS, i); } - MemoryValue offset(event.after_relative[i]); - offset += base_address.value; - event.after_relative[i] = Operand::ff(offset); + FF offset = event.after_relative[i]; + offset = offset + base_address; + event.after_relative[i] = Operand::from(offset); if (!memory.is_valid_address(offset)) { throw AddressingException(AddressingEventError::RELATIVE_COMPUTATION_OOB, i); } @@ -57,12 +56,12 @@ std::vector Addressing::resolve(const Instruction& instruction, MemoryI event.resolved_operands = event.after_relative; for (size_t i = 0; i < spec.num_addresses; ++i) { if ((instruction.indirect >> (i + spec.num_addresses)) & 1) { - MemoryValue offset(event.resolved_operands[i]); + FF offset = event.resolved_operands[i]; if (!memory.is_valid_address(offset)) { throw AddressingException(AddressingEventError::INDIRECT_INVALID_ADDRESS, i); } - auto new_address = memory.get(static_cast(offset)); - event.resolved_operands[i] = Operand::ff(new_address.value); + FF new_address = memory.get(static_cast(offset)); + event.resolved_operands[i] = Operand::from(new_address); } } @@ -72,7 +71,7 @@ std::vector Addressing::resolve(const Instruction& instruction, MemoryI throw AddressingException(AddressingEventError::FINAL_ADDRESS_INVALID, i); } event.resolved_operands[i] = - static_cast(static_cast(event.resolved_operands[i].operator FF())); + Operand::from(static_cast(event.resolved_operands[i].as_ff())); } } catch (const AddressingException& e) { event.error = e; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.cpp index 2fbad927735f..5320a0c4a139 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.cpp @@ -5,15 +5,12 @@ namespace bb::avm2::simulation { -FF Alu::add(const ValueRefAndTag& a, const ValueRefAndTag& b) +MemoryValue Alu::add(const MemoryValue& a, const MemoryValue& b) { // TODO: check types and tags and propagate. - // TODO(ilyas): need big switch here for different types, wrapping - // TODO(ilyas): come up with a better way than a big switch - FF c = a.value + b.value; + MemoryValue c = a + b; - // TODO: add tags to events. - events.emit({ .operation = AluOperation::ADD, .a = a.value, .b = b.value, .c = c }); + events.emit({ .operation = AluOperation::ADD, .a = a, .b = b, .c = c }); return c; } diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.hpp index 28c5c00a0a70..3e0daf3e1653 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.hpp @@ -14,8 +14,7 @@ namespace bb::avm2::simulation { class AluInterface { public: virtual ~AluInterface() = default; - // I'd like to return a ValueRefAndTag, but the MemoryValue& doesnt live long enough. - virtual FF add(const ValueRefAndTag& a, const ValueRefAndTag& b) = 0; + virtual MemoryValue add(const MemoryValue& a, const MemoryValue& b) = 0; }; class Alu : public AluInterface { @@ -24,7 +23,7 @@ class Alu : public AluInterface { : events(event_emitter) {} - FF add(const ValueRefAndTag& a, const ValueRefAndTag& b) override; + MemoryValue add(const MemoryValue& a, const MemoryValue& b) override; private: EventEmitterInterface& events; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.test.cpp index 6e0a87a42882..c86dcec2a249 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/alu.test.cpp @@ -17,12 +17,12 @@ TEST(AvmSimulationAluTest, Add) EventEmitter alu_event_emitter; Alu alu(alu_event_emitter); - ValueRefAndTag a = { .value = 1, .tag = MemoryTag::U32 }; - ValueRefAndTag b = { .value = 2, .tag = MemoryTag::U32 }; + auto a = MemoryValue::from(1); + auto b = MemoryValue::from(2); - FF c = alu.add(a, b); + auto c = alu.add(a, b); - EXPECT_EQ(c, 3); + EXPECT_EQ(c, MemoryValue::from(3)); } } // namespace diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.cpp index 2bf39697ab8d..3b57f27a6c81 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.cpp @@ -1,5 +1,6 @@ #include "barretenberg/vm2/simulation/bitwise.hpp" +#include #include #include "barretenberg/numeric/uint256/uint256.hpp" @@ -7,13 +8,15 @@ namespace bb::avm2::simulation { -uint128_t Bitwise::and_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) +MemoryValue Bitwise::and_op(const MemoryValue& a, const MemoryValue& b) { - const uint128_t c = a & b; + // Tag compatibility should be checked at the caller... should it? + assert(a.get_tag() == b.get_tag()); + + MemoryValue c = a & b; events.emit({ .operation = BitwiseOperation::AND, - .tag = tag, .a = a, .b = b, .res = c, @@ -22,13 +25,15 @@ uint128_t Bitwise::and_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) return c; } -uint128_t Bitwise::or_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) +MemoryValue Bitwise::or_op(const MemoryValue& a, const MemoryValue& b) { - const uint128_t c = a | b; + // Tag compatibility should be checked at the caller... should it? + assert(a.get_tag() == b.get_tag()); + + MemoryValue c = a | b; events.emit({ .operation = BitwiseOperation::OR, - .tag = tag, .a = a, .b = b, .res = c, @@ -37,13 +42,15 @@ uint128_t Bitwise::or_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) return c; } -uint128_t Bitwise::xor_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) +MemoryValue Bitwise::xor_op(const MemoryValue& a, const MemoryValue& b) { - const uint128_t c = a ^ b; + // Tag compatibility should be checked at the caller... should it? + assert(a.get_tag() == b.get_tag()); + + MemoryValue c = a ^ b; events.emit({ .operation = BitwiseOperation::XOR, - .tag = tag, .a = a, .b = b, .res = c, @@ -52,4 +59,4 @@ uint128_t Bitwise::xor_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) return c; } -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.hpp index 3e9217d7a271..a2238bed1204 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/bitwise.hpp @@ -3,20 +3,17 @@ #include #include "barretenberg/vm2/common/memory_types.hpp" -#include "barretenberg/vm2/simulation/context.hpp" #include "barretenberg/vm2/simulation/events/bitwise_event.hpp" #include "barretenberg/vm2/simulation/events/event_emitter.hpp" -#include "barretenberg/vm2/simulation/memory.hpp" namespace bb::avm2::simulation { -// TODO(fcarreiro): think if it makes sense to have memory types like in TS with implicit tag class BitwiseInterface { public: virtual ~BitwiseInterface() = default; - virtual uint128_t and_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) = 0; - virtual uint128_t or_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) = 0; - virtual uint128_t xor_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) = 0; + virtual MemoryValue and_op(const MemoryValue& a, const MemoryValue& b) = 0; + virtual MemoryValue or_op(const MemoryValue& a, const MemoryValue& b) = 0; + virtual MemoryValue xor_op(const MemoryValue& a, const MemoryValue& b) = 0; }; class Bitwise : public BitwiseInterface { @@ -25,15 +22,14 @@ class Bitwise : public BitwiseInterface { : events(event_emitter) {} - // Operands are expected to be direct. - uint128_t and_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) override; - uint128_t or_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) override; - uint128_t xor_op(MemoryTag tag, const uint128_t& a, const uint128_t& b) override; + MemoryValue and_op(const MemoryValue& a, const MemoryValue& b) override; + MemoryValue or_op(const MemoryValue& a, const MemoryValue& b) override; + MemoryValue xor_op(const MemoryValue& a, const MemoryValue& b) override; private: - // TODO: Use deduplicating events + consider (see bottom paragraph of bitwise.pil) a further deduplocation + // TODO: Use deduplicating events + consider (see bottom paragraph of bitwise.pil) a further deduplication // when some inputs are prefixes of another ones (with a bigger tag). EventEmitterInterface& events; }; -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/context.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/context.hpp index 1dd4b3bdc2ac..600a633876c0 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/context.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/context.hpp @@ -115,11 +115,13 @@ class BaseContext : public ContextInterface { { MemoryInterface& child_memory = get_child_context().get_memory(); auto get_returndata_size = child_memory.get(last_child_rd_size); - uint32_t returndata_size = static_cast(get_returndata_size.value); + uint32_t returndata_size = get_returndata_size.as(); uint32_t write_size = std::min(rd_offset + rd_size, returndata_size); - std::vector retrieved_returndata = - child_memory.get_slice(get_last_rd_offset() + rd_offset, write_size).first; + std::vector retrieved_returndata; + for (uint32_t i = 0; i < write_size; i++) { + retrieved_returndata.push_back(child_memory.get(get_last_rd_offset() + i)); + } retrieved_returndata.resize(rd_size); return retrieved_returndata; @@ -230,12 +232,15 @@ class NestedContext : public BaseContext { // Input / Output std::vector get_calldata(uint32_t cd_offset, uint32_t cd_size) const override { - ValueRefAndTag get_calldata_size = parent_context.get_memory().get(parent_cd_size); + auto get_calldata_size = parent_context.get_memory().get(parent_cd_size); // TODO(ilyas): error if tag != U32 - auto calldata_size = static_cast(get_calldata_size.value); + auto calldata_size = get_calldata_size.as(); uint32_t read_size = std::min(cd_offset + cd_size, calldata_size); - auto retrieved_calldata = parent_context.get_memory().get_slice(parent_cd_offset + cd_offset, read_size).first; + std::vector retrieved_calldata; + for (uint32_t i = 0; i < read_size; i++) { + retrieved_calldata.push_back(parent_context.get_memory().get(parent_cd_offset + i)); + } // Pad the calldata retrieved_calldata.resize(cd_size, 0); diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/addressing_event.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/addressing_event.hpp index 1e4facd8e660..d3c292f80e6a 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/addressing_event.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/addressing_event.hpp @@ -35,10 +35,9 @@ struct AddressingEvent { Instruction instruction; std::vector after_relative; std::vector resolved_operands; - MemoryValue base_address_val; - MemoryTag base_address_tag; + MemoryValue base_address; const ExecInstructionSpec* spec = nullptr; std::optional error; }; -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/alu_event.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/alu_event.hpp index 0ea117f15041..8380fef622ca 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/alu_event.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/alu_event.hpp @@ -17,13 +17,9 @@ struct AluEvent { MemoryValue a; MemoryValue b; MemoryValue c; - // Only need single tag info here (check this for MOV or CAST ) - // For operations that have a specific output tag (e.g., EQ/LT), the output tag is unambiguous - // We still might prefer to include tags per operands to simply tracegen... - MemoryTag tag; // To be used with deduplicating event emitters. - using Key = std::tuple; - Key get_key() const { return { operation, a, b, c, tag }; } + using Key = std::tuple; + Key get_key() const { return { operation, a, b }; } }; } // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/bitwise_event.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/bitwise_event.hpp index 0be0861bb312..46abaa1f57f4 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/bitwise_event.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/bitwise_event.hpp @@ -9,10 +9,9 @@ namespace bb::avm2::simulation { struct BitwiseEvent { BitwiseOperation operation; - MemoryTag tag; - uint128_t a; - uint128_t b; - uint128_t res; + MemoryValue a; + MemoryValue b; + MemoryValue res; }; -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/memory_event.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/memory_event.hpp index 061246f63f48..9721914cce46 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/events/memory_event.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/events/memory_event.hpp @@ -15,8 +15,7 @@ struct MemoryEvent { MemoryMode mode; MemoryAddress addr; MemoryValue value; - MemoryTag tag; uint32_t space_id; }; -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp index 9867295acb77..b78b77ca983f 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp @@ -16,23 +16,23 @@ namespace bb::avm2::simulation { void Execution::add(ContextInterface& context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr) { auto& memory = context.get_memory(); - ValueRefAndTag a = memory.get(a_addr); - ValueRefAndTag b = memory.get(b_addr); - FF c = alu.add(a, b); - memory.set(dst_addr, c, a.tag); + MemoryValue a = memory.get(a_addr); + MemoryValue b = memory.get(b_addr); + MemoryValue c = alu.add(a, b); + memory.set(dst_addr, c); } // TODO: My dispatch system makes me have a uint8_t tag. Rethink. -void Execution::set(ContextInterface& context, MemoryAddress dst_addr, uint8_t tag, MemoryValue value) +void Execution::set(ContextInterface& context, MemoryAddress dst_addr, uint8_t tag, FF value) { - context.get_memory().set(dst_addr, std::move(value), static_cast(tag)); + context.get_memory().set(dst_addr, MemoryValue::from_tag(static_cast(tag), value)); } void Execution::mov(ContextInterface& context, MemoryAddress src_addr, MemoryAddress dst_addr) { auto& memory = context.get_memory(); - auto [value, tag] = memory.get(src_addr); - memory.set(dst_addr, value, tag); + auto v = memory.get(src_addr); + memory.set(dst_addr, v); } void Execution::call(ContextInterface& context, MemoryAddress addr, MemoryAddress cd_offset, MemoryAddress cd_size) @@ -44,7 +44,7 @@ void Execution::call(ContextInterface& context, MemoryAddress addr, MemoryAddres // TODO: Read more stuff from call operands (e.g., calldata, gas) // TODO(ilyas): How will we tag check these? - const auto [contract_address, _] = memory.get(addr); + FF contract_address = memory.get(addr).as_ff(); // We could load cd_size here, but to keep symmetry with cd_offset - we will defer the loads to a (possible) // calldatacopy @@ -86,7 +86,7 @@ void Execution::jumpi(ContextInterface& context, MemoryAddress cond_addr, uint32 // TODO: in gadget. auto resolved_cond = memory.get(cond_addr); - if (!resolved_cond.value.is_zero()) { + if (!resolved_cond.as_ff().is_zero()) { context.set_next_pc(loc); } } @@ -195,9 +195,8 @@ inline void Execution::call_with_operands(void (Execution::*f)(ContextInterface& { assert(resolved_operands.size() == sizeof...(Ts)); auto operand_indices = std::make_index_sequence{}; - using types = std::tuple; [f, this, &context, &resolved_operands](std::index_sequence) { - (this->*f)(context, static_cast>(resolved_operands[Is])...); + (this->*f)(context, resolved_operands.at(Is).template as()...); }(operand_indices); } diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.hpp index f999214a67aa..6ed83debfa51 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.hpp @@ -59,7 +59,7 @@ class Execution : public ExecutionInterface { // Opcode handlers. The order of the operands matters and should be the same as the wire format. void add(ContextInterface& context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr); - void set(ContextInterface& context, MemoryAddress dst_addr, uint8_t tag, MemoryValue value); + void set(ContextInterface& context, MemoryAddress dst_addr, uint8_t tag, FF value); void mov(ContextInterface& context, MemoryAddress src_addr, MemoryAddress dst_addr); void jump(ContextInterface& context, uint32_t loc); void jumpi(ContextInterface& context, MemoryAddress cond_addr, uint32_t loc); diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.test.cpp index f4bc14f7e983..2b4a6afcade9 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.test.cpp @@ -46,13 +46,13 @@ class ExecutionSimulationTest : public ::testing::Test { TEST_F(ExecutionSimulationTest, Add) { - ValueRefAndTag a = { .value = 4, .tag = MemoryTag::U32 }; - ValueRefAndTag b = { .value = 5, .tag = MemoryTag::U32 }; + MemoryValue a = MemoryValue::from(4); + MemoryValue b = MemoryValue::from(5); EXPECT_CALL(context, get_memory); - EXPECT_CALL(memory, get).Times(2).WillOnce(Return(a)).WillOnce(Return(b)); - EXPECT_CALL(alu, add(a, b)).WillOnce(Return(9)); - EXPECT_CALL(memory, set(6, FF(9), MemoryTag::U32)); + EXPECT_CALL(memory, get).Times(2).WillOnce(ReturnRef(a)).WillOnce(ReturnRef(b)); + EXPECT_CALL(alu, add(a, b)).WillOnce(Return(MemoryValue::from(9))); + EXPECT_CALL(memory, set(6, MemoryValue::from(9))); execution.add(context, 4, 5, 6); } @@ -61,7 +61,7 @@ TEST_F(ExecutionSimulationTest, Call) AztecAddress parent_address = 1; AztecAddress nested_address = 2; - + MemoryValue nested_address_value = MemoryValue::from(nested_address); // Context snapshotting EXPECT_CALL(context, get_context_id); EXPECT_CALL(context, get_next_pc); @@ -70,7 +70,7 @@ TEST_F(ExecutionSimulationTest, Call) EXPECT_CALL(context, get_memory); EXPECT_CALL(context, get_address).WillRepeatedly(ReturnRef(parent_address)); - EXPECT_CALL(memory, get).WillOnce(Return(ValueRefAndTag({ .value = nested_address, .tag = MemoryTag::U32 }))); + EXPECT_CALL(memory, get).WillOnce(ReturnRef(nested_address_value)); auto nested_context = std::make_unique>(); ON_CALL(*nested_context, halted()) diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp index e3c973632bce..2db13c165e17 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp @@ -209,159 +209,6 @@ bool is_wire_opcode_valid(uint8_t w_opcode) } // namespace -Operand::Operand(const Operand& other) -{ - // Lazy implementation using the assignment operator. - *this = other; -} - -Operand& Operand::operator=(const Operand& other) -{ - if (this != &other) { - if (std::holds_alternative(other.value)) { - value = std::get(other.value); - } else if (std::holds_alternative(other.value)) { - value = std::get(other.value); - } else if (std::holds_alternative(other.value)) { - value = std::get(other.value); - } else if (std::holds_alternative(other.value)) { - value = std::get(other.value); - } else if (std::holds_alternative(other.value)) { - value = std::make_unique(*std::get(other.value)); - } else { - value = std::make_unique(*std::get(other.value)); - } - } - return *this; -} - -bool Operand::operator==(const Operand& other) const -{ - if (this == &other) { - return true; - } - - if (value.index() != other.value.index()) { - return false; - } - - if (std::holds_alternative(value)) { - return *std::get(value) == *std::get(other.value); - } - - if (std::holds_alternative(value)) { - return *std::get(value) == *std::get(other.value); - } - - return value == other.value; -} - -Operand::operator bool() const -{ - return (this->operator uint8_t() == 1); -} - -Operand::operator uint8_t() const -{ - if (std::holds_alternative(value)) { - return std::get(value); - } - - throw std::runtime_error("Operand does not fit in uint8_t"); -} - -Operand::operator uint16_t() const -{ - if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } - - throw std::runtime_error("Operand does not fit in uint16_t"); -} - -Operand::operator uint32_t() const -{ - if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } - - throw std::runtime_error("Operand does not fit in uint32_t"); -} - -Operand::operator uint64_t() const -{ - if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } - - throw std::runtime_error("Operand does not fit in uint64_t"); -} - -Operand::operator uint128_t() const -{ - if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return *std::get(value); - } - - throw std::runtime_error("Operand does not fit in uint128_t"); -} - -Operand::operator FF() const -{ - if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return std::get(value); - } else if (std::holds_alternative(value)) { - return uint256_t::from_uint128(*std::get(value)); - } else { - return *std::get(value); - } -} - -std::string Operand::to_string() const -{ - if (std::holds_alternative(value)) { - return std::to_string(std::get(value)); - } else if (std::holds_alternative(value)) { - return std::to_string(std::get(value)); - } else if (std::holds_alternative(value)) { - return std::to_string(std::get(value)); - } else if (std::holds_alternative(value)) { - return std::to_string(std::get(value)); - } else if (std::holds_alternative(value)) { - return "someu128"; - } else if (std::holds_alternative(value)) { - return "someff"; - } - - __builtin_unreachable(); -} - Instruction deserialize_instruction(std::span bytecode, size_t pos) { const auto bytecode_length = bytecode.size(); @@ -411,7 +258,7 @@ Instruction deserialize_instruction(std::span bytecode, size_t po switch (op_type) { case OperandType::TAG: case OperandType::UINT8: { - operands.emplace_back(bytecode[pos]); + operands.emplace_back(Operand::from(bytecode[pos])); break; } case OperandType::INDIRECT8: { @@ -429,35 +276,35 @@ Instruction deserialize_instruction(std::span bytecode, size_t po uint16_t operand_u16 = 0; uint8_t const* pos_ptr = &bytecode[pos]; serialize::read(pos_ptr, operand_u16); - operands.emplace_back(operand_u16); + operands.emplace_back(Operand::from(operand_u16)); break; } case OperandType::UINT32: { uint32_t operand_u32 = 0; uint8_t const* pos_ptr = &bytecode[pos]; serialize::read(pos_ptr, operand_u32); - operands.emplace_back(operand_u32); + operands.emplace_back(Operand::from(operand_u32)); break; } case OperandType::UINT64: { uint64_t operand_u64 = 0; uint8_t const* pos_ptr = &bytecode[pos]; serialize::read(pos_ptr, operand_u64); - operands.emplace_back(operand_u64); + operands.emplace_back(Operand::from(operand_u64)); break; } case OperandType::UINT128: { uint128_t operand_u128 = 0; uint8_t const* pos_ptr = &bytecode[pos]; serialize::read(pos_ptr, operand_u128); - operands.emplace_back(Operand::u128(operand_u128)); + operands.emplace_back(Operand::from(operand_u128)); break; } case OperandType::FF: { FF operand_ff; uint8_t const* pos_ptr = &bytecode[pos]; read(pos_ptr, operand_ff); - operands.emplace_back(Operand::ff(operand_ff)); + operands.emplace_back(Operand::from(operand_ff)); } } pos += operand_size; @@ -475,7 +322,7 @@ std::string Instruction::to_string() const std::ostringstream oss; oss << opcode << " indirect: " << indirect << ", operands: [ "; for (const auto& operand : operands) { - oss << operand.to_string() << " "; + oss << std::to_string(operand) << " "; } oss << "]"; return oss.str(); @@ -501,30 +348,30 @@ std::vector Instruction::serialize() const } break; case OperandType::TAG: case OperandType::UINT8: - output.emplace_back(static_cast(operands.at(operand_pos++))); + output.emplace_back(operands.at(operand_pos++).as()); break; case OperandType::UINT16: { - const auto operand_vec = to_buffer(static_cast(operands.at(operand_pos++))); + const auto operand_vec = to_buffer(operands.at(operand_pos++).as()); output.insert( output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end())); } break; case OperandType::UINT32: { - const auto operand_vec = to_buffer(static_cast(operands.at(operand_pos++))); + const auto operand_vec = to_buffer(operands.at(operand_pos++).as()); output.insert( output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end())); } break; case OperandType::UINT64: { - const auto operand_vec = to_buffer(static_cast(operands.at(operand_pos++))); + const auto operand_vec = to_buffer(operands.at(operand_pos++).as()); output.insert( output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end())); } break; case OperandType::UINT128: { - const auto operand_vec = to_buffer(static_cast(operands.at(operand_pos++))); + const auto operand_vec = to_buffer(operands.at(operand_pos++).as()); output.insert( output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end())); } break; case OperandType::FF: { - const auto operand_vec = to_buffer(static_cast(operands.at(operand_pos++))); + const auto operand_vec = to_buffer(operands.at(operand_pos++).as()); output.insert( output.end(), std::make_move_iterator(operand_vec.begin()), std::make_move_iterator(operand_vec.end())); } break; @@ -561,7 +408,7 @@ bool check_tag(const Instruction& instruction) } try { - uint8_t tag = static_cast(instruction.operands.at(pos)); // Cast to uint8_t might throw + uint8_t tag = instruction.operands.at(pos).as(); // Cast to uint8_t might throw if (tag > static_cast(MemoryTag::MAX)) { vinfo("Instruction tag operand at position: ", diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.hpp index 293fae45a9a1..e7ad9135b612 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.hpp @@ -4,6 +4,7 @@ #include "barretenberg/vm2/common/field.hpp" #include "barretenberg/vm2/common/memory_types.hpp" #include "barretenberg/vm2/common/opcodes.hpp" +#include "barretenberg/vm2/common/tagged_value.hpp" #include #include @@ -24,46 +25,7 @@ const std::unordered_map& get_operand_type_sizes(); } // namespace testonly -class Operand { - private: - // We use unique ptrs to bound the size of the Operand class to the size of a pointer. - // FIXME: Not true! Sadly the variant is sizeof(ptr) + 1 = 8 + 1, but it's aligned, - // so it's 16 bytes which wastes 7 bytes. Still better than 32 + 1 + padding = 40 bytes, - // but worth it? - using FieldInHeap = std::unique_ptr; - using U128InHeap = std::unique_ptr; - using Variant = std::variant; - Variant value; - - public: - Operand(Variant value) - : value(std::move(value)) - {} - Operand(const Operand& other); - Operand(Operand&&) = default; - Operand& operator=(const Operand& other); - bool operator==(const Operand& other) const; - - // Helpers for when we want to pass a value without casting. - static Operand u8(uint8_t value) { return { value }; } - static Operand u16(uint16_t value) { return { value }; } - static Operand u32(uint32_t value) { return { value }; } - static Operand u64(uint64_t value) { return { value }; } - static Operand u128(uint128_t value) { return { std::make_unique(value) }; } - static Operand ff(FF value) { return { std::make_unique(value) }; } - - // We define conversion to supported types. - // The conversion will throw if the type would truncate. - explicit operator bool() const; - explicit operator uint8_t() const; - explicit operator uint16_t() const; - explicit operator uint32_t() const; - explicit operator uint64_t() const; - explicit operator uint128_t() const; - explicit operator FF() const; - - std::string to_string() const; -}; +using Operand = TaggedValue; struct Instruction { WireOpCode opcode = WireOpCode::LAST_OPCODE_SENTINEL; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.test.cpp index ec0d01f61837..9b76ab8b0a80 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.test.cpp @@ -18,7 +18,7 @@ TEST(SerializationTest, Not8RoundTrip) { const Instruction instr = { .opcode = WireOpCode::NOT_8, .indirect = 5, - .operands = { Operand::u8(123), Operand::u8(45) } }; + .operands = { Operand::from(123), Operand::from(45) } }; const auto decoded = deserialize_instruction(instr.serialize(), 0); EXPECT_EQ(instr, decoded); } @@ -26,9 +26,11 @@ TEST(SerializationTest, Not8RoundTrip) // Testing serialization with some u16 variants TEST(SerializationTest, Add16RoundTrip) { - const Instruction instr = { .opcode = WireOpCode::ADD_16, - .indirect = 3, - .operands = { Operand::u16(1000), Operand::u16(1001), Operand::u16(1002) } }; + const Instruction instr = { + .opcode = WireOpCode::ADD_16, + .indirect = 3, + .operands = { Operand::from(1000), Operand::from(1001), Operand::from(1002) } + }; const auto decoded = deserialize_instruction(instr.serialize(), 0); EXPECT_EQ(instr, decoded); } @@ -38,7 +40,7 @@ TEST(SerializationTest, Jumpi32RoundTrip) { const Instruction instr = { .opcode = WireOpCode::JUMPI_32, .indirect = 7, - .operands = { Operand::u16(12345), Operand::u32(678901234) } }; + .operands = { Operand::from(12345), Operand::from(678901234) } }; const auto decoded = deserialize_instruction(instr.serialize(), 0); EXPECT_EQ(instr, decoded); } @@ -48,11 +50,11 @@ TEST(SerializationTest, Set64RoundTrip) { const uint64_t value_64 = 0xABCDEF0123456789LLU; - const Instruction instr = { - .opcode = WireOpCode::SET_64, - .indirect = 2, - .operands = { Operand::u16(1002), Operand::u8(static_cast(MemoryTag::U64)), Operand::u64(value_64) } - }; + const Instruction instr = { .opcode = WireOpCode::SET_64, + .indirect = 2, + .operands = { Operand::from(1002), + Operand::from(static_cast(MemoryTag::U64)), + Operand::from(value_64) } }; const auto decoded = deserialize_instruction(instr.serialize(), 0); EXPECT_EQ(instr, decoded); } @@ -62,11 +64,11 @@ TEST(SerializationTest, Set128RoundTrip) { const uint128_t value_128 = (uint128_t{ 0x123456789ABCDEF0LLU } << 64) + uint128_t{ 0xABCDEF0123456789LLU }; - const Instruction instr = { - .opcode = WireOpCode::SET_128, - .indirect = 2, - .operands = { Operand::u16(1002), Operand::u8(static_cast(MemoryTag::U128)), Operand::u128(value_128) } - }; + const Instruction instr = { .opcode = WireOpCode::SET_128, + .indirect = 2, + .operands = { Operand::from(1002), + Operand::from(static_cast(MemoryTag::U128)), + Operand::from(value_128) } }; const auto decoded = deserialize_instruction(instr.serialize(), 0); EXPECT_EQ(instr, decoded); } @@ -76,11 +78,11 @@ TEST(SerializationTest, SetFFRoundTrip) { const FF large_ff = FF::modulus - 981723; - const Instruction instr = { - .opcode = WireOpCode::SET_FF, - .indirect = 2, - .operands = { Operand::u16(1002), Operand::u8(static_cast(MemoryTag::FF)), Operand::ff(large_ff) } - }; + const Instruction instr = { .opcode = WireOpCode::SET_FF, + .indirect = 2, + .operands = { Operand::from(1002), + Operand::from(static_cast(MemoryTag::FF)), + Operand::from(large_ff) } }; const auto decoded = deserialize_instruction(instr.serialize(), 0); EXPECT_EQ(instr, decoded); } @@ -115,11 +117,11 @@ TEST(SerializationTest, OpcodeOutOfRange) TEST(SerializationTest, InstructionOutOfRange) { // Create a valid SET_16 instruction - Instruction instr = { - .opcode = WireOpCode::SET_16, - .indirect = 2, - .operands = { Operand::u16(1002), Operand::u8(static_cast(MemoryTag::U16)), Operand::u16(12345) } - }; + Instruction instr = { .opcode = WireOpCode::SET_16, + .indirect = 2, + .operands = { Operand::from(1002), + Operand::from(static_cast(MemoryTag::U16)), + Operand::from(12345) } }; auto bytecode = instr.serialize(); @@ -136,40 +138,40 @@ TEST(SerializationTest, InstructionOutOfRange) // Testing check_tag with a valid instruction for wire opcode SET_128 TEST(SerializationTest, CheckTagValid) { - Instruction instr = { - .opcode = WireOpCode::SET_128, - .indirect = 2, - .operands = { Operand::u16(1002), Operand::u8(static_cast(MemoryTag::U128)), Operand::u128(12345) } - }; + Instruction instr = { .opcode = WireOpCode::SET_128, + .indirect = 2, + .operands = { Operand::from(1002), + Operand::from(static_cast(MemoryTag::U128)), + Operand::from(12345) } }; EXPECT_TRUE(check_tag(instr)); } // Testing check_tag with an invalid tag for wire opcode SET_128 TEST(SerializationTest, CheckTagInvalid) { - Instruction instr = { - .opcode = WireOpCode::SET_128, - .indirect = 2, - .operands = { Operand::u16(1002), Operand::u8(static_cast(MemoryTag::MAX) + 1), Operand::u128(12345) } - }; + Instruction instr = { .opcode = WireOpCode::SET_128, + .indirect = 2, + .operands = { Operand::from(1002), + Operand::from(static_cast(MemoryTag::MAX) + 1), + Operand::from(12345) } }; EXPECT_FALSE(check_tag(instr)); } // Testing check_tag with an invalid instruction for wire opcode SET_128, not enough operands TEST(SerializationTest, CheckTagInvalidNotEnoughOperands) { - Instruction instr = { .opcode = WireOpCode::SET_128, .indirect = 2, .operands = { Operand::u16(1002) } }; + Instruction instr = { .opcode = WireOpCode::SET_128, .indirect = 2, .operands = { Operand::from(1002) } }; EXPECT_FALSE(check_tag(instr)); } // Testing check_tag with an invalid instruction for wire opcode SET_128, tag is not a byte TEST(SerializationTest, CheckTagInvalidTagNotByte) { - Instruction instr = { - .opcode = WireOpCode::SET_128, - .indirect = 2, - .operands = { Operand::u16(1002), Operand::u16(static_cast(MemoryTag::U128)), Operand::u128(12345) } - }; + Instruction instr = { .opcode = WireOpCode::SET_128, + .indirect = 2, + .operands = { Operand::from(1002), + Operand::from(static_cast(MemoryTag::U128)), + Operand::from(12345) } }; EXPECT_FALSE(check_tag(instr)); } diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp index b6e08a9a22bf..c63ed7cb8884 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp @@ -8,49 +8,35 @@ namespace bb::avm2::simulation { -bool MemoryInterface::is_valid_address(const MemoryValue& address) +bool MemoryInterface::is_valid_address(const FF& address) { - (void)address; - return true; - // This is failing and I don't know why. - // return address < static_cast(0x100000000); // 2^32 + // address fits in 32 bits + return FF(static_cast(address)) == address; } -bool MemoryInterface::is_valid_address(ValueRefAndTag address) +bool MemoryInterface::is_valid_address(const MemoryValue& address) { - return is_valid_address(address.value) && address.tag == MemoryAddressTag; + return is_valid_address(address.as_ff()) && address.get_tag() == MemoryAddressTag; } -void Memory::set(MemoryAddress index, MemoryValue value, MemoryTag tag) +void Memory::set(MemoryAddress index, MemoryValue value) { // TODO: validate tag-value makes sense. - memory[index] = { value, tag }; - debug("Memory write: ", index, " <- ", value, " (tag: ", static_cast(tag), ")"); - events.emit({ .mode = MemoryMode::WRITE, .addr = index, .value = value, .tag = tag, .space_id = space_id }); + memory[index] = value; + debug("Memory write: ", index, " <- ", value.to_string()); + events.emit({ .mode = MemoryMode::WRITE, .addr = index, .value = value, .space_id = space_id }); } -ValueRefAndTag Memory::get(MemoryAddress index) const +const MemoryValue& Memory::get(MemoryAddress index) const { - static const ValueAndTag default_value = { 0, MemoryTag::FF }; + static const auto default_value = MemoryValue::from(0); auto it = memory.find(index); const auto& vt = it != memory.end() ? it->second : default_value; - events.emit({ .mode = MemoryMode::READ, .addr = index, .value = vt.value, .tag = vt.tag, .space_id = space_id }); + events.emit({ .mode = MemoryMode::READ, .addr = index, .value = vt, .space_id = space_id }); - debug("Memory read: ", index, " -> ", vt.value, " (tag: ", static_cast(vt.tag), ")"); - return { vt.value, vt.tag }; -} - -std::pair, std::vector> Memory::get_slice(MemoryAddress start, size_t size) const -{ - std::vector values(size); - std::vector tags(size); - for (size_t i = 0; i < size; ++i) { - auto vt = get(static_cast(start + i)); - values[i] = vt.value; - tags[i] = vt.tag; - } - return { std::move(values), std::move(tags) }; + debug("Memory read: ", index, " -> ", vt.to_string()); + return vt; } } // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.hpp index 096f54402d15..4ee72dc5267d 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.hpp @@ -9,27 +9,19 @@ namespace bb::avm2::simulation { -struct ValueRefAndTag { - const MemoryValue& value; - MemoryTag tag; - - bool operator==(const ValueRefAndTag& other) const { return value == other.value && tag == other.tag; } -}; - -using SliceWithTags = std::pair, std::vector>; - class MemoryInterface { public: virtual ~MemoryInterface() = default; - virtual void set(MemoryAddress index, MemoryValue value, MemoryTag tag) = 0; - virtual ValueRefAndTag get(MemoryAddress index) const = 0; - virtual SliceWithTags get_slice(MemoryAddress start, size_t size) const = 0; + // Returned reference is only valid until the next call to set. + virtual const MemoryValue& get(MemoryAddress index) const = 0; + // Sets value. Invalidates all references to previous values. + virtual void set(MemoryAddress index, MemoryValue value) = 0; virtual uint32_t get_space_id() const = 0; static bool is_valid_address(const MemoryValue& address); - static bool is_valid_address(ValueRefAndTag address); + static bool is_valid_address(const FF& address); }; class Memory : public MemoryInterface { @@ -39,20 +31,14 @@ class Memory : public MemoryInterface { , events(event_emitter) {} - void set(MemoryAddress index, MemoryValue value, MemoryTag tag) override; - ValueRefAndTag get(MemoryAddress index) const override; - SliceWithTags get_slice(MemoryAddress start, size_t size) const override; + const MemoryValue& get(MemoryAddress index) const override; + void set(MemoryAddress index, MemoryValue value) override; uint32_t get_space_id() const override { return space_id; } private: - struct ValueAndTag { - MemoryValue value; - MemoryTag tag; - }; - uint32_t space_id; - unordered_flat_map memory; + unordered_flat_map memory; EventEmitterInterface& events; }; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.cpp index 959a2ac183da..169d6317bcfb 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.cpp @@ -20,16 +20,16 @@ void Sha256::compression(ContextInterface& context, // Load 8 elements representing the state from memory. for (uint32_t i = 0; i < 8; ++i) { - auto memory_value = memory.get(state_addr + i).value; + auto memory_value = memory.get(state_addr + i); // TODO: Check that the tag is U32 and do error handling. - state[i] = static_cast(memory_value); + state[i] = memory_value.as(); } // Load 16 elements representing the input from memory. for (uint32_t i = 0; i < 16; ++i) { - auto memory_value = memory.get(input_addr + i).value; + auto memory_value = memory.get(input_addr + i); // TODO: Check that the tag is U32 and do error handling. - input[i] = static_cast(memory_value); + input[i] = memory_value.as(); } // Perform sha256 compression. @@ -37,7 +37,7 @@ void Sha256::compression(ContextInterface& context, // Write the output back to memory. for (uint32_t i = 0; i < 8; ++i) { - memory.set(output_addr + i, output[i], MemoryTag::U32); + memory.set(output_addr + i, MemoryValue::from(output[i])); } events.emit({ .state_addr = state_addr, diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.test.cpp index 0d53d51ae9b4..b5c64e65a04c 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/sha256.test.cpp @@ -30,13 +30,13 @@ TEST(Sha256CompressionSimulationTest, Sha256Compression) std::array state = { 0, 1, 2, 3, 4, 5, 6, 7 }; MemoryAddress state_addr = 0; for (uint32_t i = 0; i < 8; ++i) { - mem.set(state_addr + i, state[i], MemoryTag::U32); + mem.set(state_addr + i, MemoryValue::from(state[i])); } std::array input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; MemoryAddress input_addr = 8; for (uint32_t i = 0; i < 16; ++i) { - mem.set(input_addr + i, input[i], MemoryTag::U32); + mem.set(input_addr + i, MemoryValue::from(input[i])); } MemoryAddress dst_addr = 25; @@ -47,7 +47,7 @@ TEST(Sha256CompressionSimulationTest, Sha256Compression) std::array result_from_memory; for (uint32_t i = 0; i < 8; ++i) { auto c = mem.get(dst_addr + i); - result_from_memory[i] = static_cast(c.value); + result_from_memory[i] = c.as(); } EXPECT_EQ(result_from_memory, result); } diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_alu.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_alu.hpp index 4fc121caca3e..a2545e502d3c 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_alu.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_alu.hpp @@ -14,7 +14,7 @@ class MockAlu : public AluInterface { MockAlu(); ~MockAlu() override; - MOCK_METHOD(FF, add, (const ValueRefAndTag& a, const ValueRefAndTag& b), (override)); + MOCK_METHOD(MemoryValue, add, (const MemoryValue& a, const MemoryValue& b), (override)); }; } // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_bitwise.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_bitwise.hpp index 17c64334e1ee..9b195e1d870b 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_bitwise.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_bitwise.hpp @@ -12,9 +12,9 @@ class MockBitwise : public BitwiseInterface { MockBitwise(); ~MockBitwise() override; - MOCK_METHOD(uint128_t, and_op, (MemoryTag tag, const uint128_t& a, const uint128_t& b), (override)); - MOCK_METHOD(uint128_t, or_op, (MemoryTag tag, const uint128_t& a, const uint128_t& b), (override)); - MOCK_METHOD(uint128_t, xor_op, (MemoryTag tag, const uint128_t& a, const uint128_t& b), (override)); + MOCK_METHOD(MemoryValue, and_op, (const MemoryValue& a, const MemoryValue& b), (override)); + MOCK_METHOD(MemoryValue, or_op, (const MemoryValue& a, const MemoryValue& b), (override)); + MOCK_METHOD(MemoryValue, xor_op, (const MemoryValue& a, const MemoryValue& b), (override)); }; -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_memory.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_memory.hpp index b1632e0fda32..9cde3fbc7463 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_memory.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_memory.hpp @@ -3,7 +3,6 @@ #include #include "barretenberg/vm2/common/memory_types.hpp" -#include "barretenberg/vm2/simulation/events/memory_event.hpp" #include "barretenberg/vm2/simulation/memory.hpp" namespace bb::avm2::simulation { @@ -14,10 +13,9 @@ class MockMemory : public MemoryInterface { MockMemory(); ~MockMemory() override; - MOCK_METHOD(void, set, (MemoryAddress index, MemoryValue value, MemoryTag tag), (override)); - MOCK_METHOD(ValueRefAndTag, get, (MemoryAddress index), (const, override)); - MOCK_METHOD(SliceWithTags, get_slice, (MemoryAddress start, size_t size), (const, override)); + MOCK_METHOD(void, set, (MemoryAddress index, MemoryValue value), (override)); + MOCK_METHOD(const MemoryValue&, get, (MemoryAddress index), (const, override)); MOCK_METHOD(uint32_t, get_space_id, (), (const, override)); }; -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/testing/fixtures.cpp b/barretenberg/cpp/src/barretenberg/vm2/testing/fixtures.cpp index 75e283659e0c..1795c3a6d322 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/testing/fixtures.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/testing/fixtures.cpp @@ -48,37 +48,37 @@ Operand random_operand(OperandType operand_type) case OperandType::UINT8: { uint8_t operand_u8 = 0; serialize::read(pos_ptr, operand_u8); - return Operand::u8(operand_u8); + return Operand::from(operand_u8); } case OperandType::TAG: { uint8_t operand_u8 = 0; serialize::read(pos_ptr, operand_u8); - return Operand::u8(operand_u8 % static_cast(MemoryTag::MAX) + - 1); // Insecure bias but it is fine for testing purposes. + return Operand::from(operand_u8 % static_cast(MemoryTag::MAX) + + 1); // Insecure bias but it is fine for testing purposes. } case OperandType::INDIRECT16: // Irrelevant bits might be toggled but they are ignored during address resolution. case OperandType::UINT16: { uint16_t operand_u16 = 0; serialize::read(pos_ptr, operand_u16); - return Operand::u16(operand_u16); + return Operand::from(operand_u16); } case OperandType::UINT32: { uint32_t operand_u32 = 0; serialize::read(pos_ptr, operand_u32); - return Operand::u32(operand_u32); + return Operand::from(operand_u32); } case OperandType::UINT64: { uint64_t operand_u64 = 0; serialize::read(pos_ptr, operand_u64); - return Operand::u64(operand_u64); + return Operand::from(operand_u64); } case OperandType::UINT128: { uint128_t operand_u128 = 0; serialize::read(pos_ptr, operand_u128); - return Operand::u128(operand_u128); + return Operand::from(operand_u128); } case OperandType::FF: - return Operand::ff(FF::random_element()); + return Operand::from(FF::random_element()); } // Need this for gcc compilation even though we fully handle the switch cases. @@ -96,8 +96,10 @@ Instruction random_instruction(WireOpCode w_opcode) for (const auto& operand_type : format) { switch (operand_type) { case OperandType::INDIRECT8: + indirect = random_operand(operand_type).as(); + break; case OperandType::INDIRECT16: - indirect = static_cast(random_operand(operand_type)); + indirect = random_operand(operand_type).as(); break; default: operands.emplace_back(random_operand(operand_type)); @@ -138,7 +140,7 @@ std::pair get_minimal_trace_with_pi() AvmTraceGenHelper trace_gen_helper; auto trace = trace_gen_helper.generate_trace({ - .alu = { { .operation = simulation::AluOperation::ADD, .a = 1, .b = 2, .c = 3, .tag = MemoryTag::U16 }, }, + .alu = { { .operation = simulation::AluOperation::ADD, .a = MemoryValue::from(1), .b = MemoryValue::from(2), .c = MemoryValue::from(3) }, }, }); return std::make_pair(std::move(trace), { .reverted = false }); diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/alu_trace.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/alu_trace.test.cpp index 869ecc1b51f3..7d02ff3a2b7f 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/alu_trace.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/alu_trace.test.cpp @@ -26,7 +26,10 @@ TEST(AluTraceGenTest, TraceGeneration) builder.process( { - { .operation = AluOperation::ADD, .a = 1, .b = 2, .c = 3, .tag = MemoryTag::U32 /* unused for now */ }, + { .operation = AluOperation::ADD, + .a = MemoryValue::from(1), + .b = MemoryValue::from(2), + .c = MemoryValue::from(3) }, }, trace); diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/bitwise_trace.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/bitwise_trace.cpp index 8977e89b7faa..e9ee7fbe35ca 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/bitwise_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/bitwise_trace.cpp @@ -21,13 +21,14 @@ void BitwiseTraceBuilder::process(const simulation::EventEmitterInterface(event.a); - uint128_t input_b = static_cast(event.b); - uint128_t output_c = static_cast(event.res); + uint128_t input_a = static_cast(event.a.as_ff()); + uint128_t input_b = static_cast(event.b.as_ff()); + uint128_t output_c = static_cast(event.res.as_ff()); // Note that for tag U1, we take only one bit. This is correctly // captured below since input_a/b and output_c are each a single bit @@ -43,10 +44,10 @@ void BitwiseTraceBuilder::process(const simulation::EventEmitterInterface(event.tag) }, + { C::bitwise_tag, static_cast(tag) }, { C::bitwise_ctr, ctr }, - { C::bitwise_ctr_inv, ctr != 0 ? MemoryValue(ctr).invert() : 1 }, - { C::bitwise_ctr_min_one_inv, ctr != 1 ? MemoryValue(ctr - 1).invert() : 1 }, + { C::bitwise_ctr_inv, ctr != 0 ? FF(ctr).invert() : 1 }, + { C::bitwise_ctr_min_one_inv, ctr != 1 ? FF(ctr - 1).invert() : 1 }, { C::bitwise_last, static_cast(ctr == 1) }, { C::bitwise_sel, static_cast(ctr != 0) }, { C::bitwise_start, static_cast(ctr == start_ctr) } } }); @@ -59,4 +60,4 @@ void BitwiseTraceBuilder::process(const simulation::EventEmitterInterface +#include "barretenberg/vm2/common/memory_types.hpp" #include "barretenberg/vm2/constraining/flavor_settings.hpp" #include "barretenberg/vm2/constraining/full_row.hpp" #include "barretenberg/vm2/testing/macros.hpp" @@ -27,10 +28,9 @@ TEST(BitwiseTraceGenTest, U1And) { { .operation = BitwiseOperation::AND, - .tag = MemoryTag::U1, - .a = 0, - .b = 1, - .res = 0, + .a = MemoryValue::from(uint1_t(0)), + .b = MemoryValue::from(uint1_t(1)), + .res = MemoryValue::from(uint1_t(0)), }, }, trace); @@ -77,10 +77,9 @@ TEST(BitwiseTraceGenTest, U32And) { { .operation = BitwiseOperation::AND, - .tag = MemoryTag::U32, - .a = 0x52488425, - .b = 0xC684486C, - .res = 0x42000024, + .a = MemoryValue::from(0x52488425), + .b = MemoryValue::from(0xC684486C), + .res = MemoryValue::from(0x42000024), }, }, trace); diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/bytecode_trace.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/bytecode_trace.test.cpp index 1c3341a66599..bd713966e119 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/bytecode_trace.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/bytecode_trace.test.cpp @@ -623,8 +623,8 @@ TEST(BytecodeTraceGenTest, InstrFetchingErrorTagOutOfRange) constexpr uint32_t cast_size = 7; constexpr uint32_t set_64_size = 13; - instr_cast.operands.at(2) = Operand::u8(0x09); // tag operand mutation to 0x09 which is out of range - instr_set.operands.at(1) = Operand::u8(0x0A); // tag operand mutation to 0x0A which is out of range + instr_cast.operands.at(2) = Operand::from(0x09); // tag operand mutation to 0x09 which is out of range + instr_set.operands.at(1) = Operand::from(0x0A); // tag operand mutation to 0x0A which is out of range auto bytecode = instr_cast.serialize(); ASSERT_EQ(bytecode.size(), cast_size); diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp index eae9098085e9..f256f4cebe4c 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp @@ -39,10 +39,10 @@ void ExecutionTraceBuilder::process( auto operands = ex_event.wire_instruction.operands; assert(operands.size() <= operand_columns); - operands.resize(operand_columns, simulation::Operand::ff(0)); + operands.resize(operand_columns, simulation::Operand::from(0)); auto resolved_operands = ex_event.resolved_operands; assert(resolved_operands.size() <= operand_columns); - resolved_operands.resize(operand_columns, simulation::Operand::ff(0)); + resolved_operands.resize(operand_columns, simulation::Operand::from(0)); trace.set(row, { { @@ -62,13 +62,13 @@ void ExecutionTraceBuilder::process( auto operands_after_relative = addr_event.after_relative; assert(operands_after_relative.size() <= operand_columns); - operands_after_relative.resize(operand_columns, simulation::Operand::ff(0)); + operands_after_relative.resize(operand_columns, simulation::Operand::from(0)); trace.set( row, { { - { C::execution_base_address_val, addr_event.base_address_val }, - { C::execution_base_address_tag, static_cast(addr_event.base_address_tag) }, + { C::execution_base_address_val, addr_event.base_address.as_ff() }, + { C::execution_base_address_tag, static_cast(addr_event.base_address.get_tag()) }, { C::execution_sel_addressing_error, addr_event.error.has_value() ? 1 : 0 }, { C::execution_addressing_error_idx, addr_event.error.has_value() ? addr_event.error->operand_idx : 0 }, { C::execution_addressing_error_kind,