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 @@ -629,7 +629,6 @@ private void doTypeCheckPartial(WurstGui gui, List<WFile> toCheckFilenames, Set<

@Override
public void reconcile(Changes changes) {
GlobalCaches.clearAll();
WurstModel model2 = model;
if (model2 == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,37 @@ public final class AttrModuleInstanciations {
private AttrModuleInstanciations() {}

public static @Nullable ModuleDef getModuleOrigin(ModuleInstanciation mi) {
// NOTE: For ModuleInstanciation the "name" used for resolution has historically been getName().
// Keep this to preserve prior behavior.
// A ModuleInstanciation's parent is always ModuleInstanciations (the list),
// whose parent is either a ClassDef or a ModuleDef.
// We must resolve the module name through that owner's scope.

final String name = mi.getName();

// 1) Normal path: resolve relative to the lexical parent (old behavior)
final Element parent = mi.getParent();
if (parent != null) {
TypeDef def = parent.lookupType(name, /*showErrors*/ false);
if (def instanceof ModuleDef) {
return (ModuleDef) def;
}
// Attached but not found -> keep the old error
mi.addError("Could not find module origin for " + Utils.printElement(mi));
Element parent = mi.getParent(); // This is ModuleInstanciations (plural)
if (parent == null) {
// Detached node during incremental compilation - this is transient.
// Don't emit errors; return null and let the next full pass resolve it.
return null;
}

// Get the actual owner (ClassDef or ModuleDef)
Element owner = parent.getParent();
if (owner == null) {
// Still detached at the owner level
return null;
}

// 2) Detached during incremental build: try the nearest attached scope
final WScope scope = mi.attrNearestScope();
if (scope != null) {
TypeDef def = scope.lookupType(name, /*showErrors*/ false);
if (def instanceof ModuleDef) {
return (ModuleDef) def;
}
// Resolve through the owner's scope
TypeDef def = owner.lookupType(name, /*showErrors*/ false);
if (def instanceof ModuleDef) {
return (ModuleDef) def;
}

// Only emit error if we're fully attached (not in a transient state)
if (mi.getModel() != null) {
mi.addError("Could not find module origin for " + Utils.printElement(mi));
}

// 3) Still not found and we're detached: this can be a transient state,
// so don't emit an error here. Return null and let callers handle gracefully.
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,14 @@ public static ImmutableCollection<FunctionSignature> calculate(ExprMemberMethod
if (recv != null) {
VariableBinding m = leftType.matchAgainstSupertype(
recv, mm, sig.getMapping(), VariablePosition.RIGHT);
if (m == null) {
// Should not happen; lookupMemberFuncs already checked. Skip defensively.
continue;

// IMPORTANT:
// For members injected via `use module`, the receiver can be a synthetic/module `thistype`
// that is not directly comparable here (especially during incremental builds).
// Do NOT drop the candidate if binding fails; keep it and let arg matching rank it later.
if (m != null) {
sig = sig.setTypeArgs(mm, m);
}
sig = sig.setTypeArgs(mm, m);
}

// Apply explicit type args from the call-site (e.g., c.foo<T,...>(...))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import de.peeeq.wurstscript.jassIm.ImExprOpt;
import de.peeeq.wurstscript.jassIm.ImType;
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
import de.peeeq.wurstscript.validation.GlobalCaches;
import io.vavr.control.Option;
import org.eclipse.jdt.annotation.Nullable;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
package de.peeeq.wurstscript.validation;

import de.peeeq.wurstscript.ast.Element;
import de.peeeq.wurstscript.attributes.names.NameResolution;
import de.peeeq.wurstscript.intermediatelang.ILconst;
import de.peeeq.wurstscript.intermediatelang.interpreter.LocalState;
import de.peeeq.wurstscript.types.WurstType;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;

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

// Expose static fields only if you already have them there; otherwise, just clear via dedicated methods.
public final class GlobalCaches {

// Statistics tracking
public static class CacheStats {
final AtomicLong hits = new AtomicLong();
final AtomicLong misses = new AtomicLong();
final AtomicLong evictions = new AtomicLong();
final String name;

CacheStats(String name) {
this.name = name;
}

void recordHit() {
hits.incrementAndGet();
}

void recordMiss() {
misses.incrementAndGet();
}

void recordEviction(int count) {
evictions.addAndGet(count);
}

double hitRate() {
long h = hits.get();
long m = misses.get();
long total = h + m;
return total == 0 ? 0.0 : (double) h / total;
}

@Override
public String toString() {
return String.format("%s: hits=%d, misses=%d, hitRate=%.2f%%, evictions=%d",
name, hits.get(), misses.get(), hitRate() * 100, evictions.get());
}
}

private static final CacheStats lookupStats = new CacheStats("LookupCache");
private static final CacheStats localStateStats = new CacheStats("LocalStateCache");

// Optimized ArgumentKey that minimizes allocation overhead
public static final class ArgumentKey {
private final ILconst[] args;
Expand Down Expand Up @@ -48,21 +85,48 @@ public boolean equals(Object o) {
}
}

public enum Mode { TEST_ISOLATED, DEV_PERSISTENT }
public enum Mode {TEST_ISOLATED, DEV_PERSISTENT}

private static volatile Mode mode = Mode.DEV_PERSISTENT;

public static void setMode(Mode m) { mode = m; }
public static Mode mode() { return mode; }
public static void setMode(Mode m) {
mode = m;
}

private GlobalCaches() {}
public static Mode mode() {
return mode;
}

public static final Object2ObjectOpenHashMap<Object, Object2ObjectOpenHashMap<ArgumentKey, LocalState>> LOCAL_STATE_CACHE = new Object2ObjectOpenHashMap<>();
public static final Reference2ObjectOpenHashMap<WurstType, Reference2BooleanOpenHashMap<WurstType>> SUBTYPE_MEMO = new Reference2ObjectOpenHashMap<>();
private GlobalCaches() {
}

/** Call this between tests (and after each compile) */
// Wrapped caches with statistics
public static final Object2ObjectOpenHashMap<Object, Object2ObjectOpenHashMap<ArgumentKey, LocalState>> LOCAL_STATE_CACHE =
new Object2ObjectOpenHashMap<Object, Object2ObjectOpenHashMap<ArgumentKey, LocalState>>() {
@Override
public Object2ObjectOpenHashMap<ArgumentKey, LocalState> get(Object key) {
Object2ObjectOpenHashMap<ArgumentKey, LocalState> result = super.get(key);
if (result != null) {
localStateStats.recordHit();
} else {
localStateStats.recordMiss();
}
return result;
}

@Override
public void clear() {
localStateStats.recordEviction(size());
super.clear();
}
};


/**
* Call this between tests (and after each compile)
*/
public static void clearAll() {
LOCAL_STATE_CACHE.clear();
SUBTYPE_MEMO.clear();
lookupCache.clear();
}

Expand Down Expand Up @@ -95,5 +159,59 @@ public int hashCode() {
}
}

public static final Map<CacheKey, Object> lookupCache = new Object2ObjectOpenHashMap<>();
public static final Map<CacheKey, Object> lookupCache = new Object2ObjectOpenHashMap<CacheKey, Object>() {
@Override
public Object get(Object key) {
Object result = super.get(key);
if (result != null) {
lookupStats.recordHit();
} else {
lookupStats.recordMiss();
}
return result;
}

@Override
public Object put(CacheKey key, Object value) {
// Note: put returns old value, null if new entry
Object old = super.put(key, value);
if (old == null) {
// New entry, the miss was already recorded in get()
}
return old;
}

@Override
public void clear() {
lookupStats.recordEviction(size());
super.clear();
}
};

// Statistics methods
public static void printStats() {
System.out.println("=== GlobalCaches Statistics ===");
System.out.println(lookupStats);
System.out.println(localStateStats);
System.out.println("Current sizes: lookup=" + lookupCache.size() +
", localState=" + LOCAL_STATE_CACHE.size());
System.out.println("==============================");
}

public static void resetStats() {
lookupStats.hits.set(0);
lookupStats.misses.set(0);
lookupStats.evictions.set(0);
localStateStats.hits.set(0);
localStateStats.misses.set(0);
localStateStats.evictions.set(0);
}

public static CacheStats getLookupStats() {
return lookupStats;
}

public static CacheStats getLocalStateStats() {
return localStateStats;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.stream.Collectors;

import static de.peeeq.wurstscript.attributes.SmallHelpers.superArgs;
import static de.peeeq.wurstscript.validation.GlobalCaches.SUBTYPE_MEMO;

/**
* this class validates a wurstscript program
Expand Down Expand Up @@ -65,7 +64,7 @@ public void validate(Collection<CompilationUnit> toCheck) {
visitedFunctions = 0;
heavyFunctions.clear();
heavyBlocks.clear();
SUBTYPE_MEMO.clear();
GlobalCaches.clearAll();

lightValidation(toCheck);

Expand Down Expand Up @@ -1614,26 +1613,12 @@ private static boolean isStrictSuperclassOf(ClassDef sup, ClassDef sub) {
return false;
}

private static boolean isSubtypeCached(WurstType actual, WurstType expected, Annotation site) {
private static boolean isSubtypeCached(WurstType actual, WurstType expected, Element site) {
// Fast paths first
if (actual == expected) return true;
// quick structural equality before expensive check
if (actual.equalsType(expected, site)) return true;

Reference2BooleanOpenHashMap<WurstType> inner = SUBTYPE_MEMO.get(actual);
if (inner != null && inner.containsKey(expected)) {
return inner.getBoolean(expected);
}

boolean res = actual.isSubtypeOf(expected, site);

if (inner == null) {
inner = new Reference2BooleanOpenHashMap<>();
SUBTYPE_MEMO.put(actual, inner);
}
if (!inner.containsKey(expected)) {
inner.put(expected, res);
}
return res;
return actual.isSubtypeOf(expected, site);
}

private void checkAnnotation(Annotation a) {
Expand Down
Loading
Loading