Skip to content

Commit 2900bf1

Browse files
committed
Add VC Constraint Elimination Simplification
1 parent c643a39 commit 2900bf1

5 files changed

Lines changed: 188 additions & 1 deletion

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package liquidjava.rj_language.opt;
2+
3+
import static liquidjava.rj_language.opt.VCSimplificationUtils.copyWithRefinement;
4+
5+
import liquidjava.processor.VCImplication;
6+
import liquidjava.processor.context.Context;
7+
import liquidjava.rj_language.Predicate;
8+
import liquidjava.smt.SMTEvaluator;
9+
import liquidjava.smt.SMTResult;
10+
11+
/**
12+
* Removes antecedent constraints that are implied by stronger constraints later in the VC chain
13+
*/
14+
public class VCConstraintElimination implements VCSimplificationPass {
15+
16+
/**
17+
* Applies one constraint elimination in a VC chain
18+
*/
19+
@Override
20+
public VCImplication apply(VCImplication implication) {
21+
VCImplication cloned = implication.clone();
22+
VCImplication simplified = simplify(cloned);
23+
return simplified == null ? cloned : simplified;
24+
}
25+
26+
/**
27+
* Removes the first antecedent implied by a later antecedent
28+
*/
29+
private VCImplication simplify(VCImplication implication) {
30+
if (implication == null || implication.getNext() == null)
31+
return null;
32+
33+
VCImplication implying = findImplyingAntecedent(implication);
34+
if (implying != null)
35+
return eliminate(implication, implying);
36+
37+
VCImplication next = simplify(implication.getNext());
38+
if (next == null)
39+
return null;
40+
41+
VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone());
42+
result.setNext(next);
43+
return result;
44+
}
45+
46+
/**
47+
* Finds a later antecedent that implies the current constraint. The final node is the conclusion and is not a
48+
* candidate
49+
*/
50+
private VCImplication findImplyingAntecedent(VCImplication implication) {
51+
for (VCImplication candidate = implication.getNext(); candidate != null
52+
&& candidate.getNext() != null; candidate = candidate.getNext()) {
53+
if (implies(candidate.getRefinement(), implication.getRefinement()))
54+
return candidate;
55+
}
56+
return null;
57+
}
58+
59+
/**
60+
* Eliminates one redundant constraint while preserving any binder attached to it
61+
*/
62+
private VCImplication eliminate(VCImplication implication, VCImplication implying) {
63+
if (!implication.hasBinder())
64+
return implication.getNext().clone();
65+
66+
VCImplication result = copyWithRefinement(implication, implying.getRefinement().clone());
67+
result.setNext(remove(implication.getNext(), implying));
68+
return result;
69+
}
70+
71+
/**
72+
* Removes one node from a suffix
73+
*/
74+
private VCImplication remove(VCImplication implication, VCImplication target) {
75+
if (implication == target)
76+
return implication.getNext() == null ? null : implication.getNext().clone();
77+
78+
VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone());
79+
result.setNext(remove(implication.getNext(), target));
80+
return result;
81+
}
82+
83+
/**
84+
* Checks logical implication using the verifier's existing SMT context
85+
*/
86+
private boolean implies(Predicate stronger, Predicate weaker) {
87+
try {
88+
SMTResult result = new SMTEvaluator().verifySubtype(stronger, weaker, Context.getInstance(), true);
89+
return result.isOk();
90+
} catch (Exception e) {
91+
return false;
92+
}
93+
}
94+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
public class VCSimplification {
1111

1212
private static final List<VCSimplificationPass> PASSES = List.of(new VCSubstitution(), new VCBinderSimplification(),
13-
new VCFolding(), new VCArithmeticSimplification(), new VCLogicalSimplification());
13+
new VCFolding(), new VCArithmeticSimplification(), new VCLogicalSimplification(),
14+
new VCConstraintElimination());
1415

