Skip to content

Push empty-type equality short-circuit into convert_expr_to_smt#9015

Draft
tautschnig wants to merge 3 commits into
diffblue:developfrom
tautschnig:fix-8077-smt2-incr-empty-followup
Draft

Push empty-type equality short-circuit into convert_expr_to_smt#9015
tautschnig wants to merge 3 commits into
diffblue:developfrom
tautschnig:fix-8077-smt2-incr-empty-followup

Conversation

@tautschnig
Copy link
Copy Markdown
Collaborator

Builds upon #8907.

Previously, equality of two zero-width-typed (typically void) operands was handled at the top level of smt2_incremental_decision_proceduret::set_to: the equal_exprt was matched textually before lowering reached convert_expr_to_smt, and a dedicated branch emitted either nothing (for value == true) or (assert false) (for value == false). This worked for the common case but failed for nested shapes such as

set_to(and_exprt{equal_exprt{x_void, y_void}, other}, true)

because the recursive visitor in convert_expr_to_smt would still descend into the void operands of the inner equal_exprt and trip UNIMPLEMENTED_FEATURE in convert_type_to_smt_sort(empty_typet).

Move the short-circuit one layer down, into convert_expr_to_smt:

  • Add a const namespacet & parameter to the public convert_expr_to_smt and propagate it to the dispatcher and to the equal_exprt overload.
  • In the entry-point's filtered_visit_post filter, return false for an equal_exprt whose operands have zero-width type, so that the visitor does not descend into the void operands and does not attempt convert_type_to_smt_sort on them.
  • In the equal_exprt overload of convert_expr_to_smt, return smt_bool_literal_termt{true} directly when the operand type is zero-width, mirroring smt2_conv.cpp:1604 (convert_expr(true_exprt())).
  • Drop the now-redundant top-level short-circuit from set_to: convert_expr_to_smt produces the boolean literal true, which set_to asserts as-is for value == true and negates for value == false. This means the SMT output for the trivial top-level case becomes (assert true) / (assert (not true)) rather than nothing / (assert false). Both are semantically equivalent and match the end-to-end behaviour of the non-incremental SMT2 backend.

The gather-side filter in gather_dependent_expressions is kept: it is a separate walk that runs before the convert-side visitor and still needs to skip zero-width-typed leaves to avoid sending declare-fun for them (which would also call convert_type_to_smt_sort on an empty type).

Update the unit test for set_to over empty type to reflect the new SMT command sequence, and add a new unit test set_to over nested empty-type equality that exercises the nested shape (an and_exprt whose lhs is an empty-type equal_exprt). The existing getting value of empty struct test, which uses empty structs (lowered to bv_typet{8} via struct_encoding) rather than true void, continues to pass unchanged.

Co-authored-by: Kiro kiro-agent@users.noreply.github.com

  • Each commit message has a non-empty body, explaining why the change was made.
  • Methods or procedures I have added are documented, following the guidelines provided in CODING_STANDARD.md.
  • n/a The feature or user visible behaviour I have added or modified has been documented in the User Guide in doc/cprover-manual/
  • Regression or unit tests are included, or existing tests cover the modified code (in this case I have detailed which ones those are in the commit message).
  • n/a My commit message includes data points confirming performance improvements (if claimed).
  • My PR is restricted to a single feature or bugfix.
  • n/a White-space or formatting changes outside the feature-related changed lines are in commits of their own.

tautschnig and others added 3 commits May 27, 2026 09:28
is_zero_width(const typet &, const namespacet &) was a file-static
helper in src/solvers/smt2/smt2_conv.cpp. The predicate is generic
('does this type have effective width of zero bits?') and not
specific to the non-incremental SMT2 backend; the incremental SMT2
backend will need exactly the same predicate to detect empty-typed
expressions correctly without re-treading the same struct/union/
struct-tag/union-tag/array recursion. Move the function to
src/util/pointer_offset_size.{h,cpp} alongside its semantic
neighbours pointer_offset_size and pointer_offset_bits, drop the
'static' qualifier on the original definition, and remove the
duplicate body from smt2_conv.cpp.

