Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getArgs() == null) ? 0 : getArgs().hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((name == null) ? 0 : Utils.getSimpleName(name).hashCode()); // same here
return result;
}

Expand All @@ -114,7 +114,10 @@ public boolean equals(Object obj) {
if (name == null) {
return other.name == null;
} else {
return name.equals(other.name);
// prefixes are inconsistent for refined class ghost calls: some use the
// original class prefix, others use the caller class prefix
// for now we compare simple names, but prefix handling should be fixed instead of having this workaround
return other.name != null && Utils.getSimpleName(name).equals(Utils.getSimpleName(other.name));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package liquidjava.rj_language.opt;

import static liquidjava.rj_language.opt.VCSimplificationUtils.copyWithRefinement;
import static liquidjava.rj_language.opt.VCSimplificationUtils.containsExpression;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import liquidjava.processor.VCImplication;
Expand All @@ -16,47 +19,67 @@
public class VCFunctionSubstitution implements VCSimplificationPass {

/**
* A substitution discovered from a function invocation equality
* A substitution discovered from a function invocation equality. At {@code sourceNode}, remove
* {@code sourceEquality} and in the following nodes replace {@code invocation} with {@code replacement}
*/
private record Substitution(VCImplication node, FunctionInvocation invocation, Expression replacement) {
private record Substitution(VCImplication sourceNode, FunctionInvocation invocation, Expression replacement,
Expression sourceEquality) {
}

/**
* Applies one function invocation substitution in a VC chain
*/
@Override
public VCImplication apply(VCImplication implication) {
VCImplication result = implication.clone();
Optional<Substitution> substitutionOpt = findSubstitution(result);
Optional<Substitution> substitutionOpt = findSubstitution(implication);

if (substitutionOpt.isPresent()) {
Substitution substitution = substitutionOpt.get();
result = substitute(result, substitution.node(), substitution.invocation(), substitution.replacement());
return substitute(implication, substitution.sourceNode(), substitution.invocation(),
substitution.replacement(), substitution.sourceEquality());
}
return result;
return implication;
}

/**
* Preserves nodes before the source equality and starts rewriting at the source suffix
* Rewrites one VC chain with a single substitution and removes its source equality
*/
private VCImplication substitute(VCImplication implication, VCImplication node, FunctionInvocation invocation,
Expression replacement) {
Expression replacement, Expression sourceEquality) {
if (implication == null)
return null;

// skip the source node to remove it from the chain and start substitution from the next node
// consume the source equality and start substitution from the next node
if (implication == node) {
VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone());
result.setNext(substituteSuffix(implication.getNext(), invocation, replacement));
return result;
VCImplication suffix = substituteSuffix(implication.getNext(), invocation, replacement);
VCImplication source = removeSourceEquality(implication, sourceEquality);
if (source == null)
return suffix;
source.setNext(suffix);
return source;
}

// preserve the current node and continue rewriting the suffix
VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone());
result.setNext(substitute(implication.getNext(), node, invocation, replacement));
VCImplication result = copyWithRefinement(implication, implication.getRefinement());
result.setNext(substitute(implication.getNext(), node, invocation, replacement, sourceEquality));
return result;
}

/**
* Removes the equality conjunct that supplied the substitution, preserving any sibling conjuncts
*/
private VCImplication removeSourceEquality(VCImplication implication, Expression sourceEquality) {
List<Expression> remaining = new ArrayList<>(implication.getRefinement().getExpression().getConjuncts());
remaining.remove(sourceEquality);
if (remaining.isEmpty())
return null;

Predicate refinement = new Predicate();
for (Expression conjunct : remaining)
refinement = Predicate.createConjunction(refinement, new Predicate(conjunct));
return copyWithRefinement(implication, refinement);
}

/**
* Rewrites every node after the source equality with one function substitution
*/
Expand All @@ -75,11 +98,11 @@ private VCImplication substituteSuffix(VCImplication implication, FunctionInvoca
*/
private VCImplication substituteNode(VCImplication implication, FunctionInvocation invocation,
Expression replacement) {
Expression expression = implication.getRefinement().getExpression().clone();
Expression expression = implication.getRefinement().getExpression();
if (!containsExpression(expression, invocation))
return copyWithRefinement(implication, new Predicate(expression));
return copyWithRefinement(implication, implication.getRefinement());

Expression substituted = expression.substitute(invocation, replacement.clone());
Expression substituted = expression.substitute(invocation, replacement);
return copyWithRefinement(implication, new Predicate(substituted));
}

Expand All @@ -101,7 +124,7 @@ private Optional<Substitution> findSubstitution(VCImplication implication) {
* Extracts a substitution from one VC node refinement
*/
private Optional<Substitution> getSubstitution(VCImplication implication) {
return getSubstitution(implication, implication.getRefinement().getExpression().clone());
return getSubstitution(implication, implication.getRefinement().getExpression());
}

/**
Expand All @@ -121,33 +144,11 @@ private Optional<Substitution> getSubstitution(VCImplication implication, Expres
Expression left = binary.getFirstOperand();
Expression right = binary.getSecondOperand();
if (left instanceof FunctionInvocation invocation && !containsExpression(right, left))
return Optional.of(new Substitution(implication, (FunctionInvocation) invocation.clone(), right.clone()));
return Optional.of(new Substitution(implication, invocation, right, binary));
if (right instanceof FunctionInvocation invocation && !containsExpression(left, right))
return Optional.of(new Substitution(implication, (FunctionInvocation) invocation.clone(), left.clone()));
return Optional.of(new Substitution(implication, invocation, left, binary));

return Optional.empty();
}

/**
* Checks whether an expression contains another expression
*/
private boolean containsExpression(Expression expression, Expression target) {
if (expression.equals(target))
return true;

for (Expression child : expression.getChildren())
if (containsExpression(child, target))
return true;
return false;
}

/**
* Checks whether a VC suffix contains an expression
*/
private boolean containsExpression(VCImplication implication, Expression target) {
for (VCImplication current = implication; current != null; current = current.getNext())
if (containsExpression(current.getRefinement().getExpression(), target))
return true;
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
*/
public class VCSimplification {

private static final List<VCSimplificationPass> PASSES = List.of(new VCSubstitution(), new VCBinderSimplification(),
new VCFolding(), new VCArithmeticSimplification(), new VCLogicalSimplification());
private static final List<VCSimplificationPass> PASSES = List.of(new VCSubstitution(), new VCFunctionSubstitution(),
new VCBinderSimplification(), new VCFolding(), new VCArithmeticSimplification(),
new VCLogicalSimplification());

/**
* Applies all available simplification steps to a VC chain until a fixed point is reached
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ public static boolean containsVar(VCImplication implication, String name) {
return false;
}

public static boolean containsExpression(Expression expression, Expression target) {
if (expression.equals(target))
return true;

for (Expression child : expression.getChildren())
if (containsExpression(child, target))
return true;
return false;
}

public static boolean containsExpression(VCImplication implication, Expression target) {
for (VCImplication current = implication; current != null; current = current.getNext())
if (containsExpression(current.getRefinement().getExpression(), target))
return true;
return false;
}

public static boolean isTrue(Expression expression) {
return expression instanceof LiteralBoolean literal && literal.isBooleanTrue();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ class VCFunctionSubstitutionTest {
void substitutesExactFunctionInvocationIntoSuffix() {
VCImplication implication = vc("f(x) == 0", "f(y) == f(x) + 1");

assertSimplificationSteps(substitution::apply, implication, step("f(x) == 0", "f(y) == 0 + 1"));
assertSimplificationSteps(substitution::apply, implication, step("f(y) == 0 + 1"));
}

@Test
void substitutesReverseFunctionEquality() {
VCImplication implication = vc("0 == f(x)", "f(y) == f(x) + 1");

assertSimplificationSteps(substitution::apply, implication, step("0 == f(x)", "f(y) == 0 + 1"));
assertSimplificationSteps(substitution::apply, implication, step("f(y) == 0 + 1"));
}

@Test
void preservesSourceNode() {
void consumesSourceNodeWhenSubstitutedInvocationIsGoneFromSuffix() {
VCImplication implication = vc("f(x) == 0", "f(x) > -1");

assertSimplificationSteps(substitution::apply, implication, step("f(x) == 0", "0 > -1"));
assertSimplificationSteps(substitution::apply, implication, step("0 > -1"));
}

@Test
Expand All @@ -41,8 +41,8 @@ void doesNotRewriteEarlierNodesFromLaterEquality() {
void skipsUsedUpEqualityAndUsesNextAvailableEquality() {
VCImplication implication = vc("f(x) == 0", "f(y) == f(x) + 1", "f(y) == 1");

assertSimplificationSteps(substitution::apply, implication, step("f(x) == 0", "f(y) == 0 + 1", "f(y) == 1"),
step("f(x) == 0", "f(y) == 0 + 1", "0 + 1 == 1"));
assertSimplificationSteps(substitution::apply, implication, step("f(y) == 0 + 1", "f(y) == 1"),
step("0 + 1 == 1"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this second step? Shouldn't it be just 1 == 1 ? Probably not related to this change precisely but this step doesnt make much sense to me

@rcosta358 rcosta358 Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are just testing the VCFunctionSubstitution, not the VCFolding.

}

@Test
Expand All @@ -63,6 +63,13 @@ void ignoresRecursiveFunctionEquality() {
void extractsEqualityFromTopLevelConjunction() {
VCImplication implication = vc("ok && f(x) == 0", "f(y) == f(x) + 1");

assertSimplificationSteps(substitution::apply, implication, step("ok && f(x) == 0", "f(y) == 0 + 1"));
assertSimplificationSteps(substitution::apply, implication, step("ok", "f(y) == 0 + 1"));
}

@Test
void removesOnlySourceEqualityConjunct() {
VCImplication implication = vc("ok && f(x) == 0 && ready", "f(y) == f(x) + 1");

assertSimplificationSteps(substitution::apply, implication, step("ok && ready", "f(y) == 0 + 1"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static liquidjava.rj_language.opt.VCSimplificationUtils.containsExpression;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -15,6 +16,7 @@
import liquidjava.rj_language.Predicate;
import liquidjava.rj_language.ast.BinaryExpression;
import liquidjava.rj_language.ast.Expression;
import liquidjava.rj_language.ast.FunctionInvocation;
import liquidjava.rj_language.ast.Var;
import liquidjava.smt.SMTEvaluator;
import liquidjava.smt.SMTResult;
Expand Down Expand Up @@ -81,10 +83,28 @@ private static Predicate substitutionPremises(VCImplication implication) {
for (VCImplication current = implication; current != null; current = current.getNext()) {
if (isSubstitution(current))
premises = Predicate.createConjunction(premises, current.getRefinement());
for (Expression conjunct : current.getRefinement().getExpression().getConjuncts()) {
if (!isFunctionSubstitution(conjunct, current.getNext()))
continue;
premises = Predicate.createConjunction(premises, new Predicate(conjunct.clone()));
}
}
return premises;
}

private static boolean isFunctionSubstitution(Expression expression, VCImplication suffix) {
if (!(expression instanceof BinaryExpression binary) || !"==".equals(binary.getOperator()))
return false;

Expression left = binary.getFirstOperand();
Expression right = binary.getSecondOperand();
if (left instanceof FunctionInvocation && !containsExpression(right, left))
return containsExpression(suffix, left);
if (right instanceof FunctionInvocation && !containsExpression(left, right))
return containsExpression(suffix, right);
return false;
}

private static boolean isSubstitution(VCImplication implication) {
if (!implication.hasBinder())
return false;
Expand Down
Loading