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

wurstscript / WurstScript / 271

29 Sep 2025 12:12PM UTC coverage: 64.649% (+2.4%) from 62.222%
271

Pull #1096

circleci

Frotty
Merge branch 'perf-improvements' of https://github.com/wurstscript/WurstScript into perf-improvements
Pull Request #1096: Perf improvements

18202 of 28155 relevant lines covered (64.65%)

0.65 hits per line

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

75.15
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
165
    private static final int MAX_CACHE_PER_FUNC = 2048;
166

167
    private static final LocalState EMPTY_LOCAL_STATE = new LocalState();
1✔
168

169
    private static LocalState runBuiltinFunction(ProgramState globalState, ImFunction f, ILconst... args) {
170
        // Delegate to the array overload to avoid double-allocations.
171
        return runBuiltinFunction(globalState, f, args, /*isVarargs*/ true);
1✔
172
    }
173

174
    private static LocalState runBuiltinFunction(ProgramState globalState, ImFunction f, ILconst[] args, boolean isVarargs) {
175
        // Cache purity + name once
176
        final String fname = f.getName();
1✔
177
        final boolean pure = cache && isFunctionPure(fname);
1✔
178

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

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

193
        // Build error text lazily (only if we actually get exceptions)
194
        StringBuilder errors = null;
1✔
195

196
        for (NativesProvider natives : globalState.getNativeProviders()) {
1✔
197
            try {
198
                // Invoke native; TODO: cache method handles per name elsewhere.
199
                final LocalState localState = new LocalState(natives.invoke(fname, args));
1✔
200

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

217
                return localState;
1✔
218

219
            } catch (NoSuchNativeException e) {
1✔
220
                if (errors == null) errors = new StringBuilder(128);
1✔
221
                errors.append('\n').append(e.getMessage());
1✔
222
                // keep trying next provider
223
            }
224
        }
1✔
225

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

231
        // Return a lightweight state
232
        if (f.getReturnType() instanceof ImVoid) {
×
233
            return EMPTY_LOCAL_STATE;
×
234
        }
235
        final ILconst returnValue = ImHelper.defaultValueForComplexType(f.getReturnType())
×
236
            .evaluate(globalState, EMPTY_LOCAL_STATE);
×
237
        return new LocalState(returnValue);
×
238
    }
239

240
    /** Zero-allocation combined hash for ILconst[] (order-sensitive). */
241
    private static int fastHashArgs(ILconst[] args) {
242
        return Arrays.hashCode(args);
×
243
    }
244

245

246
    private static boolean isCompiletimeNative(ImFunction f) {
247
        if (f.getTrace() instanceof HasModifier) {
1✔
248
            HasModifier f2 = (HasModifier) f.getTrace();
1✔
249
            for (Modifier m : f2.getModifiers()) {
1✔
250
                if (m instanceof Annotation) {
1✔
251
                    Annotation annotation = (Annotation) m;
1✔
252
                    if (annotation.getAnnotationType().equals("@compiletimenative")) {
1✔
253
                        return true;
1✔
254
                    }
255
                }
256
            }
1✔
257
        }
258
        return false;
1✔
259
    }
260

261
    public LocalState executeFunction(String funcName, @Nullable Element trace) {
262
        globalState.resetStackframes();
1✔
263
        for (ImFunction f : prog.getFunctions()) {
1✔
264
            if (f.getName().equals(funcName)) {
1✔
265
                return runFunc(globalState, f, trace);
×
266
            }
267
        }
1✔
268

269
        throw new Error("no function with name " + funcName + "was found.");
×
270
    }
271

272
    public void runVoidFunc(ImFunction f, @Nullable Element trace) {
273
        globalState.resetStackframes();
1✔
274
        ILconst[] args = {};
1✔
275
        if (!f.getParameters().isEmpty()) {
1✔
276
            // this should only happen because of added stacktrace parameter
277
            args = new ILconstString[]{new ILconstString("initial call")};
1✔
278
        }
279
        runFunc(globalState, f, trace, args);
1✔
280
    }
1✔
281

282
    public Element getLastStatement() {
283
        return globalState.getLastStatement();
×
284
    }
285

286
    public void writebackGlobalState(boolean injectObjects) {
287
        globalState.writeBack(injectObjects);
1✔
288

289
    }
1✔
290

291
    public ProgramState getGlobalState() {
292
        return globalState;
1✔
293
    }
294

295
    public void addNativeProvider(NativesProvider np) {
296
        globalState.addNativeProvider(np);
1✔
297
    }
1✔
298

299
    public void setProgram(ImProg imProg) {
300
        this.prog = imProg;
×
301
        this.getGlobalState().setProg(imProg);
×
302
        globalState.resetStackframes();
×
303
    }
×
304

305
    public ProgramState.StackTrace getStackFrames() {
306
        return globalState.getStackFrames();
×
307

308
    }
309

310
    @Override
311
    public void runFuncRef(ILconstFuncRef obj, @Nullable Element trace) {
312
        runVoidFunc(obj.getFunc(), trace);
1✔
313
    }
1✔
314

315
    @Override
316
    public TimerMockHandler getTimerMockHandler() {
317
        return timerMockHandler;
1✔
318
    }
319

320
    @Override
321
    public void completeTimers() {
322
        timerMockHandler.completeTimers();
1✔
323
    }
1✔
324

325
    @Override
326
    public ImProg getImProg() {
327
        return prog;
1✔
328
    }
329

330
    @Override
331
    public int getInstanceCount(int val) {
332
        return (int) globalState.getAllObjects()
1✔
333
            .stream()
1✔
334
            .filter(o -> o.getType().getClassDef().attrTypeId() == val)
1✔
335
            .filter(o -> !o.isDestroyed())
1✔
336
            .count();
1✔
337
    }
338

339
    @Override
340
    public int getMaxInstanceCount(int val) {
341
        return (int) globalState.getAllObjects()
1✔
342
            .stream()
1✔
343
            .filter(o -> o.getType().getClassDef().attrTypeId() == val)
1✔
344
            .count();
1✔
345
    }
346
}
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