• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wurstscript / WurstScript / 267

29 Sep 2025 09:17AM UTC coverage: 62.258% (+0.04%) from 62.222%
267

Pull #1096

circleci

web-flow
Update de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Pull Request #1096: Perf improvements

17484 of 28083 relevant lines covered (62.26%)

0.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.37
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ILInterpreter.java
1
package de.peeeq.wurstscript.intermediatelang.interpreter;
2

3
import de.peeeq.wurstio.jassinterpreter.DebugPrintError;
4
import de.peeeq.wurstio.jassinterpreter.InterpreterException;
5
import de.peeeq.wurstio.jassinterpreter.VarargArray;
6
import de.peeeq.wurstscript.ast.Annotation;
7
import de.peeeq.wurstscript.ast.HasModifier;
8
import de.peeeq.wurstscript.ast.Modifier;
9
import de.peeeq.wurstscript.gui.WurstGui;
10
import de.peeeq.wurstscript.intermediatelang.*;
11
import de.peeeq.wurstscript.jassIm.*;
12
import de.peeeq.wurstscript.jassinterpreter.ReturnException;
13
import de.peeeq.wurstscript.jassinterpreter.TestFailException;
14
import de.peeeq.wurstscript.jassinterpreter.TestSuccessException;
15
import de.peeeq.wurstscript.parser.WPos;
16
import de.peeeq.wurstscript.translation.imtranslation.FunctionFlagEnum;
17
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
18
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
19
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
20
import org.eclipse.jdt.annotation.Nullable;
21

22
import java.io.File;
23
import java.util.Arrays;
24
import java.util.Optional;
25
import java.util.stream.Collectors;
26

27
import static de.peeeq.wurstscript.translation.imoptimizer.UselessFunctionCallsRemover.isFunctionPure;
28

