@@ -99,6 +99,8 @@ simplify_exprt::resultt<> simplify_exprt::simplify_boolean(const exprt &expr)
9999 bool no_change = true ;
100100 bool may_be_reducible_to_interval =
101101 expr.id () == ID_or && expr.operands ().size () > 2 ;
102+ bool may_be_reducible_to_singleton_interval =
103+ expr.id () == ID_and && expr.operands ().size () == 2 ;
102104
103105 exprt::operandst new_operands = expr.operands ();
104106
@@ -137,6 +139,103 @@ simplify_exprt::resultt<> simplify_exprt::simplify_boolean(const exprt &expr)
137139 }
138140 }
139141
142+ // NOLINTNEXTLINE(whitespace/line_length)
143+ // This block reduces singleton intervals like (value >= 255 && value <= 255)
144+ // to just (value == 255). We also need to be careful with the operands
145+ // as some of them are erased in the previous step. We proceed only if
146+ // no operands have been erased (i.e. the expression structure has been
147+ // preserved by previous simplification rounds.)
148+ if (may_be_reducible_to_singleton_interval && new_operands.size () == 2 )
149+ {
150+ struct boundst
151+ {
152+ mp_integer lower;
153+ mp_integer higher;
154+ };
155+ boundst bounds;
156+ bool structure_matched = false ;
157+
158+ // Before we do anything else, we need to "pattern match" against the
159+ // expression and make sure that it has the structure we're looking for.
160+ // The structure we're looking for is going to be
161+ // (value >= 255 && !(value >= 256)) -- 255, 256 values indicative.
162+ // (this is because previous simplification runs will have transformed
163+ // the less_than_or_equal expression to a not(greater_than_or_equal)
164+ // expression)
165+
166+ // matching (value >= 255)
167+ auto const match_first_operand = [&bounds](const exprt &op) -> bool {
168+ if (
169+ const auto ge_expr =
170+ expr_try_dynamic_cast<greater_than_or_equal_exprt>(op))
171+ {
172+ // The construction of these expressions ensures that the RHS
173+ // is constant, therefore if we don't have a constant, it's a
174+ // different expression, so we bail.
175+ if (!ge_expr->rhs ().is_constant ())
176+ return false ;
177+ if (
178+ auto int_opt =
179+ numeric_cast<mp_integer>(to_constant_expr (ge_expr->rhs ())))
180+ {
181+ bounds.lower = *int_opt;
182+ return true ;
183+ }
184+ return false ;
185+ }
186+ return false ;
187+ };
188+
189+ // matching !(value >= 256)
190+ auto const match_second_operand = [&bounds](const exprt &op) -> bool {
191+ if (const auto not_expr = expr_try_dynamic_cast<not_exprt>(op))
192+ {
193+ PRECONDITION (not_expr->operands ().size () == 1 );
194+ if (
195+ const auto ge_expr =
196+ expr_try_dynamic_cast<greater_than_or_equal_exprt>(
197+ not_expr->op ()))
198+ {
199+ // If the rhs() is not constant, it has a different structure
200+ // (e.g. i >= j)
201+ if (!ge_expr->rhs ().is_constant ())
202+ return false ;
203+ if (
204+ auto int_opt =
205+ numeric_cast<mp_integer>(to_constant_expr (ge_expr->rhs ())))
206+ {
207+ bounds.higher = *int_opt - 1 ;
208+ return true ;
209+ }
210+ return false ;
211+ }
212+ return false ;
213+ }
214+ return false ;
215+ };
216+
217+ // We need to match both operands, at the particular sequence we expect.
218+ structure_matched |= match_first_operand (new_operands[0 ]);
219+ structure_matched &= match_second_operand (new_operands[1 ]);
220+
221+ if (structure_matched && bounds.lower == bounds.higher )
222+ {
223+ // Go through the expression again and convert >= operand into ==
224+ for (const auto &op : new_operands)
225+ {
226+ if (
227+ const auto ge_expr =
228+ expr_try_dynamic_cast<greater_than_or_equal_exprt>(op))
229+ {
230+ equal_exprt new_expr{ge_expr->lhs (), ge_expr->rhs ()};
231+ return changed (new_expr);
232+ }
233+ else
234+ continue ;
235+ }
236+ }
237+ }
238+
140239 if (may_be_reducible_to_interval)
141240 {
142241 optionalt<symbol_exprt> symbol_opt;
0 commit comments