Skip to content
Open
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
4 changes: 4 additions & 0 deletions de.peeeq.wurstscript/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* text=auto
*.sh text eol=lf
gradlew text eol=lf
*.bat text eol=crlf
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ public void checkProg(WurstModel root, Collection<CompilationUnit> toCheck) {

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

// compute the flow attributes
for (CompilationUnit cu : toCheck) {
WurstValidator.computeFlowAttributes(cu);
}


// validate the resource:
WurstValidator validator = new WurstValidator(root);
validator.validate(toCheck);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package de.peeeq.wurstscript.validation;

import de.peeeq.wurstscript.ast.CompilationUnit;
import de.peeeq.wurstscript.ast.Element;
import de.peeeq.wurstscript.ast.WurstModel;
import de.peeeq.wurstscript.attributes.AttrNearest;
import de.peeeq.wurstscript.intermediatelang.ILconst;
import de.peeeq.wurstscript.intermediatelang.interpreter.LocalState;
import de.peeeq.wurstscript.jassIm.ImFunction;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;

import java.util.Arrays;
import java.util.Map;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

// Expose static fields only if you already have them there; otherwise, just clear via dedicated methods.
Expand Down Expand Up @@ -96,7 +99,7 @@ public boolean equals(Object o) {

public enum Mode {TEST_ISOLATED, DEV_PERSISTENT}

private static volatile Mode mode = Mode.DEV_PERSISTENT;
public static volatile Mode mode = Mode.DEV_PERSISTENT;

public static void setMode(Mode m) {
mode = m;
Expand Down Expand Up @@ -144,6 +147,158 @@ public static void clearAll() {
lookupCache.clear();
}

/**
* Evict cache entries that are tied to any of the given compilation units.
*
* <p>The validator replaces only a subset of units on incremental runs. Instead of
* purging the full global caches (which would force re-computation for every file), we
* walk both caches and drop entries whose owner element/function belongs to one of the
* affected compilation units.</p>
*/
public static void invalidateFor(WurstModel model, Collection<CompilationUnit> changedUnits) {
if (mode == Mode.TEST_ISOLATED) {
clearAll();
return;
}

Set<CompilationUnit> affected = toIdentitySet(changedUnits);
Set<CompilationUnit> live = model == null ? null : toIdentitySet(model);

if (affected.isEmpty() && (live == null || live.isEmpty())) {
return;
}

invalidateLookupCache(affected, live);
invalidateLocalStateCache(affected, live);
invalidateLocalStateNoArgCache(affected, live);
}

private static Set<CompilationUnit> toIdentitySet(Iterable<CompilationUnit> units) {
Set<CompilationUnit> set = Collections.newSetFromMap(new IdentityHashMap<>());
if (units == null) {
return set;
}
for (CompilationUnit cu : units) {
if (cu != null) {
set.add(cu);
}
}
return set;
}

private static void invalidateLookupCache(Set<CompilationUnit> affected, Set<CompilationUnit> live) {
if (lookupCache.isEmpty()) {
return;
}

int evicted = 0;
Iterator<Map.Entry<CacheKey, Object>> it = lookupCache.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CacheKey, Object> entry = it.next();
Element element = entry.getKey().element;
if (element == null) {
continue;
}

CompilationUnit owner = AttrNearest.nearestCompilationUnit(element);
if (owner == null) {
continue;
}

boolean shouldEvict = affected.contains(owner)
|| (live != null && !live.contains(owner));

if (shouldEvict) {
it.remove();
evicted++;
}
}

if (evicted > 0) {
lookupStats.recordEviction(evicted);
}
}

private static void invalidateLocalStateCache(Set<CompilationUnit> affected, Set<CompilationUnit> live) {
if (LOCAL_STATE_CACHE.isEmpty()) {
return;
}

int evicted = 0;
Iterator<Map.Entry<Object, Object2ObjectOpenHashMap<ArgumentKey, LocalState>>> it =
LOCAL_STATE_CACHE.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Object, Object2ObjectOpenHashMap<ArgumentKey, LocalState>> entry = it.next();
Object key = entry.getKey();
if (!(key instanceof ImFunction)) {
continue;
}

ImFunction function = (ImFunction) key;
Element trace = function.attrTrace();
if (trace == null) {
continue;
}

CompilationUnit owner = AttrNearest.nearestCompilationUnit(trace);
if (owner == null) {
continue;
}

boolean shouldEvict = affected.contains(owner)
|| (live != null && !live.contains(owner));

if (shouldEvict) {
it.remove();
evicted++;
}
}

if (evicted > 0) {
localStateStats.recordEviction(evicted);
}
}

private static void invalidateLocalStateNoArgCache(Set<CompilationUnit> affected, Set<CompilationUnit> live) {
if (LOCAL_STATE_NOARG_CACHE.isEmpty()) {
return;
}

int evicted = 0;
Iterator<Map.Entry<Object, LocalState>> it = LOCAL_STATE_NOARG_CACHE.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Object, LocalState> entry = it.next();
Object key = entry.getKey();
if (!(key instanceof ImFunction)) {
continue;
}

ImFunction function = (ImFunction) key;
Element trace = function.attrTrace();
if (trace == null) {
continue;
}

CompilationUnit owner = AttrNearest.nearestCompilationUnit(trace);
if (owner == null) {
continue;
}

boolean shouldEvict = affected.contains(owner)
|| (live != null && !live.contains(owner));

if (shouldEvict) {
it.remove();
evicted++;
}
}

if (evicted > 0) {
localStateStats.recordEviction(evicted);
}
}


public enum LookupType {
FUNC, VAR, TYPE, PACKAGE, MEMBER_FUNC, MEMBER_VAR
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public void validate(Collection<CompilationUnit> toCheck) {
visitedFunctions = 0;
heavyFunctions.clear();
heavyBlocks.clear();
GlobalCaches.clearAll();
GlobalCaches.invalidateFor(prog, toCheck);

lightValidation(toCheck);

Expand Down Expand Up @@ -607,7 +607,7 @@ private int distanceToOwner(ClassDef start, ClassDef owner) {

private void visit(StmtExitwhen exitwhen) {
Element parent = exitwhen.getParent();
while (!(parent instanceof FunctionDefinition)) {
while (parent != null && !(parent instanceof FunctionDefinition)) {
if (parent instanceof StmtForEach) {
StmtForEach forEach = (StmtForEach) parent;
if (forEach.getIn().tryGetNameDef().attrIsVararg()) {
Expand All @@ -624,7 +624,7 @@ private void visit(StmtExitwhen exitwhen) {

private void visit(StmtContinue stmtContinue) {
Element parent = stmtContinue.getParent();
while (!(parent instanceof FunctionDefinition)) {
while (parent != null && !(parent instanceof FunctionDefinition)) {
if (parent instanceof StmtForEach) {
StmtForEach forEach = (StmtForEach) parent;
if (forEach.getIn().tryGetNameDef().attrIsVararg()) {
Expand All @@ -636,7 +636,7 @@ private void visit(StmtContinue stmtContinue) {
}
parent = parent.getParent();
}
stmtContinue.addError("Continue is not allowed outside of loop statements.");
stmtContinue.addError("Continue statements must be used inside a loop.");
}

private void checkTupleDef(TupleDef e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void bug62_codearray() {

@Test
public void bug61_break() {
testAssertErrorsLines(false, "inside a loop",
testAssertErrorsLines(false, "not allowed outside of loop",
"package test",
" init",
" break",
Expand Down
Loading
Loading