-
Notifications
You must be signed in to change notification settings - Fork 51
skip bitwise_and when noop to preserve relations #997
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
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds a short-circuit in FiniteDomain::bitwise_and for RHS masks (2^n-1) that preserves relational constraints when LHS signed and unsigned intervals are identical and within the mask; otherwise performs the AND using 64-bit width. Adds test cases for AND with constants and loop truncation behaviors. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Signed-off-by: Michael Agun <danielagun@microsoft.com>
43a5530 to
d69487e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/crab/finite_domain.cpp`:
- Around line 951-964: The early-exit in FiniteDomain::bitwise_and is sound but
the comment should explicitly state the mathematical identity used for
auditability: when mask = 2^n − 1, every v in Interval[0, mask] satisfies v &
mask = v, and therefore if eval_interval(lhss) and eval_interval(lhsu) are both
⊆ [0, mask] and equal the AND is a no-op; update the comment above the guard
(near mask_interval, sinterval, uinterval) to spell out this assumption (mask
definition, v range, and the equality v & mask = v) and reference the reason why
sinterval == uinterval prevents skipping, keeping the code
(apply_unsigned(BitwiseBinOp::AND, ...)) unchanged.
In `@test-data/bitop.yaml`:
- Around line 79-117: Add a new YAML test-case that covers the edge where
r1.svalue != r1.uvalue but both signed and unsigned intervals fit inside the
mask so the AND optimization should proceed; e.g. add a test-case with pre:
["r1.type=number","r1.svalue=[0,100]","r1.uvalue=[0,200]","r2.type=number","r2.svalue=r1.svalue","r2.uvalue=r1.svalue"],
code: "r1 &= 255", and a post that asserts the relation is preserved (signed
bounds established from unsigned) such as r1.svalue=[0,100], r1.uvalue=r1.svalue
and r2.svalue/r2.uvalue tied to r1.svalue; use the existing test-case naming
style to place it alongside the other AND-with-mask cases.
In `@test-data/loop.yaml`:
- Around line 423-527: Add a negative test that mirrors the 8-bit AND case but
sets r1.svalue to a range that can exceed the mask (e.g., r1.svalue=[1,300]) so
the verifier must report a loop-counter-too-large/widened bound; specifically
copy the "while loop with 8-bit AND truncation, variable bound" test-case
(referencing symbols r0, r1, r2 and the instruction r2 &= 255) and change the
precondition to "r1.svalue=[1,300]" and update the expected post/messages to
assert the verifier emits the appropriate error or widened bound (messages not
empty / contains loop counter too large).
The interval equality check (sinterval == uinterval) was insufficient: overflow_bounds can destroy the svalue==uvalue relational constraint while leaving both intervals numerically identical. Add dom.entail(eq(lhss, lhsu)) to verify the SplitDBM difference constraint exists before skipping, allowing apply_unsigned to re-establish it when needed. Fixes movsx8/16 32-bit range tests. Signed-off-by: Michael Agun <danielagun@microsoft.com>
731721c to
cc67321
Compare
Pull Request Test Coverage Report for Build 21921970224Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
Preserve relational constraints in bitwise_and when AND is a no-op
Problem
When clang compiles bounded loops using narrow integer types (uint8_t, uint16_t, uint32_t), it generates SHL 32 +
RSH 32 sequences (or AND with a mask like 0xFF) for truncation. The verifier's bitwise_and implementation calls
apply_unsigned → apply(AND) → set() → havoc(), which destroys all SplitDBM difference constraints on the variable —
including relational constraints like counter ≤ r.svalue established by calculate_constant_limits() during loop
widening.
Without those relational constraints, the loop counter widens to [0, +∞] and verification fails with "Loop counter
is too large", even when the loop is provably bounded.
Fix
Add a no-op check at the top of FiniteDomain::bitwise_and(Variable, Variable, const Number&) in finite_domain.cpp.
When all of the following hold, the AND is a mathematical identity and the function returns early, preserving
relational constraints:
The check is inside bitwise_and itself rather than at call sites, so all callers (immediate AND, 32-bit
zero-extension, etc.) benefit automatically.
Why sinterval == uinterval is required
Without this guard, cases like sext.yaml "small zext relational nop" would regress: when uvalue=[0,5] but svalue is
unconstrained, the AND must execute to establish svalue from uvalue. The equality check ensures we only skip when
both are already in agreement.
Tests added
bitop.yaml — unit tests for the no-op optimization:
to [0, 255]
loop.yaml — end-to-end bounded loop verification (prior commit):
Summary by CodeRabbit
Tests
Refactor