Skip to content

Commit 9ca9b04

Browse files
committed
evict cache selectively
1 parent 57218bf commit 9ca9b04

6 files changed

Lines changed: 279 additions & 14 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
* text=auto
2+
*.sh text eol=lf
3+
gradlew text eol=lf
4+
*.bat text eol=crlf

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstChecker.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,6 @@ public void checkProg(WurstModel root, Collection<CompilationUnit> toCheck) {
3939

4040
if (errorHandler.getErrorCount() > 0) return;
4141

42-
// compute the flow attributes
43-
for (CompilationUnit cu : toCheck) {
44-
WurstValidator.computeFlowAttributes(cu);
45-
}
46-
47-
4842
// validate the resource:
4943
WurstValidator validator = new WurstValidator(root);
5044
validator.validate(toCheck);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/GlobalCaches.java

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package de.peeeq.wurstscript.validation;
22

3+
import de.peeeq.wurstscript.ast.CompilationUnit;
34
import de.peeeq.wurstscript.ast.Element;
5+
import de.peeeq.wurstscript.ast.WurstModel;
6+
import de.peeeq.wurstscript.attributes.AttrNearest;
47
import de.peeeq.wurstscript.intermediatelang.ILconst;
58
import de.peeeq.wurstscript.intermediatelang.interpreter.LocalState;
9+
import de.peeeq.wurstscript.jassIm.ImFunction;
610
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
711

8-
import java.util.Arrays;
9-
import java.util.Map;
12+
import java.util.*;
1013
import java.util.concurrent.atomic.AtomicLong;
1114

1215
// Expose static fields only if you already have them there; otherwise, just clear via dedicated methods.
@@ -87,7 +90,7 @@ public boolean equals(Object o) {
8790

8891
public enum Mode {TEST_ISOLATED, DEV_PERSISTENT}
8992

90-
private static volatile Mode mode = Mode.DEV_PERSISTENT;
93+
public static volatile Mode mode = Mode.DEV_PERSISTENT;
9194

9295
public static void setMode(Mode m) {
9396
mode = m;
@@ -130,6 +133,118 @@ public static void clearAll() {
130133
lookupCache.clear();
131134
}
132135

136+
/**
137+
* Evict cache entries that are tied to any of the given compilation units.
138+
*
139+
* <p>The validator replaces only a subset of units on incremental runs. Instead of
140+
* purging the full global caches (which would force re-computation for every file), we
141+
* walk both caches and drop entries whose owner element/function belongs to one of the
142+
* affected compilation units.</p>
143+
*/
144+
public static void invalidateFor(WurstModel model, Collection<CompilationUnit> changedUnits) {
145+
if (mode == Mode.TEST_ISOLATED) {
146+
clearAll();
147+
return;
148+
}
149+
150+
Set<CompilationUnit> affected = toIdentitySet(changedUnits);
151+
Set<CompilationUnit> live = model == null ? null : toIdentitySet(model);
152+
153+
if (affected.isEmpty() && (live == null || live.isEmpty())) {
154+
return;
155+
}
156+
157+
invalidateLookupCache(affected, live);
158+
invalidateLocalStateCache(affected, live);
159+
}
160+
161+
private static Set<CompilationUnit> toIdentitySet(Iterable<CompilationUnit> units) {
162+
Set<CompilationUnit> set = Collections.newSetFromMap(new IdentityHashMap<>());
163+
if (units == null) {
164+
return set;
165+
}
166+
for (CompilationUnit cu : units) {
167+
if (cu != null) {
168+
set.add(cu);
169+
}
170+
}
171+
return set;
172+
}
173+
174+
private static void invalidateLookupCache(Set<CompilationUnit> affected, Set<CompilationUnit> live) {
175+
if (lookupCache.isEmpty()) {
176+
return;
177+
}
178+
179+
int evicted = 0;
180+
Iterator<Map.Entry<CacheKey, Object>> it = lookupCache.entrySet().iterator();
181+
while (it.hasNext()) {
182+
Map.Entry<CacheKey, Object> entry = it.next();
183+
Element element = entry.getKey().element;
184+
if (element == null) {
185+
continue;
186+
}
187+
188+
CompilationUnit owner = AttrNearest.nearestCompilationUnit(element);
189+
if (owner == null) {
190+
continue;
191+
}
192+
193+
boolean shouldEvict = affected.contains(owner)
194+
|| (live != null && !live.contains(owner));
195+
196+
if (shouldEvict) {
197+
it.remove();
198+
evicted++;
199+
}
200+
}
201+
202+
if (evicted > 0) {
203+
lookupStats.recordEviction(evicted);
204+
}
205+
}
206+
207+
private static void invalidateLocalStateCache(Set<CompilationUnit> affected, Set<CompilationUnit> live) {
208+
if (LOCAL_STATE_CACHE.isEmpty()) {
209+
return;
210+
}
211+
212+
int evicted = 0;
213+
Iterator<Map.Entry<Object, Object2ObjectOpenHashMap<ArgumentKey, LocalState>>> it =
214+
LOCAL_STATE_CACHE.entrySet().iterator();
215+
while (it.hasNext()) {
216+
Map.Entry<Object, Object2ObjectOpenHashMap<ArgumentKey, LocalState>> entry = it.next();
217+
Object key = entry.getKey();
218+
if (!(key instanceof ImFunction)) {
219+
continue;
220+
}
221+
222+
ImFunction function = (ImFunction) key;
223+
Element trace = function.attrTrace();
224+
if (trace == null) {
225+
continue;
226+
}
227+
228+
CompilationUnit owner = AttrNearest.nearestCompilationUnit(trace);
229+
if (owner == null) {
230+
continue;
231+
}
232+
233+
boolean shouldEvict = affected.contains(owner)
234+
|| (live != null && !live.contains(owner));
235+
236+
if (shouldEvict) {
237+
it.remove();
238+
evicted++;
239+
}
240+
}
241+
242+
if (evicted > 0) {
243+
localStateStats.recordEviction(evicted);
244+
}
245+
}
246+
247+
133248
public enum LookupType {
134249
FUNC, VAR, TYPE, PACKAGE, MEMBER_FUNC, MEMBER_VAR
135250
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void validate(Collection<CompilationUnit> toCheck) {
6464
visitedFunctions = 0;
6565
heavyFunctions.clear();
6666
heavyBlocks.clear();
67-
GlobalCaches.clearAll();
67+
GlobalCaches.invalidateFor(prog, toCheck);
6868

6969
lightValidation(toCheck);
7070

@@ -601,7 +601,7 @@ private int distanceToOwner(ClassDef start, ClassDef owner) {
601601

602602
private void visit(StmtExitwhen exitwhen) {
603603
Element parent = exitwhen.getParent();
604-
while (!(parent instanceof FunctionDefinition)) {
604+
while (parent != null && !(parent instanceof FunctionDefinition)) {
605605
if (parent instanceof StmtForEach) {
606606
StmtForEach forEach = (StmtForEach) parent;
607607
if (forEach.getIn().tryGetNameDef().attrIsVararg()) {

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/BugTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public void bug62_codearray() {
7171

7272
@Test
7373
public void bug61_break() {
74-
testAssertErrorsLines(false, "inside a loop",
74+
testAssertErrorsLines(false, "not allowed outside of loop",
7575
"package test",
7676
" init",
7777
" break",

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434

3535
import static org.hamcrest.CoreMatchers.containsString;
3636
import static org.hamcrest.MatcherAssert.assertThat;
37-
import static org.testng.Assert.assertEquals;
38-
import static org.testng.Assert.assertNotNull;
37+
import static org.testng.Assert.*;
3938

4039
public class ModelManagerTests {
4140

@@ -265,6 +264,116 @@ private Map<WFile, String> keepErrorsInMap(ModelManagerImpl manager) {
265264
return results;
266265
}
267266

267+
private CacheFixture setupCacheFixture(String projectName) throws IOException {
268+
GlobalCaches.clearAll();
269+
270+
File projectFolder = new File("./temp/" + projectName + "/");
271+
File wurstFolder = new File(projectFolder, "wurst");
272+
newCleanFolder(wurstFolder);
273+
274+
String packageA = string(
275+
"package A",
276+
"import B",
277+
"public function a() returns int",
278+
" return b()"
279+
);
280+
281+
String packageB = string(
282+
"package B",
283+
"import C",
284+
"public function b() returns int",
285+
" return c()"
286+
);
287+
288+
String packageC = string(
289+
"package C",
290+
"public function c() returns int",
291+
" return 1"
292+
);
293+
294+
String packageD = string(
295+
"package D",
296+
"public function d() returns int",
297+
" return 2"
298+
);
299+
300+
WFile fileA = WFile.create(new File(wurstFolder, "A.wurst"));
301+
WFile fileB = WFile.create(new File(wurstFolder, "B.wurst"));
302+
WFile fileC = WFile.create(new File(wurstFolder, "C.wurst"));
303+
WFile fileD = WFile.create(new File(wurstFolder, "D.wurst"));
304+
WFile fileWurst = WFile.create(new File(wurstFolder, "Wurst.wurst"));
305+
306+
writeFile(fileA, packageA);
307+
writeFile(fileB, packageB);
308+
writeFile(fileC, packageC);
309+
writeFile(fileD, packageD);
310+
writeFile(fileWurst, "package Wurst\n");
311+
312+
ModelManagerImpl manager = new ModelManagerImpl(projectFolder, new BufferManager());
313+
Map<WFile, String> results = keepErrorsInMap(manager);
314+
manager.buildProject();
315+
316+
CompilationUnit cuA = manager.getCompilationUnit(fileA);
317+
CompilationUnit cuB = manager.getCompilationUnit(fileB);
318+
CompilationUnit cuC = manager.getCompilationUnit(fileC);
319+
CompilationUnit cuD = manager.getCompilationUnit(fileD);
320+
321+
assertNotNull(cuA);
322+
assertNotNull(cuB);
323+
assertNotNull(cuC);
324+
assertNotNull(cuD);
325+
326+
WPackage pkgA = cuA.getPackages().get(0);
327+
WPackage pkgB = cuB.getPackages().get(0);
328+
WPackage pkgC = cuC.getPackages().get(0);
329+
WPackage pkgD = cuD.getPackages().get(0);
330+
331+
GlobalCaches.CacheKey keyA = new GlobalCaches.CacheKey(pkgA, "markerA", GlobalCaches.LookupType.FUNC);
332+
GlobalCaches.CacheKey keyB = new GlobalCaches.CacheKey(pkgB, "markerB", GlobalCaches.LookupType.FUNC);
333+
GlobalCaches.CacheKey keyC = new GlobalCaches.CacheKey(pkgC, "markerC", GlobalCaches.LookupType.FUNC);
334+
GlobalCaches.CacheKey keyD = new GlobalCaches.CacheKey(pkgD, "markerD", GlobalCaches.LookupType.FUNC);
335+
336+
GlobalCaches.lookupCache.put(keyA, Boolean.TRUE);
337+
GlobalCaches.lookupCache.put(keyB, Boolean.TRUE);
338+
GlobalCaches.lookupCache.put(keyC, Boolean.TRUE);
339+
GlobalCaches.lookupCache.put(keyD, Boolean.TRUE);
340+
341+
assertTrue(GlobalCaches.lookupCache.containsKey(keyA));
342+
assertTrue(GlobalCaches.lookupCache.containsKey(keyB));
343+
assertTrue(GlobalCaches.lookupCache.containsKey(keyC));
344+
assertTrue(GlobalCaches.lookupCache.containsKey(keyD));
345+
346+
return new CacheFixture(manager, results, fileA, fileB, fileC, fileD, keyA, keyB, keyC, keyD);
347+
}
348+
349+
private static final class CacheFixture {
350+
final ModelManagerImpl manager;
351+
final Map<WFile, String> results;
352+
final WFile fileA;
353+
final WFile fileB;
354+
final WFile fileC;
355+
final WFile fileD;
356+
final GlobalCaches.CacheKey keyA;
357+
final GlobalCaches.CacheKey keyB;
358+
final GlobalCaches.CacheKey keyC;
359+
final GlobalCaches.CacheKey keyD;
360+
361+
CacheFixture(ModelManagerImpl manager, Map<WFile, String> results, WFile fileA, WFile fileB,
362+
WFile fileC, WFile fileD, GlobalCaches.CacheKey keyA, GlobalCaches.CacheKey keyB,
363+
GlobalCaches.CacheKey keyC, GlobalCaches.CacheKey keyD) {
364+
this.manager = manager;
365+
this.results = results;
366+
this.fileA = fileA;
367+
this.fileB = fileB;
368+
this.fileC = fileC;
369+
this.fileD = fileD;
370+
this.keyA = keyA;
371+
this.keyB = keyB;
372+
this.keyC = keyC;
373+
this.keyD = keyD;
374+
}
375+
}
376+
268377
private void newCleanFolder(File f) throws IOException {
269378
FileUtils.deleteRecursively(f);
270379
Files.createDirectories(f.toPath());
@@ -355,6 +464,49 @@ public void visit(ClassDef c) {
355464

356465
}
357466

467+
@Test
468+
public void selectiveCacheInvalidationSkipsUnaffectedUnits() throws IOException {
469+
CacheFixture fixture = setupCacheFixture("cacheInvalidationProject1");
470+
fixture.results.clear();
471+
472+
String packageBUpdated = string(
473+
"package B",
474+
"import C",
475+
"public function b() returns int",
476+
" return c() + 1"
477+
);
478+
479+
ModelManager.Changes changes = fixture.manager.syncCompilationUnitContent(fixture.fileB, packageBUpdated);
480+
fixture.manager.reconcile(changes);
481+
482+
assertEquals(fixture.results.keySet(), ImmutableSet.of(fixture.fileA, fixture.fileB));
483+
assertFalse(GlobalCaches.lookupCache.containsKey(fixture.keyA));
484+
assertFalse(GlobalCaches.lookupCache.containsKey(fixture.keyB));
485+
assertTrue(GlobalCaches.lookupCache.containsKey(fixture.keyC));
486+
assertTrue(GlobalCaches.lookupCache.containsKey(fixture.keyD));
487+
}
488+
489+
@Test
490+
public void selectiveCacheInvalidationCoversTransitiveDependents() throws IOException {
491+
CacheFixture fixture = setupCacheFixture("cacheInvalidationProject2");
492+
fixture.results.clear();
493+
494+
String packageCUpdated = string(
495+
"package C",
496+
"public function c() returns int",
497+
" return 2"
498+
);
499+
500+
ModelManager.Changes changes = fixture.manager.syncCompilationUnitContent(fixture.fileC, packageCUpdated);
501+
fixture.manager.reconcile(changes);
502+
503+
assertEquals(fixture.results.keySet(), ImmutableSet.of(fixture.fileA, fixture.fileB, fixture.fileC));
504+
assertFalse(GlobalCaches.lookupCache.containsKey(fixture.keyA));
505+
assertFalse(GlobalCaches.lookupCache.containsKey(fixture.keyB));
506+
assertFalse(GlobalCaches.lookupCache.containsKey(fixture.keyC));
507+
assertTrue(GlobalCaches.lookupCache.containsKey(fixture.keyD));
508+
}
509+
358510
@Test
359511
public void changeModuleAbstractMethod() throws IOException {
360512
File projectFolder = new File("./temp/testProject2/");

0 commit comments

Comments
 (0)