-
Notifications
You must be signed in to change notification settings - Fork 55
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
Round 2 of AntiAliasing changes #1252
Conversation
9a84d38
to
cda82ca
Compare
cda82ca
to
2bcebc1
Compare
Converting this to a draft due to some inefficiency introduced by #1245. import stainless._
import stainless.lang._
object i1251b {
case class Box(var length: Int)
def mutate(b: Box): Unit = { b.length = 123; }
def h2(b1: Box, b2: Box, cond: Boolean): Unit = {
val x = b1.length
val c = if (cond) b1 else b2
val d = c
mutate(d)
assert(b1.length == x)
}
} we generate: val x: Int = b1.length
var c: Box = (if (cond) {
b1
} else {
b2
})
var d: Box = (if (cond) {
b1
} else if (!cond) {
b2
} else {
c
})
var res: (Unit, Box) = mutate(@DropVCs (if (cond) {
b1
} else if (!cond && !cond) {
b2
} else if (!cond && !!cond) {
if (cond) {
b1
} else if (!cond) {
b2
} else {
c
}
} else if (cond) {
b1
} else if (!cond && !cond) {
b2
} else if (!cond && !!cond) {
c
} else {
d
}))
(if (cond) {
b1 = @DropVCs Box(@DropVCs res._2.length).asInstanceOf[Box]
} else {
()
})
(if (!cond && (!cond && !cond)) {
b2 = @DropVCs Box(@DropVCs res._2.length).asInstanceOf[Box]
} else {
()
})
(if (!cond && (!(!cond && !cond) && (!cond && !!cond))) {
c = @DropVCs Box(@DropVCs res._2.length).asInstanceOf[Box]
} else {
()
})
(if (!cond && (!(!cond && !cond) && !(!cond && !!cond))) {
d = @DropVCs Box(@DropVCs res._2.length).asInstanceOf[Box]
} else {
()
})
res._1
assert(b1.length == x)
() due to duplicating every occurrence of |
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.
About duplicating expressions, we're already doing this a lot in the imperative phase (e.g. during effect copying after function calls), so it's not necessarily a deal breaker.
You could try introducing some additional simplifications in the ImperativeCleanup
phase to deal with these. Maybe using simplifyLets
and/or simplifyExpr
will give good results.
@@ -384,12 +390,12 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override | |||
val updates1 = | |||
targets1 map { target => | |||
val applied = updatedTarget(target, ArraySelect(array2, index2).setPos(swap)) | |||
transform(Assignment(target.receiver, applied).setPos(swap), env) | |||
Assignment(target.receiver, applied).setPos(swap) |
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.
I think we still need to transform array1
, index1
, array2
and index2
.
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.
Good point, I've totally missed that out! I'll also add tests for these cases.
val applied = updatedTarget(target, v) | ||
transform(Assignment(target.receiver, applied).copiedFrom(up), env) | ||
val applied = updatedTarget(target, recV) | ||
Assignment(target.receiver, applied).copiedFrom(up) |
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.
I think this transformation (and a few others) might be incorrectly dropping stuff from the input array expression. If there was a Block
expression in a
, then I believe it would be silently dropped in the output here.
I'm not sure what the cleanest approach to fix this is, though. I guess some options are:
- adding a method which extracts the non-target part of an expression and join it into the output,
- we could always include the expression in the output and hope simplification cleans it up later on by having the result be something like
Block(transform(a, env), Assignement(...))
- we could have a pre-transformation which makes sure to bring all blocks/targets into a normal form which is compatible with target assignment. This option would also make sure that mutating calls inside targets are handled correctly.
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.
Option 3 seems to also benefit #1068 (as you pointed out)
case au@ArrayUpdated(arr, i, arg) => | ||
val Seq(rewrArr, rewrI, rewrArg) = rewriteAndCheckArgs(au, Seq(arr, i, arg)) | ||
ArrayUpdated(transform(rewrArr, env), transform(rewrI, env), transform(rewrArg, env)).copiedFrom(au) | ||
|
||
case mu@MapUpdated(map, k, v) => | ||
val Seq(rewrMap, rewrK, rewrV) = rewriteAndCheckArgs(mu, Seq(map, k, v)) | ||
MapUpdated(transform(rewrMap, env), transform(rewrK, env), transform(rewrV, env)).copiedFrom(mu) |
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.
I think these are only relevant when the array and map base types are mutable, no?
case _ => | ||
throw MalformedStainlessCode(expr, s"Couldn't compute effect targets of array copy update ${au.asString}") | ||
} |
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.
This (and the corresponding MapUpdated
logic) seems to strict. This will also prevent users from using the snapshot(a).updated(i, v)
construct which we were discussing.
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.
That's true, for instance, the SortedArray
benchmark needs to be updated to account for that restriction (I'm realizing that we can also return Seq.empty
if the effect kind is replacement).
For ModfiyingKind
though, I do not think there is a way to express the concerned targets.
2bcebc1
to
d315f3f
Compare
It remains to comment on parts of the code, I'll notify you once (if ever...) I'm done :) |
d315f3f
to
ece5e16
Compare
@samarion Ready for re-review! (when & if you have the time and desire to do so, of course :) ) |
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.
I've read through most of the change and left a few comments. I quite like the move away from rewritings with explicit target tracking instead.
I didn't check the normalization logic yet, but I'm surprised by some of the benchmarks which were disabled due to it (for example, the MutateInsideN benchmarks). Which problems are the block and let hoisting steps solving?
if (be.receiver == vd.toVariable) getTargets(e, kind, be.path.path) | ||
else Seq(be) | ||
val targs0 = getTargets(b, kind, path) | ||
// TODO: Explain why |
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.
Please do :)
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.
Oops, thank you for catching that!
getAllTargets(expr) flatMap { (target: Target) => | ||
inEnv(target.toEffect(ModifyingKind), env) | ||
inEnv(target.toEffect(ModifyingKind), env).toSet |
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.
Nit: you don't need the call to toSet
here, right?
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.
I have reverted the getTargets
returning a Seq
to return a Set
(as it was before), but I forgot to review the parts in EffectsAnalyzer
that depend on this Seq
/Set
return type (like for the commented snippet below!)
@@ -782,7 +821,7 @@ trait EffectsAnalyzer extends oo.CachingPhase { | |||
.filter(p => effects contains p._2) | |||
.flatMap(_._1) | |||
|
|||
case Assignment(v, value) => rec(value, env) ++ env.get(v) | |||
case Assignment(v, value) => rec(value, env) ++ env.get(v).toSeq.flatten |
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.
Nit: why toSeq
?
val castedValue = | ||
if (adt.isInstanceOf[ADTType] && adt.asInstanceOf[ADTType].getSort.constructors.size == 1) | ||
newValue | ||
else | ||
Annotated(AsInstanceOf(newValue, effect.receiver.getType), Seq(DropVCs)).copiedFrom(newValue) | ||
Assignment(effect.receiver, castedValue).setPos(pos) | ||
wrap(AsInstanceOf(newValue, target.receiver.getType)) |
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.
Should we have AsInstanceOf(...).copiedFrom(newValue)
to propagate positions?
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.
Ah yes, good point!
}.filter(_._2.nonEmpty) | ||
} | ||
|
||
def aliasingValDefs2(updTargets: Set[Target], env: Env): Map[ValDef, Set[Target]] = { |
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.
Does overloading not work here? Adding the 2 suffix is more confusing IMO
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.
I had some issues with overloading IRC, but I can try adding a DummyImplicit
to resolve the issue
if (conds.isEmpty) None | ||
else { |
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.
This case is redundant, calling orJoin
on Seq()
will return BooleanLiteral(true)
and result in a None
value for combinedCond
.
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.
Fair point!
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.
Actually, orJoin
returns BooleanLiteral(false)
on an empty sequence (but andJoin
does return BooleanLiteral(true)
)
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.
Oh right, good point.
Hmm, but then shouldn't we consider an empty condition as implying BooleanLiteral(true)
? It seems fishy to me that we have special handling of the empty case. Maybe conds
should rather be defined as
val conds = tgs.map(_.condition.getOrElse(BooleanLiteral(true))
(and then we can just use orJoin
and avoid the special case for empty conditions).
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.
That's a more sensical thing to do indeed
// But it also conditionally introduce a new target, namely Ref(123) when !cond1. | ||
// In such case, we introduce `y` as a new target, with condition !cond1. | ||
assert(targs.forall(_.condition.isDefined)) | ||
val negatedConds = not(orJoin(targs.map(_.condition.get).toSeq)) |
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.
Does this really work in practice? IIRC, the not
and orJoin
simplifications are really trivial so you might not even catch the case where you have cond
and !cond
as conditions. Maybe you could introduce some dedicated logic since you have a strong expectation of what the conditions will look like.
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.
You are right, it doesn't work at all in practice (it ends up generating quite a lot of extra update statements, though it's not incorrect). I'll add something that at least catches things such as cond
and !cond
.
val ra = freshenLocals(replaceFromSymbols(env.rewritings, a)) | ||
val targets = getDirectTargets(ra, ArrayAccessor(i)) | ||
// Sanity check (the same as for Swap) | ||
assert(Some(targets) == getDirectTargetsDealiased(transform(arr, env), ArrayAccessor(recI), env)) |
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.
Isn't there a risk that target computation will fail on the transformed expression?
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.
getDirectTargetsDealiased
returns a None
in case of failure (which would fire the assertion...)
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.
Yes, I meant: won't this generate spurious failures? It's not clear to me that we expect getDirectTargetsDealiased
to always work on transformed trees.
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.
Ah ok, in that sense! Yes you are right, I can remove these checks
Thank you for reviewing! For the normalization, the two main motivations are to sequence effects of function calls (and addresses issue such as #1068) and introduce |
Regarding block hoisting, could we maybe avoid flattening them? Blocks are somewhat useful in allowing local effects which is what the |
Will do! |
(note: without mutation, we couldn't create a cycle with epfl-lara/bolts#44 (without resorting to lazy evaluation) :p)