1516
/**
1617
* Applies all available simplification steps to a VC chain until a fixed point is reached
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package liquidjava.rj_language.opt;
2+
3+
import static liquidjava.utils.VCTestUtils.assertSimplificationSteps;
4+
import static liquidjava.utils.VCTestUtils.step;
5+
import static liquidjava.utils.VCTestUtils.vc;
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
9+
import org.junit.jupiter.api.AfterEach;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
13+
import liquidjava.processor.VCImplication;
14+
import liquidjava.processor.context.Context;
15+
import liquidjava.utils.TestUtils;
16+
17+
class VCConstraintEliminationTest {
18+
19+
private final VCConstraintElimination constraintElimination = new VCConstraintElimination();
20+
21+
@BeforeEach
22+
void setUpContext() {
23+
Context.getInstance().reinitializeAllContext();
24+
TestUtils.addIntVariableToContext("x");
25+
TestUtils.addIntVariableToContext("y");
26+
}
27+
28+
@AfterEach
29+
void resetContext() {
30+
Context.getInstance().reinitializeAllContext();
31+
}
32+
33+
@Test
34+
void removesConstraintImpliedByLaterAntecedent() {
35+
assertSimplificationSteps(constraintElimination,
36+
vc("∀x:int. x > 0", "∀cond:boolean. x > 1", "∀y:int. y == x + 1", "y < 0"),
37+
step("x > 1", "y == x + 1", "y < 0"));
38+
}
39+
40+
@Test
41+
void preservesBinderOnStrongerConstraint() {
42+
VCImplication simplified = constraintElimination
43+
.apply(vc("∀x:int. x > 0", "∀cond:boolean. x > 1", "∀y:int. y == x + 1", "y < 0"));
44+
45+
assertTrue(simplified.hasBinder());
46+
assertEquals("x", simplified.getName());
47+
assertEquals("int", simplified.getType().getQualifiedName());
48+
}
49+
50+
@Test
51+
void keepsConstraintThatIsNotImpliedByLaterAntecedent() {
52+
assertSimplificationSteps(constraintElimination, vc("∀x:int. x > 1", "x > 0", "y > 0"),
53+
step("x > 1", "x > 0", "y > 0"));
54+
}
55+
56+
@Test
57+
void ignoresUnrelatedLaterAntecedent() {
58+
assertSimplificationSteps(constraintElimination, vc("∀x:int. x > 0", "y > 1", "x + y > 0"),
59+
step("x > 0", "y > 1", "x + y > 0"));
60+
}
61+
62+
@Test
63+
void doesNotUseConclusionToEliminateConstraint() {
64+
assertSimplificationSteps(constraintElimination, vc("∀x:int. x > 0", "x > 1"), step("x > 0", "x > 1"));
65+
}
66+
67+
@Test
68+
void removesOnlyFirstImpliedConstraint() {
69+
assertSimplificationSteps(constraintElimination, vc("∀x:int. x > 0", "x > 1", "x > 2", "y > 0"),
70+
step("x > 1", "x > 2", "y > 0"), step("x > 2", "y > 0"));
71+
}
72+
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@
33
import static liquidjava.utils.VCTestUtils.*;
44
import static org.junit.jupiter.api.Assertions.assertNull;
55

6+
import org.junit.jupiter.api.AfterEach;
67
import org.junit.jupiter.api.Test;
78

9+
import liquidjava.processor.context.Context;
10+
import liquidjava.utils.TestUtils;
11+
812
class VCSimplificationTest {
913

14+
@AfterEach
15+
void resetContext() {
16+
Context.getInstance().reinitializeAllContext();
17+
}
18+
1019
@Test
1120
void simplifyReturnsNullForNullImplication() {
1221
assertNull(VCSimplification.simplifyToFixedPoint(null));
@@ -163,4 +172,12 @@ void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() {
163172
void simplifyLeavesUnchangedVcAsPlainPredicates() {
164173
assertSimplificationSteps(vc("x > 0", "y > x"), step("x > 0", "y > x"));
165174
}
175+
176+
@Test
177+
void simplifyEliminatesConstraintImpliedByLaterAntecedent() {
178+
TestUtils.addIntVariableToContext("x");
179+
TestUtils.addIntVariableToContext("y");
180+
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("x > 1", "x + 1 < 0"));
182+
}
166183
}

liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
public class VCTestUtils {
1919

2020
private static final CtTypeReference<?> INT = new Launcher().getFactory().Type().INTEGER_PRIMITIVE;
21+
private static final CtTypeReference<?> BOOLEAN = new Launcher().getFactory().Type().BOOLEAN_PRIMITIVE;
2122

2223
public static VCImplication vc(String... implications) {
2324
VCImplication first = null;
@@ -98,6 +99,8 @@ private static VCImplication parseImplication(String implication) {
9899
private static CtTypeReference<?> type(String name) {
99100
if ("int".equals(name))
100101
return INT;
102+
if ("boolean".equals(name))
103+
return BOOLEAN;
101104
throw new IllegalArgumentException("Unsupported test type: " + name);
102105
}
103106

0 commit comments

Comments
 (0)