Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(avm): range checks in vm2 #11433

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft

feat(avm): range checks in vm2 #11433

wants to merge 1 commit into from

Conversation

dbanks12
Copy link
Collaborator

@dbanks12 dbanks12 commented Jan 23, 2025

Also sel_rng_chk_8/16 and power_of_2 precomputed tables

Copy link
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied this file over but renamed some things and removed the selectors specifying which subtrace a range check originated from

@dbanks12 dbanks12 force-pushed the db/range-checks-vm2 branch from 9e6df37 to 834e682 Compare January 23, 2025 02:02
Comment on lines 56 to 57
sel {op1, op2, op3, op4} is sel {op1, op2, op3, op4}; No newline at end of file
sel {op1, op2, op3, op4} is sel {op1, op2, op3, op4};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what's different about our editors, but this happens all the time

Comment on lines +10 to +26
TEST(AvmSimulationRangeCheckTest, AssertRange)
{
EventEmitter<RangeCheckEvent> emitter;
RangeCheck range_check(emitter);

uint128_t value = 333;
uint8_t num_bits = 100;
range_check.assert_range(value, num_bits);

RangeCheckEvent expect_event = {
.value = value,
.num_bits = num_bits,
};

std::vector<RangeCheckEvent> events = { expect_event };
EXPECT_EQ(emitter.dump_events(), events);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test feels like it's doing... nothing. But I guess the range-check simulation module does just about nothing since it is really a tracegen thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah indeed. It's such a simple gadget (simulation wise) that it doesnt even call anything else.

Comment on lines +38 to +70
uint32_t row = 0;
for (const auto& event : events) {
uint256_t value_u256 = uint256_t::from_uint128(event.value);
uint8_t num_bits = 0;

std::array<uint16_t, 7> fixed_slice_registers; // u16_r0...6
// Enum representing which of is_lte_u* holds true.
// Only one can be chosen/true for a given range check.
// The highest X is chosen such that "value is lte X" holds true.
IsLte is_lte = IsLte::IS_LTE_U16;
uint16_t dynamic_slice_register = 0; // same as u16_r7
uint16_t dynamic_bits = 0;

// Split the value into 16-bit chunks
for (size_t i = 0; i < 8; i++) {
// The most significant 16-bits have to be placed in the dynamic slice register
if (event.num_bits <= 16) {
dynamic_slice_register = static_cast<uint16_t>(value_u256);
// Set is_lte based on on how many 16 bit chunks have already been processed
// For example, if this is the 0th, then IS_LTE_U16 is set
// If this is the first, then IS_LTE_U32 is set, ...
// TODO(dbanks12): is this more or less intuitive then the bit magic from before?
is_lte = IsLte(i);
dynamic_bits = event.num_bits;
break;
}
// We have more chunks of 16-bits to operate on, so set the ith fixed register
fixed_slice_registers[i] = static_cast<uint16_t>(value_u256);
num_bits -= 16;
value_u256 >>= 16;
}

auto dynamic_diff = static_cast<uint16_t>((1 << dynamic_bits) - dynamic_slice_register - 1);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my TODO here

Comment on lines +12 to +18
struct RangeCheckEvent {
uint128_t value;
uint8_t num_bits;

// TODO(dbanks12): do we want this operator just for tests?
bool operator==(const RangeCheckEvent& other) const { return value == other.value && num_bits == other.num_bits; }
};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See TODO. The simple test for this event uses this operator== to do checks. But maybe it's unnecessary?

Copy link
Contributor

@fcarreiro fcarreiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great from a VM2 perspective!

@@ -0,0 +1,189 @@
namespace range_check(256);
// Range check selector
pol commit sel_rng_chk;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are now just using sel for the subtrace selector so that it looks like range_check.sel when the namespace is used.

sel_r5_16_bit_rng_lookup - (is_lte_u112 + is_lte_u128) = 0;
sel_r6_16_bit_rng_lookup - is_lte_u128 = 0;

#[LOOKUP_RNG_CHK_IS_R0_16_BIT]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These interactions were not supposed to happen :) but I was about to propose that we can start doing lookups into precomputed (and only precomputed). So let's keep them and make them work :)

} // namespace bb::avm2::constraining
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Re: line +57]

Revert pls

See this comment inline on Graphite.


TEST(AvmConstrainingTest, RangeCheckNegativeCheckRecomposition)
{

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please remove these extra new lines

{ C::range_check_sel_r1_16_bit_rng_lookup, 0 }, // BAD! SHOULD BE 1
} });

EXPECT_THROW_WITH_MESSAGE(check_relation<range_check>(trace.as_rows()), "Relation range_check");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative tests should probably be more granular (non full); if sth else starts failing, this statement will pass. See other negative tests. Probably you want to check a given subrelation(s).

{
using C = Column;

uint32_t row = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you use any shifts (') in pil? If yes you have to start from 1, otherwise it's ok.

{ C::range_check_dyn_rng_chk_bits, dynamic_bits },
{ C::range_check_dyn_rng_chk_pow_2, 1 << dynamic_bits },
{ C::range_check_dyn_diff, dynamic_diff },

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a comment if you want to separate them, e.g.
// Lookups.

Comment on lines +55 to +66
[&]() {
PrecomputedTraceBuilder precomputed_builder;
AVM_TRACK_TIME("tracegen/precomputed/rng_chk_8", precomputed_builder.process_sel_rng_chk_8(trace));
},
[&]() {
PrecomputedTraceBuilder precomputed_builder;
AVM_TRACK_TIME("tracegen/precomputed/rng_chk_16", precomputed_builder.process_sel_rng_chk_16(trace));
},
[&]() {
PrecomputedTraceBuilder precomputed_builder;
AVM_TRACK_TIME("tracegen/precomputed/power_of_2", precomputed_builder.process_power_of_2(trace));
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are so cheap that I'd put the 3 of them in the same job.

@@ -122,6 +146,46 @@ TraceContainer AvmTraceGenHelper::generate_trace(EventsContainer&& events)
PermutationBuilder<perm_dummy_dynamic_permutation_settings> perm_execution_execution;
perm_execution_execution.process(trace);
},
[&]() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perfect 👍


namespace bb::avm2::tracegen {

template <typename LookupSettings> class LookupIntoRngChk : public BaseLookupTraceBuilder<LookupSettings> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah interesting! You were able to use the same class for lookups into u8, u16 etc, because they all use CLK and only change based on the selector. Maybe add a comment?

// Range check selector
pol commit sel_rng_chk;
sel_rng_chk * (1 - sel_rng_chk) = 0;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add

// No relations will be checked if this identity is satisfied.
#[skippable_if]
sel = 0;

?

we can discuss skippable in a huddle

using C = Column;
using range_check = bb::avm2::range_check<FF>;

TEST(AvmConstrainingTest, RangeCheckPositiveIsLteMutuallyExclusive)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test where you

  • create a single row with clk = 1. this is to force a row to be in the trace. I actually want a completely empty row but it doesnt matter.
  • check the whole relation and expect no failures
    ?

This is to test that all relations pass in rows fulls of 0. We probably need to add this test to every gadget.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants