Skip to content

Commit dcc937d

Browse files
committed
perf: substitute quantifier instantiations in a single traversal
Substitution.applyWithoutCasts applied each variable in its own ClashFreeSubst pass -- one full traversal of the term per variable. As the substitution is ground, all variables can be replaced in a single shadow-aware traversal, keeping the per-variable path only as a fallback for the rare cast case. Behaviour-preserving; cuts substitution time roughly threefold on quantifier-heavy proofs. Created with AI tooling support
1 parent 134339b commit dcc937d

1 file changed

Lines changed: 71 additions & 1 deletion

File tree

  • key.core/src/main/java/de/uka/ilkd/key/strategy/quantifierHeuristics

key.core/src/main/java/de/uka/ilkd/key/strategy/quantifierHeuristics/Substitution.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.key_project.logic.op.Function;
1616
import org.key_project.logic.op.QuantifiableVariable;
1717
import org.key_project.logic.sort.Sort;
18+
import org.key_project.util.collection.ImmutableList;
1819
import org.key_project.util.collection.ImmutableMap;
1920
import org.key_project.util.collection.ImmutableSet;
2021

@@ -94,10 +95,79 @@ private Term applySubst(QuantifiableVariable var, Term instance, Term t, TermBui
9495

9596
/**
9697
* Try to apply the substitution to a term, introducing casts if necessary (may never be the
97-
* case any more, XXX)
98+
* case any more, XXX).
99+
* <p>
100+
* Fast path: all variables are substituted in a single traversal ({@link #applyGroundOnePass}).
101+
* This is sound because the substitution is ground (the instances contain no free variables, so
102+
* no capture can happen); shadowing of a substituted variable by a binder inside {@code t} is
103+
* respected. If a cast is required (the slow, possibly dead path), it falls back to the
104+
* per-variable substitution {@link #applyWithoutCastsPerVar}.
98105
*/
99106
public Term applyWithoutCasts(Term t, Services services) {
100107
assert isGround() : "non-ground substitutions are not yet implemented: " + this;
108+
final TermBuilder tb = services.getTermBuilder();
109+
try {
110+
return applyGroundOnePass((JTerm) t, ImmutableList.nil(), tb);
111+
} catch (TermCreationException e) {
112+
return applyWithoutCastsPerVar(t, services);
113+
}
114+
}
115+
116+
/**
117+
* Substitute every variable of {@link #varMap} in a single traversal of {@code t}. A variable
118+
* occurrence is replaced unless it is shadowed, i.e. re-bound by a binder above it (tracked in
119+
* {@code boundAbove}). Subterms in which no substituted variable occurs free are returned
120+
* unchanged.
121+
*/
122+
private JTerm applyGroundOnePass(JTerm t, ImmutableList<QuantifiableVariable> boundAbove,
123+
TermBuilder tb) {
124+
// Variable leaves (the most common node) are handled directly, without the freeVars
125+
// bookkeeping of containsSubstVar.
126+
if (t.op() instanceof QuantifiableVariable qv) {
127+
final Term inst = varMap.get(qv);
128+
return inst != null && !boundAbove.contains(qv) ? (JTerm) inst : t;
129+
}
130+
if (!containsSubstVar(t)) {
131+
return t;
132+
}
133+
final int arity = t.arity();
134+
final JTerm[] newSubs = new JTerm[arity];
135+
boolean changed = false;
136+
for (int i = 0; i < arity; i++) {
137+
final JTerm sub = t.sub(i);
138+
ImmutableList<QuantifiableVariable> below = boundAbove;
139+
final var boundHere = t.varsBoundHere(i);
140+
for (int j = 0; j < boundHere.size(); j++) {
141+
below = below.prepend(boundHere.get(j));
142+
}
143+
newSubs[i] = applyGroundOnePass(sub, below, tb);
144+
if (newSubs[i] != sub) {
145+
changed = true;
146+
}
147+
}
148+
if (!changed) {
149+
return t;
150+
}
151+
return tb.tf().createTerm(t.op(), newSubs, t.boundVars(), t.getLabels());
152+
}
153+
154+
/** Whether any variable of {@link #varMap} occurs free in {@code t}. */
155+
private boolean containsSubstVar(JTerm t) {
156+
final ImmutableSet<QuantifiableVariable> free = t.freeVars();
157+
if (free.isEmpty()) {
158+
return false;
159+
}
160+
final Iterator<QuantifiableVariable> it = varMap.keyIterator();
161+
while (it.hasNext()) {
162+
if (free.contains(it.next())) {
163+
return true;
164+
}
165+
}
166+
return false;
167+
}
168+
169+
/** Per-variable substitution (one {@link ClashFreeSubst} pass per variable), with casts. */
170+
private Term applyWithoutCastsPerVar(Term t, Services services) {
101171
final TermBuilder tb = services.getTermBuilder();
102172
final Iterator<QuantifiableVariable> it = varMap.keyIterator();
103173
while (it.hasNext()) {

0 commit comments

Comments
 (0)