29
public class ILInterpreter implements AbstractInterpreter {
30
    private ImProg prog;
31
    private static boolean cache = false;
1✔
32
    private final ProgramState globalState;
33
    private final TimerMockHandler timerMockHandler = new TimerMockHandler();
1✔
34

35
    public ILInterpreter(ImProg prog, WurstGui gui, Optional<File> mapFile, ProgramState globalState, boolean cache) {
1✔
36
        this.prog = prog;
1✔
37
        this.globalState = globalState;
1✔
38
        ILInterpreter.cache = cache;
1✔
39
        globalState.addNativeProvider(new BuiltinFuncs(globalState));
1✔
40
//        globalState.addNativeProvider(new NativeFunctions());
41
    }
1✔
42

43
    public ILInterpreter(ImProg prog, WurstGui gui, Optional<File> mapFile, boolean isCompiletime, boolean cache) {
44
        this(prog, gui, mapFile, new ProgramState(gui, prog, isCompiletime), cache);
1✔
45
    }
1✔
46

47
    public static LocalState runFunc(ProgramState globalState, ImFunction f, @Nullable Element caller,
48
                                     ILconst... args) {
49
        if (Thread.currentThread().isInterrupted()) {
1✔
50
            throw new InterpreterException(globalState, "Execution interrupted");
×
51
        }
52
        try {
53
            if (f.hasFlag(FunctionFlagEnum.IS_VARARG)) {
1✔
54
                // for vararg functions, rewrite args and put last argument
55
                ILconst[] newArgs = new ILconst[f.getParameters().size()];
1✔
56
                if (newArgs.length - 1 >= 0) System.arraycopy(args, 0, newArgs, 0, newArgs.length - 1);
1✔
57

58
                ILconst[] varargArray = new ILconst[1 + args.length - newArgs.length];
1✔
59
                for (int i = newArgs.length - 1, j = 0; i < args.length; i++, j++) {
1✔
60
                    varargArray[j] = args[i];
1✔
61
                }
62
                newArgs[newArgs.length - 1] = new VarargArray(varargArray);
1✔
63
                args = newArgs;
1✔
64
            }
65

66
            if (f.getParameters().size() != args.length) {
1✔
67
                throw new Error("wrong number of parameters when calling func " + f.getName() + "(" +
×
68
                    Arrays.stream(args).map(Object::toString).collect(Collectors.joining(", ")) + ")");
×
69
            }
70

71
            for (int i = 0; i < f.getParameters().size(); i++) {
1✔
72
                // TODO could do typecheck here
73
                args[i] = adjustTypeOfConstant(args[i], f.getParameters().get(i).getType());
1✔
74
            }
75

76
            if (isCompiletimeNative(f)) {
1✔
77
                return runBuiltinFunction(globalState, f, args);
1✔
78
            }
79

80
            if (f.isNative()) {
1✔
81
                return runBuiltinFunction(globalState, f, args);
1✔
82
            }
83

84
            LocalState localState = new LocalState();
1✔
85
            int i = 0;
1✔
86
            for (ImVar p : f.getParameters()) {
1✔
87
                localState.setVal(p, args[i]);
1✔
88
                i++;
1✔
89
            }
1✔
90

91
            if (f.getBody().isEmpty()) {
1✔
92
                return localState.setReturnVal(ILconstNull.instance());
1✔
93
            } else {
94
                globalState.setLastStatement(f.getBody().get(0));
1✔
95
            }
96

97
            globalState.pushStackframe(f, args, (caller == null ? f : caller).attrTrace().attrErrorPos());
1✔
98

99
            try {
100
                f.getBody().runStatements(globalState, localState);
1✔
101
                globalState.popStackframe();
1✔
102
            } catch (ReturnException e) {
1✔
103
                globalState.popStackframe();
1✔
104
                ILconst retVal = e.getVal();
1✔
105
                retVal = adjustTypeOfConstant(retVal, f.getReturnType());
1✔
106
                return localState.setReturnVal(retVal);
1✔
107
            }
1✔
108
            if (f.getReturnType() instanceof ImVoid) {
1✔
109
                return localState;
1✔
110
            }
111
            throw new InterpreterException("function " + f.getName() + " did not return any value...");
×
112
        } catch (InterpreterException e) {
1✔
113
            String msg = buildStacktrace(globalState, e);
1✔
114
            e.setStacktrace(msg);
1✔
115
            e.setTrace(getTrace(globalState, f));
1✔
116
            throw e;
1✔
117
        } catch (TestSuccessException | TestFailException | DebugPrintError e) {
1✔
118
            throw e;
1✔
119
        } catch (Throwable e) {
×
120
            String msg = buildStacktrace(globalState, e);
×
121
            de.peeeq.wurstscript.ast.Element trace = getTrace(globalState, f);
×
122
            throw new InterpreterException(trace, "You encountered a bug in the interpreter: " + e, e).setStacktrace(msg);
×
123
        }
124
    }
125

126
    public static de.peeeq.wurstscript.ast.Element getTrace(ProgramState globalState, ImFunction f) {
127
        Element lastStatement = globalState.getLastStatement();
1✔
128
        return lastStatement == null ? f.attrTrace() : lastStatement.attrTrace();
1✔
129
    }
130

131
    public static String buildStacktrace(ProgramState globalState, Throwable e) {
132
        StringBuilder err = new StringBuilder();
1✔
133
        try {
134
            WPos src = globalState.getLastStatement().attrTrace().attrSource();
1✔
135
            err.append("at : ").append(new File(src.getFile()).getName()).append(", line ").append(src.getLine()).append("\n");
1✔
136
        } catch (Exception _e) {
×
137
            // ignore
138
        }
1✔
139
        globalState.getStackFrames().appendTo(err);
1✔
140
        return err.toString();
1✔
141
    }
142

143
    @SuppressWarnings("null")
144
    private static ILconst adjustTypeOfConstant(@Nullable ILconst retVal, ImType expectedType) {
145
        if (retVal instanceof ILconstInt && isTypeReal(expectedType)) {
1✔
146
            ILconstInt retValI = (ILconstInt) retVal;
1✔
147
            retVal = new ILconstReal(retValI.getVal());
1✔
148
        }
149
        return retVal;
1✔
150
    }
151

152
    private static boolean isTypeReal(ImType t) {
153
        if (t instanceof ImSimpleType) {
1✔
154
            ImSimpleType st = (ImSimpleType) t;
1✔
155
            return st.getTypename().equals("real");
1✔
156
        }
157
        return false;
1✔
158
    }
159

160

161
    private static final Object2ObjectOpenHashMap<ImFunction, Int2ObjectLinkedOpenHashMap<LocalState>> localStateCache =
1✔
162
        new Object2ObjectOpenHashMap<>();
163

164
    // Cap per-function cache size to avoid unbounded growth (tune as needed)
165
    private static final int MAX_CACHE_PER_FUNC = 2048;
166

167
    // If LocalState is immutable-or-treated-as-readonly when used as "no return":
168
    // Prefer a TRUE singleton to avoid allocating huge internal maps for "void" cases.
169
    private static final LocalState EMPTY_LOCAL_STATE = new LocalState();
1✔
170

171
    // -------------- public entry with varargs kept for API compatibility --------------
172
    private static LocalState runBuiltinFunction(ProgramState globalState, ImFunction f, ILconst... args) {
173
        // Delegate to the array overload to avoid double-allocations.
174
        return runBuiltinFunction(globalState, f, args, /*isVarargs*/ true);
1✔
175
    }
176

177
    // -------------- internal overload that can be called with an existing ILconst[] --------------
178
    private static LocalState runBuiltinFunction(ProgramState globalState, ImFunction f, ILconst[] args, boolean isVarargs) {
179
        // Cache purity + name once
180
        final String fname = f.getName();
1✔
181
        final boolean pure = cache && isFunctionPure(fname);
1✔
182

183
        // Fast, zero-allocation rolling hash for args (no Object[] boxing like Objects.hash)
184
        final int combinedHash = pure ? fastHashArgs(args) : 0;
1✔
185

186
        if (pure) {
1✔
187
            final Int2ObjectLinkedOpenHashMap<LocalState> perFn =
×
188
                localStateCache.get(f);
×
189
            if (perFn != null) {
×
190
                final LocalState cached = perFn.get(combinedHash);
×
191
                if (cached != null) {
×
192
                    return cached;
×
193
                }
194
            }
195
        }
196

197
        // Build error text lazily (only if we actually get exceptions)
198
        StringBuilder errors = null;
1✔
199

200
        for (NativesProvider natives : globalState.getNativeProviders()) {
1✔
201
            try {
202
                // Invoke native; ideally you cache method handles per name elsewhere.
203
                final LocalState localState = new LocalState(natives.invoke(fname, args));
1✔
204

205
                if (pure) {
1✔
206
                    // insert into per-function cache with bounded size
207
                    Int2ObjectLinkedOpenHashMap<LocalState> perFn =
×
208
                        localStateCache.get(f);
×
209
                    if (perFn == null) {
×
210
                        perFn = new Int2ObjectLinkedOpenHashMap<>(16);
×
211
                        localStateCache.put(f, perFn);
×
212
                    }
213
                    perFn.put(combinedHash, localState);
×
214
                    if (perFn.size() > MAX_CACHE_PER_FUNC) {
×
215
                        // evict eldest (insertion order) to bound memory
216
                        final int eldest = perFn.firstIntKey();
×
217
                        perFn.remove(eldest);
×
218
                    }
219
                }
220

221
                return localState;
1✔
222

223
            } catch (NoSuchNativeException e) {
1✔
224
                if (errors == null) errors = new StringBuilder(128);
1✔
225
                errors.append('\n').append(e.getMessage());
1✔
226
                // keep trying next provider
227
            }
228
        }
1✔
229

230
        // If we reach here, none of the providers handled it
231
        if (errors == null) errors = new StringBuilder(64);
×
232
        errors.insert(0, "function ").append(fname).append(" cannot be used from the Wurst interpreter.\n");
×
233
        globalState.compilationError(errors.toString());
×
234

235
        // Return a lightweight state
236
        if (f.getReturnType() instanceof ImVoid) {
×
237
            return EMPTY_LOCAL_STATE;
×
238
        }
239
        // If you can, pass a lightweight state to evaluate default (avoid allocating a heavy LocalState)
240
        final ILconst returnValue = ImHelper.defaultValueForComplexType(f.getReturnType())
×
241
            .evaluate(globalState, EMPTY_LOCAL_STATE);
×
242
        return new LocalState(returnValue);
×
243
    }
244

245
    /** Zero-allocation combined hash for ILconst[] (order-sensitive). */
246
    private static int fastHashArgs(ILconst[] args) {
247
        int h = 1;
×
248
        for (final ILconst a : args) {
×
249
            // If ILconst has a stable, cheap hash (recommended), rely on it.
250
            // If not, consider a dedicated method (e.g., a.fastHash()).
251
            h = 31 * h + (a == null ? 0 : a.hashCode());
×
252
        }
253
        // Spread bits a little to reduce clustering (optional)
254
        h ^= (h >>> 16);
×
255
        return h;
×
256
    }
257

258

259
    private static boolean isCompiletimeNative(ImFunction f) {
260
        if (f.getTrace() instanceof HasModifier) {
1✔
261
            HasModifier f2 = (HasModifier) f.getTrace();
1✔
262
            for (Modifier m : f2.getModifiers()) {
1✔
263
                if (m instanceof Annotation) {
1✔
264
                    Annotation annotation = (Annotation) m;
1✔
265
                    if (annotation.getAnnotationType().equals("@compiletimenative")) {
1✔
266
                        return true;
1✔
267
                    }
268
                }
269
            }
1✔
270
        }
271
        return false;
1✔
272
    }
273

274
    public LocalState executeFunction(String funcName, @Nullable Element trace) {
275
        globalState.resetStackframes();
1✔
276
        for (ImFunction f : prog.getFunctions()) {
1✔
277
            if (f.getName().equals(funcName)) {
1✔
278
                return runFunc(globalState, f, trace);
×
279
            }
280
        }
1✔
281

282
        throw new Error("no function with name " + funcName + "was found.");
×
283
    }
284

285
    public void runVoidFunc(ImFunction f, @Nullable Element trace) {
286
        globalState.resetStackframes();
1✔
287
        ILconst[] args = {};
1✔
288
        if (!f.getParameters().isEmpty()) {
1✔
289
            // this should only happen because of added stacktrace parameter
290
            args = new ILconstString[]{new ILconstString("initial call")};
1✔
291
        }
292
        runFunc(globalState, f, trace, args);
1✔
293
    }
1✔
294

295
    public Element getLastStatement() {
296
        return globalState.getLastStatement();
×
297
    }
298

299
    public void writebackGlobalState(boolean injectObjects) {
300
        globalState.writeBack(injectObjects);
1✔
301

302
    }
1✔
303

304
    public ProgramState getGlobalState() {
305
        return globalState;
1✔
306
    }
307

308
    public void addNativeProvider(NativesProvider np) {
309
        globalState.addNativeProvider(np);
1✔
310
    }
1✔
311

312
    public void setProgram(ImProg imProg) {
313
        this.prog = imProg;
×
314
        this.getGlobalState().setProg(imProg);
×
315
        globalState.resetStackframes();
×
316
    }
×
317

318
    public ProgramState.StackTrace getStackFrames() {
319
        return globalState.getStackFrames();
×
320

321
    }
322

323
    @Override
324
    public void runFuncRef(ILconstFuncRef obj, @Nullable Element trace) {
325
        runVoidFunc(obj.getFunc(), trace);
1✔
326
    }
1✔
327

328
    @Override
329
    public TimerMockHandler getTimerMockHandler() {
330
        return timerMockHandler;
1✔
331
    }
332

333
    @Override
334
    public void completeTimers() {
335
        timerMockHandler.completeTimers();
1✔
336
    }
1✔
337

338
    @Override
339
    public ImProg getImProg() {
340
        return prog;
1✔
341
    }
342

343
    @Override
344
    public int getInstanceCount(int val) {
345
        return (int) globalState.getAllObjects()
1✔
346
            .stream()
1✔
347
            .filter(o -> o.getType().getClassDef().attrTypeId() == val)
1✔
348
            .filter(o -> !o.isDestroyed())
1✔
349
            .count();
1✔
350
    }
351

352
    @Override
353
    public int getMaxInstanceCount(int val) {
354
        return (int) globalState.getAllObjects()
1✔
355
            .stream()
1✔
356
            .filter(o -> o.getType().getClassDef().attrTypeId() == val)
1✔
357
            .count();
1✔
358
    }
359
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc