3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-23 15:32:32 +00:00

Fix invalid model from int_to_bv missing modular reduction in bv2int_translator (#10185)

With `smt.bv.solver=2`, `int_to_bv(x)` was translated to the raw integer
`x` without normalizing it to `[0, 2^N)`. Bitwise operations like
`bvxor(A, B)` are translated as `A + B - 2·band(A, B)`, which is only
valid when both operands are in `[0, 2^N)`. When `x` was negative or ≥
2^N, the LP solver could assign a band value inconsistent with bit
semantics, producing a model that fails validation.

## Changes

- **`OP_INT2BV` translation** (`translate_bv`): Apply `umod(e, 0)`
instead of passing the raw integer argument through. This normalizes the
value to `[0, 2^N)` before it participates in any bitwise arithmetic.

  ```cpp
  // Before
  case OP_INT2BV:
      r = arg(0);  // raw integer, may be negative or ≥ 2^N

  // After
  case OP_INT2BV:
      r = umod(e, 0);  // normalized to [0, 2^N)
  ```

- **`amod` shortcut**: Add a fast-path for `mod(t, N)` when the divisor
already equals `N` — the result is already in `[0, N)`, so wrapping it
again with another `mod(_, N)` is unnecessary and avoids expression
bloat.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot 2026-07-22 10:34:23 -07:00 committed by GitHub
parent 9ac438b199
commit 847ee63b2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -428,7 +428,10 @@ void bv2int_translator::translate_bv(app* e) {
case OP_INT2BV:
m_int2bv.push_back(e);
ctx.push(push_back_vector(m_int2bv));
r = arg(0);
// Normalize the integer argument to [0, 2^N) so that bitwise operations
// on int_to_bv produce correct results when the argument is negative or
// otherwise outside the valid range.
r = umod(e, 0);
break;
case OP_UBV2INT:
m_bv2int.push_back(e);
@ -697,6 +700,9 @@ expr* bv2int_translator::amod(expr* bv_expr, expr* x, rational const& N) {
r = x;
else if (a.is_mod(x, t, e) && a.is_numeral(t, v) && 0 <= v && v < N)
r = x;
else if (a.is_mod(x, t, e) && a.is_numeral(e, v) && v == N)
// mod(t, N) is already in [0, N); no need to wrap it again.
r = x;
else if (a.is_numeral(x, v))
r = a.mk_int(mod(v, N));
else if (is_bounded(x, N))