No functional change. smt2_conv.cpp continues to call is_zero_width
through the shared header (which it already includes); other
back-ends and util-level call sites can now do the same.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Skip zero-width-typed leaves (typically void) in
gather_dependent_expressions so that we never attempt to call
convert_type_to_smt_sort on them, which would hit
UNIMPLEMENTED_FEATURE. Handle the only top-level consumer that
could legitimately involve such a leaf -- equality of two
zero-width-typed operands -- by short-circuiting set_to: for
value == true the equality is vacuously satisfied (no command
emitted); for value == false we send `(assert false)` directly.
This matches the end-to-end behaviour of the non-incremental SMT2
backend, which converts equal_exprt over a zero-width type to
true_exprt and then negates.

Both filter sites use the shared is_zero_width predicate (now
exposed via util/pointer_offset_size.h) rather than a narrow
ID_empty check, so they stay correct even if struct_encoding's
pre-pass ever stops normalising other zero-width forms.

The set_to short-circuit handles only the top-level equality.
Nested cases such as and_exprt{equal_exprt{x_void, y_void}, other}
would still reach convert_expr_to_smt and trip
UNIMPLEMENTED_FEATURE; CBMC's GOTO programs do not produce such
nested shapes today, but pushing the short-circuit into the
equal_exprt overload of convert_expr_to_smt (mirroring
smt2_conv.cpp's structure more closely) is left as a follow-up.

Add a focused unit test in
unit/solvers/smt2_incremental/smt2_incremental_decision_procedure.cpp
covering both set_to(equal_exprt{void_x, void_y}, true) (no extra
commands sent) and set_to(..., false) (a single `(assert false)`).

Remove the no-new-smt exclusion tag from havoc_slice_checks and
memset_null regression tests since they now pass with the
incremental SMT2 backend.

Fixes: diffblue#8077

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Previously, equality of two zero-width-typed (typically void)
operands was handled at the top level of
smt2_incremental_decision_proceduret::set_to: the `equal_exprt`
was matched textually before lowering reached convert_expr_to_smt,
and a dedicated branch emitted either nothing (for value == true)
or `(assert false)` (for value == false). This worked for the
common case but failed for nested shapes such as

    set_to(and_exprt{equal_exprt{x_void, y_void}, other}, true)

because the recursive visitor in convert_expr_to_smt would still
descend into the void operands of the inner equal_exprt and trip
UNIMPLEMENTED_FEATURE in convert_type_to_smt_sort(empty_typet).

Move the short-circuit one layer down, into convert_expr_to_smt:

  - Add a `const namespacet &` parameter to the public
    convert_expr_to_smt and propagate it to the dispatcher and to
    the equal_exprt overload.
  - In the entry-point's `filtered_visit_post` filter, return false
    for an `equal_exprt` whose operands have zero-width type, so
    that the visitor does not descend into the void operands and
    does not attempt convert_type_to_smt_sort on them.
  - In the equal_exprt overload of convert_expr_to_smt, return
    `smt_bool_literal_termt{true}` directly when the operand type
    is zero-width, mirroring smt2_conv.cpp:1604
    (`convert_expr(true_exprt())`).
  - Drop the now-redundant top-level short-circuit from set_to:
    convert_expr_to_smt produces the boolean literal `true`, which
    set_to asserts as-is for value == true and negates for
    value == false. This means the SMT output for the trivial
    top-level case becomes `(assert true)` / `(assert (not true))`
    rather than nothing / `(assert false)`. Both are semantically
    equivalent and match the end-to-end behaviour of the
    non-incremental SMT2 backend.

The gather-side filter in gather_dependent_expressions is kept:
it is a separate walk that runs before the convert-side visitor
and still needs to skip zero-width-typed leaves to avoid sending
declare-fun for them (which would also call
convert_type_to_smt_sort on an empty type).

Update the unit test for `set_to over empty type` to reflect the
new SMT command sequence, and add a new unit test
`set_to over nested empty-type equality` that exercises the nested
shape (an `and_exprt` whose lhs is an empty-type `equal_exprt`).
The existing `getting value of empty struct` test, which uses
empty structs (lowered to bv_typet{8} via struct_encoding) rather
than true void, continues to pass unchanged.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant