Skip to content

Commit c799fe9

Browse files
committed
Update Constraint Simplification
1 parent e8aa3d3 commit c799fe9

3 files changed

Lines changed: 85 additions & 43 deletions

File tree

liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCConstraintSimplification.java

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package liquidjava.rj_language.opt;
22

3+
import static liquidjava.rj_language.opt.VCSimplificationUtils.containsVar;
34
import static liquidjava.rj_language.opt.VCSimplificationUtils.copyWithRefinement;
45
import static liquidjava.rj_language.opt.VCSimplificationUtils.isTrue;
56

67
import liquidjava.processor.VCImplication;
78
import liquidjava.processor.context.Context;
89
import liquidjava.rj_language.Predicate;
9-
import liquidjava.rj_language.ast.LiteralBoolean;
1010
import liquidjava.smt.SMTEvaluator;
1111
import liquidjava.smt.SMTResult;
1212

1313
/**
14-
* Simplifies antecedent constraints that are implied by stronger constraints later in the VC chain
14+
* Removes antecedent constraints that are implied by another antecedent
1515
*/
1616
public class VCConstraintSimplification implements VCSimplificationPass {
1717

@@ -26,51 +26,73 @@ public VCImplication apply(VCImplication implication) {
2626
}
2727

2828
/**
29-
* Simplifies the first antecedent implied by a later antecedent
29+
* Removes the first antecedent implied by another antecedent
3030
*/
3131
private VCImplication simplify(VCImplication implication) {
32-
if (implication == null || implication.getNext() == null)
33-
return null;
32+
for (VCImplication redundant = implication; redundant.getNext() != null; redundant = redundant.getNext()) {
33+
if (isTrue(redundant.getRefinement().getExpression()))
34+
continue;
3435

35-
if (!isTrue(implication.getRefinement().getExpression())) { // skip trivial constraints
36-
VCImplication implying = findImplyingAntecedent(implication);
37-
if (implying != null)
38-
return simplifyConstraint(implication);
36+
for (VCImplication stronger = implication; stronger.getNext() != null; stronger = stronger.getNext()) {
37+
if (stronger != redundant && canEliminate(implication, stronger, redundant)
38+
&& implies(stronger.getRefinement(), redundant.getRefinement()))
39+
return eliminate(implication, stronger, redundant);
40+
}
3941
}
42+
return null;
43+
}
4044

41-
// continue searching for simplifications in the suffix
42-
VCImplication next = simplify(implication.getNext());
43-
if (next == null)
44-
return null;
45+
/**
46+
* Checks whether either node can be removed while preserving required binders
47+
*/
48+
private boolean canEliminate(VCImplication implication, VCImplication stronger, VCImplication redundant) {
49+
return isRemovable(implication, redundant) || isRelocatable(implication, stronger);
50+
}
4551

46-
VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone());
47-
result.setNext(next);
48-
return result;
52+
/**
53+
* Removes the redundant node, or moves the stronger refinement onto its required binder and removes the stronger
54+
* node
55+
*/
56+
private VCImplication eliminate(VCImplication implication, VCImplication stronger, VCImplication redundant) {
57+
if (isRemovable(implication, redundant))
58+
return rewrite(implication, redundant, null, null);
59+
return rewrite(implication, stronger, redundant, stronger.getRefinement());
4960
}
5061

5162
/**
52-
* Finds a later antecedent that implies the current constraint. The final node is the conclusion and is not a
53-
* candidate
63+
* Checks whether removing a node also removes every use of its binder
5464
*/
55-
private VCImplication findImplyingAntecedent(VCImplication implication) {
56-
for (VCImplication candidate = implication.getNext(); candidate != null
57-
&& candidate.getNext() != null; candidate = candidate.getNext()) {
58-
// ∀x. x > 0 => x > 1 -> ∀x. true => x > 1
59-
if (implies(candidate.getRefinement(), implication.getRefinement()))
60-
return candidate;
61-
}
62-
return null;
65+
private boolean isRemovable(VCImplication implication, VCImplication node) {
66+
if (!node.hasBinder())
67+
return true;
68+
69+
for (VCImplication current = implication; current != null; current = current.getNext())
70+
if (current != node && containsVar(current.getRefinement().getExpression(), node.getName()))
71+
return false;
72+
return true;
6373
}
6474

6575
/**
66-
* Simplifies a redundant constraint to true
76+
* Checks whether a node can be removed while retaining its refinement elsewhere
6777
*/
68-
private VCImplication simplifyConstraint(VCImplication implication) {
69-
if (!implication.hasBinder())
70-
return implication.getNext().clone();
78+
private boolean isRelocatable(VCImplication implication, VCImplication node) {
79+
return isRemovable(implication, node)
80+
&& (!node.hasBinder() || !containsVar(node.getRefinement().getExpression(), node.getName()));
81+
}
82+
83+
/**
84+
* Clones a chain while removing one node and optionally replacing another node's refinement
85+
*/
86+
private VCImplication rewrite(VCImplication implication, VCImplication removed, VCImplication replaced,
87+
Predicate replacement) {
88+
if (implication == null)
89+
return null;
90+
if (implication == removed)
91+
return rewrite(implication.getNext(), removed, replaced, replacement);
7192

72-
VCImplication result = copyWithRefinement(implication, new Predicate(new LiteralBoolean(true)));
73-
result.setNext(implication.getNext().clone());
93+
Predicate refinement = implication == replaced ? replacement.clone() : implication.getRefinement().clone();
94+
VCImplication result = copyWithRefinement(implication, refinement);
95+
result.setNext(rewrite(implication.getNext(), removed, replaced, replacement));
7496
return result;
7597
}
7698

liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCConstraintSimplificationTest.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,49 @@ void resetContext() {
3131
}
3232

3333
@Test
34-
void simplifiesConstraintImpliedByLaterAntecedent() {
34+
void keepsRequiredBinderAndRemovesLaterStrongerBinder() {
3535
assertSimplificationSteps(simplification,
3636
vc("∀x:int. x > 0", "∀cond:boolean. x > 1", "∀y:int. y == x + 1", "y < 0"),
37-
step("true", "x > 1", "y == x + 1", "y < 0"));
37+
step("x > 1", "y == x + 1", "y < 0"));
3838
}
3939

4040
@Test
41-
void preservesBothBindersWhenSimplifyingConstraint() {
41+
void preservesRequiredBinderWhenRemovingStrongerBinder() {
4242
VCImplication simplified = simplification
4343
.apply(vc("∀x:int. x > 0", "∀cond:boolean. x > 1", "∀y:int. y == x + 1", "y < 0"));
4444

4545
assertTrue(simplified.hasBinder());
4646
assertEquals("x", simplified.getName());
4747
assertEquals("int", simplified.getType().getQualifiedName());
4848
assertTrue(simplified.getNext().hasBinder());
49-
assertEquals("cond", simplified.getNext().getName());
50-
assertEquals("boolean", simplified.getNext().getType().getQualifiedName());
49+
assertEquals("y", simplified.getNext().getName());
50+
assertEquals("int", simplified.getNext().getType().getQualifiedName());
5151
}
5252

5353
@Test
54-
void keepsConstraintThatIsNotImpliedByLaterAntecedent() {
55-
assertSimplificationSteps(simplification, vc("∀x:int. x > 1", "x > 0", "y > 0"),
56-
step("x > 1", "x > 0", "y > 0"));
54+
void removesConstraintImpliedByEarlierAntecedent() {
55+
assertSimplificationSteps(simplification, vc("∀x:int. x > 1", "∀cond:boolean. x > 0", "∀y:int. y == x + 1"),
56+
step("x > 1", "y == x + 1"));
57+
}
58+
59+
@Test
60+
void removesCoverageConstraintsImpliedByStrongerLaterConstraints() {
61+
assertSimplificationSteps(simplification,
62+
vc("∀x:int. x >= 0", "∀#fresh_40:boolean. !(y < 40)", "∀#fresh_60:boolean. !(y < 60)",
63+
"∀#fresh_80:boolean. !(y < 80)", "x + y > 0"),
64+
step("x >= 0", "!(y < 60)", "!(y < 80)", "x + y > 0"), step("x >= 0", "!(y < 80)", "x + y > 0"));
65+
}
66+
67+
@Test
68+
void keepsConstraintsWhenBothBindersAreRequired() {
69+
assertSimplificationSteps(simplification, vc("∀x:int. y >= 0", "∀y:int. y > 0", "x + y > 0"),
70+
step("y >= 0", "y > 0", "x + y > 0"));
71+
}
72+
73+
@Test
74+
void keepsConstraintsThatDoNotImplyEachOther() {
75+
assertSimplificationSteps(simplification, vc("∀x:int. x > 1", "y > 0", "x + y > 0"),
76+
step("x > 1", "y > 0", "x + y > 0"));
5777
}
5878

5979
@Test
@@ -70,6 +90,6 @@ void doesNotUseConclusionToSimplifyConstraint() {
7090
@Test
7191
void simplifiesOnlyFirstImpliedConstraint() {
7292
assertSimplificationSteps(simplification, vc("∀x:int. x > 0", "x > 1", "x > 2", "y > 0"),
73-
step("true", "x > 1", "x > 2", "y > 0"), step("true", "x > 2", "y > 0"));
93+
step("x > 1", "x > 2", "y > 0"), step("x > 2", "y > 0"));
7494
}
7595
}

liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,10 @@ void simplifyLeavesUnchangedVcAsPlainPredicates() {
174174
}
175175

176176
@Test
177-
void simplifyNeutralizesConstraintImpliedByLaterAntecedent() {
177+
void simplifyEliminatesConstraintImpliedByLaterAntecedent() {
178178
TestUtils.addIntVariableToContext("x");
179179
TestUtils.addIntVariableToContext("y");
180180
assertSimplificationSteps(vc("∀x:int. x > 0", "∀cond:boolean. x > 1", "∀y:int. y == x + 1", "y < 0"),
181-
step("x > 0", "x > 1", "x + 1 < 0"), step("true", "x > 1", "x + 1 < 0"));
181+
step("x > 0", "x > 1", "x + 1 < 0"), step("x > 1", "x + 1 < 0"));
182182
}
183183
}

0 commit comments

Comments
 (0)