what4: Don't annotate {Nonce,}AppExprs#247
Conversation
1dd0545 to
bfb4b66
Compare
|
CI fails with: the test: what4/what4/test/ExprBuilderSMTLib2.hs Lines 1144 to 1160 in e46dff4 I'm able to reproduce this locally with On this branch, print e2A
print e2B
print e2C
print e2C'
print e2Dyields whereas on |
|
Printf-debugging shows that this branch hits this case in what4/what4/src/What4/Expr/Builder.hs Line 1401 in e46dff4 whereas what4/what4/src/What4/Expr/Builder.hs Line 1425 in e46dff4 Counter-intuitively, it appears that hitting the more specialized case results in a less-specific answer. Possibly, the |
EDIT: Speculation that turned out to be falseI think this may have to do with the fact that the catch-all case calls this function: what4/what4/src/What4/Expr/Builder.hs Lines 1213 to 1220 in e46dff4 whereas the more specific case calls
[EDIT]: This does not appear to be the problem, adding |
|
It appears that what4/what4/src/What4/Expr/Builder.hs Line 589 in e46dff4 whereas the result of what4/what4/src/What4/Expr/Builder.hs Line 590 in e46dff4 So I need to figure out why To be more specific, the question is why |
FWIW, let e2C' = O.BVDArith (A.range w 2 2)
let e2D = O.add e2C' (O.singleton w 1)
case O.asSingleton e2D of
Just bv -> pure () -- bv == mkBV w 3
Nothing -> error "sad"works just fine. Perhaps the abstract domain is getting lost when the binary |
|
Okay, I've managed to whittle down the issue to a test case, reported here (as it happens on |
This could previously be done with `Annotation`, but that behavior was not well documented/part of the interface for `annotateTerm`.
bfb4b66 to
f324e45
Compare
These constructors already carry `Nonce`s, adding another layer can only hurt performance by adding allocations/indirections, and especially, disabling important rewrites.
85d2571 to
862a865
Compare
|
Alright, I've worked around the issues with the abstract domains by introducing a new type of |
|
I'm trying this out on a codebase that makes extensive use of annotations. |
| -- | See 'What4.Interface.opacify'. | ||
| -- | ||
| -- No rewrite rules in 'IsExprBuilder' should examine the subterms of | ||
| -- 'Opaque', this is the contract of 'What4.Interface.opacify'. | ||
| Opaque :: | ||
| !(BaseTypeRepr tp) -> | ||
| !(e tp) -> | ||
| NonceApp t e tp |
There was a problem hiding this comment.
What part of the code ignores the subterms of Opaque? (It's not obvious to me at a glance where the magic actually happens.)
There was a problem hiding this comment.
Ah, it's just that the implementation of IsExprBuilder for ExprBuilder doesn't match on it and rewrite under it! That's the second part of the Haddock here.
There was a problem hiding this comment.
Sorry, I must be missing something—where exactly does this happen in the IsExprBuilder instance for ExprBuilder?
There was a problem hiding this comment.
Sorry, I must be missing something—where exactly does this happen in the IsExprBuilder instance for ExprBuilder?
That's the thing haha: It doesn't happen. There are no pattern matches on Opaque, which is what makes it preserve the abstract values. It works just like Annotation did for this purpose.
There was a problem hiding this comment.
To give an example of what I'm talking about: #248 happens because bvAdd calls semiRingAdd, which performs some rewrites using pattern-matching like so:
what4/what4/src/What4/Expr/Builder.hs
Line 1798 in ebf9ac0
Specifically, that issue calls out this case as problematic:
what4/what4/src/What4/Expr/Builder.hs
Line 1806 in ebf9ac0
But note that none of those patterns would match if either x or y were Opaque, so the rewrite rules wouldn't fire, so the abstract domain would be preserved. This is how it works for Annotation on master.
There was a problem hiding this comment.
But note that none of those patterns would match if either x or y were
Opaque
I'm not sure how to typecheck this sentence, since x and y are Exprs, but Opaque is a NonceApp (as is Annotation). Surely Opaque wouldn't matter here?
There was a problem hiding this comment.
I'm not sure how to typecheck this sentence, since x and y are Exprs, but Opaque is a NonceApp (as is Annotation). Surely Opaque wouldn't matter here?
To be more precise: none of those patterns would match, because viewSemiRing applied to the NonceAppExpr containing the Opaque would yield SR_General, and not any of these more particular cases.
There was a problem hiding this comment.
Thank you! This is the part that I was missing.
| @@ -407,7 +407,7 @@ class HasAbsValue e => IsExpr e where | |||
| -- Note that composing expressions together can sometimes widen the abstract | |||
| -- domains involved, so if you use this function to change an abstract value, | |||
| -- be careful than subsequent operations do not widen away the value. As a | |||
There was a problem hiding this comment.
| -- be careful than subsequent operations do not widen away the value. As a | |
| -- be careful that subsequent operations do not widen away the value. As a |
| -- | Make an expression /opaque/, inhibiting rewrites that, in many cases, | ||
| -- coarsen abstract domain information. See #248 for discussion and #249 for | ||
| -- progress towards obviating opaque expressions. | ||
| opacify :: sym -> SymExpr sym tp -> IO (SymExpr sym tp) |
There was a problem hiding this comment.
After reading these Haddocks, I'm a bit confused about the relationship between opacify and annotateTerm. Is the idea that annotateTerm should make no guarantees about inhibiting rewrites, as that should be the role of opacify? Or is the idea that annotateTerm should guarantee that rewrites are inhibited, and that the only reason it doesn't are due to bugs (e.g., #248)? If it's the latter, is opacify meant to be a short-term workaround until bugs like #248 are fixed?
There was a problem hiding this comment.
Is the idea that annotateTerm should make no guarantees about inhibiting rewrites, as that should be the role of opacify?
Exactly! annotateTerm was previously treated as if it made this guarantee, but I think it's better to have a separate constructor (Opaque) for that (which doesn't need to attach an extra Nonce, either).
Or is the idea that annotateTerm should guarantee that rewrites are inhibited, and that the only reason it doesn't are due to bugs (e.g., #248)?
The test case in that issue doesn't use annotateTerm, which is why the abstract value is discarded. annotateTerm does happen to prevent rewrites at the moment, but for the sake of the changes in this PR, I think we should abandon that.
If it's the latter, is opacify meant to be a short-term workaround until bugs like #248 are fixed?
Yes, it is a temporary workaround for that issue.
There was a problem hiding this comment.
Yes, it is a temporary workaround for that issue.
I am confused. If annotateTerm is no longer meant to inhibit rewrites, then isn't opacify the only part of the API that serves this role? If so, I would expect opacify to be a stable part of the API, but your wording suggests that it isn't... I must be missing something.
There was a problem hiding this comment.
If annotateTerm is no longer meant to inhibit rewrites, then isn't opacify the only part of the API that serves this role?
Yes, opacify is meant to replace the use of annotateTerm for this purpose, because the primary changes in this MR (not attaching Annotation to {Nonce,}AppExprs) make annotateTerm no longer suitable for it (as demonstrated by the weird test case failures above).
If so, I would expect opacify to be a stable part of the API, but your wording suggests that it isn't... I must be missing something.
Well, the entire use of annotateTerm to preserve abstract values was a hack to work around #248 and issues like it - If all the rewrite rules preserved abstract values, there would be no need to hide terms under Annotation/Opaque to preserve their abstract values. This behavior was not documented as part of the intent of the annotation API. So IMO, this is replacing one temporary workaround with one that is better-documented and more efficient.
There was a problem hiding this comment.
Well, the entire use of
annotateTermto preserve abstract values was a hack to work around #248 and issues like it - If all the rewrite rules preserved abstract values, there would be no need to hide terms underAnnotation/Opaqueto preserve their abstract values.
Ah, you had preserving abstract values in mind when you wrote your comments—I see. I think I agree that if everything preserved abstract values, then we'd no longer need Opaque, although it's hard to say for sure that there wouldn't be other use cases for Opaque. For instance, it's conceivable that there are SAW proof goals that are easier to prove when derived from certain what4 Expr shapes than others, and Opaque could be one possible mechanism to achieve this. (I don't have a concrete example of this in mind, but it doesn't seem totally outside of the realm of possibility.)
In that case, I think if we added some additional clarification to the annotateTerm Haddocks about what it does (and does not) guarantee, then I'd be content with this.
| -- | Inverse operation of 'opacify'. | ||
| clarify :: sym -> SymExpr sym tp -> SymExpr sym tp |
There was a problem hiding this comment.
It would be helpful to specify what happens if you call clarify on a SymExpr that wasn't returned by a call to opacify.
|
Do you expect that these changes to how annotations work will have effects on downstream code? If so, is it worth advertising this in the changelog? |
Yes, in that using |
|
OK. I think I have large SAW proofs in mind when I wrote #247 (comment) , as I worry that this could lead to subtle changes in proof goals that are hard to track down. If all that is required is to audit uses of |
|
I'm not familiar with how SAW uses annotations, so not sure I can be helpful there (apologies!) |
Fixes #246. Really,
Annotationshouldn't need to carry aNonce, because the outerNonceAppExprwill already have one. However, actually removing it is challenging due to the return type ofsbNonceExpr, and in turnExprAllocator'snonceExpr, which returns anExpr. Surely in practice, this is always aNonceAppExpr, but there's no way to tell from the type. To avoid a larger refactor or introducing partiality, I'm keeping theNonceinAnnotationfor now.Fixes #246, though we may want to create a follow-up about removing the
NoncefromAnnotation.