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

wurstscript / WurstScript / 265

29 Sep 2025 09:00AM UTC coverage: 62.244% (+0.02%) from 62.222%
265

Pull #1096

circleci

Frotty
restore determinism, fix tests
Pull Request #1096: Perf improvements

17480 of 28083 relevant lines covered (62.24%)

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.Object2ObjectLinkedOpenHashMap;
20
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
21
import org.eclipse.jdt.annotation.Nullable;
22

23
import java.io.File;
24
import java.util.Arrays;
25
import java.util.LinkedHashMap;
26
import java.util.Objects;
27
import java.util.Optional;
28
import java.util.stream.Collectors;
29

30
import static de.peeeq.wurstscript.translation.imoptimizer.UselessFunctionCallsRemover.isFunctionPure;
31

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

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

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

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

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

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

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

79
            if (isCompiletimeNative(f)) {
1✔
80
                return runBuiltinFunction(globalState, f, args);
1✔
81
            }
82

83
            if (f.isNative()) {
1✔
84
                return runBuiltinFunction(globalState, f, args);
1✔
85
            }
86

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

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

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

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

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

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

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

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

163

164
    private static final Object2ObjectOpenHashMap<ImFunction, Int2ObjectLinkedOpenHashMap<LocalState>> localStateCache =
1✔
165
        new Object2ObjectOpenHashMap<>();
166

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

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

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

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

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

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

200
        // Build error text lazily (only if we actually get exceptions)
201
        StringBuilder errors = null;
1✔
202

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

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

224
                return localState;
1✔
225

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

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

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

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

261

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

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

285
        throw new Error("no function with name " + funcName + "was found.");
×
286
    }
287

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

298
    public Element getLastStatement() {
299
        return globalState.getLastStatement();
×
300
    }
301

302
    public void writebackGlobalState(boolean injectObjects) {
303
        globalState.writeBack(injectObjects);
1✔
304

305
    }
1✔
306

307
    public ProgramState getGlobalState() {
308
        return globalState;
1✔
309
    }
310

311
    public void addNativeProvider(NativesProvider np) {
312
        globalState.addNativeProvider(np);
1✔
313
    }
1✔
314

315
    public void setProgram(ImProg imProg) {
316
        this.prog = imProg;
×
317
        this.getGlobalState().setProg(imProg);
×
318
        globalState.resetStackframes();
×
319
    }
×
320

321
    public ProgramState.StackTrace getStackFrames() {
322
        return globalState.getStackFrames();
×
323

324
    }
325

326
    @Override
327
    public void runFuncRef(ILconstFuncRef obj, @Nullable Element trace) {
328
        runVoidFunc(obj.getFunc(), trace);
1✔
329
    }
1✔
330

331
    @Override
332
    public TimerMockHandler getTimerMockHandler() {
333
        return timerMockHandler;
1✔
334
    }
335

336
    @Override
337
    public void completeTimers() {
338
        timerMockHandler.completeTimers();
1✔
339
    }
1✔
340

341
    @Override
342
    public ImProg getImProg() {
343
        return prog;
1✔
344
    }
345

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

355
    @Override
356
    public int getMaxInstanceCount(int val) {
357
        return (int) globalState.getAllObjects()
1✔
358
            .stream()
1✔
359
            .filter(o -> o.getType().getClassDef().attrTypeId() == val)
1✔
360
            .count();
1✔
361
    }
362
}
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