-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
SMTChecker: Inline let expressions in solvers' models #15336
Conversation
We potentially also have quantifiers and thus otherwise bound variables in the smt expressions, right?
(I'm not fluent in writing smtlib, so however you'd actually write this properly) happen? If so, the current code looks like it'd still just substitute If this can happen, then I guess, the let-body (the part in which the let-bound things occur I mean by that) would need to be checked for other binders of the let-bound names and they'd need to be prevented from being replaced in further subexpressions of such nested binders or such... But yeah, I haven't thought about what the expressions could look like that this runs on, so maybe this will just never happen and it's fine - but for arbitrary smtlib2 expressions I'd guess this could be an issue... |
If I'm right, maybe something along the lines of this is safer? void inlineLetExpressions(SMTLib2Expression& _expr, std::unordered_map<std::string, std::reference_wrapper<SMTLib2Expression>> const& _substitutions = {})
{
if (isAtom(_expr))
{
if (auto* bound = util::valueOrNullptr(_substitutions, asAtom(_expr)))
_expr = *bound;
return;
}
auto& subexprs = asSubExpressions(_expr);
solAssert(!subexprs.empty());
auto const& first = subexprs.at(0);
if (isAtom(first))
{
if (asAtom(first) == "let")
{
solAssert(subexprs.size() == 3);
solAssert(!isAtom(subexprs[1]));
auto& bindingExpressions = asSubExpressions(subexprs[1]);
auto newSubstitutions = _substitutions;
for (auto& binding: bindingExpressions)
{
solAssert(!isAtom(binding));
auto& bindingPair = asSubExpressions(binding);
solAssert(bindingPair.size() == 2);
solAssert(isAtom(bindingPair.at(0)));
inlineLetExpressions(bindingPair.at(1), _substitutions);
if (
auto [it, newlyInserted] = newSubstitutions.emplace(asAtom(bindingPair.at(0)), bindingPair.at(1));
!newlyInserted
)
// overwrite existing bindings
it->second = bindingPair.at(1);
}
inlineLetExpressions(subexprs.at(2), newSubstitutions);
// update the expression
auto tmp = std::move(subexprs.at(2));
_expr = std::move(tmp);
return;
}
else if (asAtom(first) == "forall" || asAtom(first) == "exists") // any other binders?
{
auto newSubstitutions = _substitutions;
smtAssert(subexprs.size() == 3);
for (auto const& sortedVar: asSubExpressions(subexprs.at(1)))
// remove substitutions for the quantifier-bound names
newSubstitutions.erase(asAtom(asSubExpressions(sortedVar).at(0)));
inlineLetExpressions(subexprs.at(2), newSubstitutions);
return;
}
}
// not a let expression, just process all arguments recursively
for (auto& subexpr: subexprs)
inlineLetExpressions(subexpr, _substitutions);
} ? (I've rather blindly written that - the snippet stores references to |
libsmtutil/CHCSmtLib2Interface.cpp
Outdated
auto const& first = subexprs.at(0); | ||
if (isAtom(first) && asAtom(first) == "let") | ||
{ | ||
solAssert(subexprs.size() >= 3); |
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.
solAssert(subexprs.size() >= 3); | |
solAssert(subexprs.size() == 3); |
or why can the rest be ignored, if there can be any more subexpressions?
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.
Fixed to assert exactly 3 subexpressions.
You are right in the sense that the expression could contain quantifiers and theoretically it could bind the same variable. I will check your suggestion. |
Yeah, ok - this is part of parsing the solver-generated counterexamples, right? It stands to reason that they internally use unique names and then also serialize them sanely, yeah. So if my suggestion doesn't work due to some life-time issue (or you think it copying the substitutions more often is too expensive for the kinds of expressions we expect here), I'd be fine with just documenting it - but that would be nice at least then, just to make sure that nobody ever tries to use this on code for which we couldn't assume sane/unique names. Conversely, if we assume unique names anyways, I guess that'd also go for the same let-name not being "re-bound", which could simplify the code a bit further... unfortunately, asserting against non-unique names in binders there is almost as much of a complication as treating them properly :-). But yeah, your call, I'd approve this as is, if this part is noted in a comment or such. |
Models computed by CHC solvers (especially Z3) can contain SMT-LIB's let expressions. These are used to name a subterm and use the name instead of repeating the subterm possibly many times. The easiest (but not necessary the best) way to deal with let terms is to inline (expand) them. In our current experiments inlining did not pose any problem. If, in the future, this would turn out to cause problems, we can devise a way to process let terms without expanding the term to its full tree-like representation. This is part of the preparation to use Z3 as an external solver. It allows us to properly process models returned by Z3.
@ekpyron, I added a case to handle quantifiers based on your suggestion, but I kept my original data structure. |
And yes, this is only for parsing the solver-generated counterexamples. I could even hide the method in the cpp file and not expose it, if that is preferable. |
Models computed by CHC solvers (especially Z3) can contain SMT-LIB's let expressions. These are used to name a subterm and use the name instead of repeating the subterm possibly many times.
The easiest (but not necessary the best) way to deal with let terms is to inline (expand) them. In our current experiments inlining did not pose any problem. If, in the future, this would turn out to cause problems, we can devise a way to process let terms without expanding the term to its full tree-like representation.
This is part of the preparation to use Z3 as an external solver. It allows us to properly process models returned by Z3.
It has been separated from #15252.