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
1 change: 1 addition & 0 deletions de.peeeq.wurstscript/parserspec/lua.parseq
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ LuaStatements * LuaStatement

LuaStatement =
LuaDefinition
| LuaChunkLocal(LuaDefinition definition) // a definition printed as a local of the main chunk
| LuaAssignment(LuaExpr left, LuaExpr right)
| LuaWhile(LuaExpr cond, LuaStatements body)
| LuaFor(ref LuaVariable loopVar, LuaExpr from, LuaExpr to, LuaExprOpt step, LuaStatements body)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,9 @@ public LuaCompilationUnit transformProgToLua() {
optimizer.removeGarbage();
imProg.flatten(imTranslator);
timeTaker.endPhase();
// After the inlining an allocation is followed by the constructor's writes, so the local optimizations can
// drop the defaults those overwrite. The backend no longer writes them, so this runs in every mode.
LuaFieldDefaults.materialize(imProg, imTranslator2);
stage = 10;
if (runArgs.isLocalOptimizations()) {
beginPhase(10, "local optimizations");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,13 @@ public ILconstInt __wurst_rawFloorModInt(ILconstInt a, ILconstInt b) {
public ILconstReal __wurst_rawFmodReal(ILconstReal a, ILconstReal b) {
return ILconstReal.create(a.getVal() % b.getVal());
}

/** Lua 5.3's float {@code %}: fmod, plus the divisor when {@code fmod * b < 0}. */
public ILconstReal __wurst_rawFloorModReal(ILconstReal a, ILconstReal b) {
float m = a.getVal() % b.getVal();
if (m * b.getVal() < 0) {
m += b.getVal();
}
return ILconstReal.create(m);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package de.peeeq.wurstscript.intermediatelang.optimizer;

import de.peeeq.wurstscript.jassIm.*;
import de.peeeq.wurstscript.translation.imoptimizer.OptimizerPass;
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
import de.peeeq.wurstscript.translation.imtranslation.LuaTraps;

import java.util.*;

/**
* Removes a write of a constant to a field of an object allocated in the same statement list, which the list writes
* again before anything can read it.
* <p>
* Between the two writes there must be no read of the field through any object (so an alias cannot see the first
* value), no call (a callee can read anything, a native can run Wurst code through an event), nothing which can stop
* the thread or leave the list ({@link LuaTraps#mayRaise}, such as a division, a deallocation, arithmetic on a value
* which may be nil, or a write through an object which may be null; {@code exitwhen}, {@code return}: the first value
* would then be what remains) and no statement with statement lists of its own (whose paths are not followed). The object is told by value: a local copied from another names the same object
* until either is assigned again. It must have been allocated in the list, so it is not null and the first write
* cannot fail either. An allocation reads no field and runs no Wurst code, so it is not in the way.
* <p>
* This removes the defaults an allocation writes on Lua where the constructor sets the field
* ({@code LuaFieldDefaults}). Fields are only still fields on Lua: on Jass the class elimination has turned them into
* arrays before the local optimisations run.
*/
public class RedundantFieldStores implements OptimizerPass {

private ImTranslator trans;
private int removed;

@Override
public int optimize(ImTranslator trans) {
this.trans = trans;
removed = 0;
for (ImFunction func : ImHelper.calculateFunctionsOfProg(trans.getImProg())) {
if (!func.isNative() && !func.isBj()) {
optimizeStatements(func.getBody());
}
}
return removed;
}

@Override
public String getName() {
return "Redundant field stores removed";
}

/** A field, by its storage, of the object a value number stands for. */
private record Slot(ImVar field, int object) {
}

private void optimizeStatements(ImStmts stmts) {
Map<ImVar, Integer> objectOf = new IdentityHashMap<>();
int[] nextObject = {0};
Set<Integer> allocated = new HashSet<>();
Map<Slot, ImSet> pending = new HashMap<>();
Set<ImStmt> dead = Collections.newSetFromMap(new IdentityHashMap<>());
for (ImStmt s : stmts) {
if (hasStatementLists(s)) {
// Its paths can assign the locals, so their values are told apart afresh after it.
pending.clear();
objectOf.clear();
allocated.clear();
s.accept(new Element.DefaultVisitor() {
@Override
public void visit(ImStmts nested) {
optimizeStatements(nested);
}
});
continue;
}
if (isBarrier(s)) {
pending.clear();
}
if (s instanceof ImSet set) {
forgetReadFields(set.getRight(), pending);
if (set.getLeft() instanceof ImMemberAccess target) {
forgetReadFields(target.getReceiver(), pending);
forgetReadFields(target.getIndexes(), pending);
Integer object = target.getIndexes().isEmpty()
&& target.getReceiver() instanceof ImVarAccess receiver
&& !receiver.getVar().isGlobal() ? objectOf.get(receiver.getVar()) : null;
if (object == null || !allocated.contains(object)) {
// A write through an object which may be null raises (a nil table key), like a read of an
// array field through one.
pending.clear();
} else {
Slot slot = new Slot(trans.canonical(target.getVar()), object);
ImSet earlier = pending.remove(slot);
if (earlier != null) {
dead.add(earlier);
}
if (isConstant(set.getRight())) {
pending.put(slot, set);
}
}
} else {
forgetReadFields(set.getLeft(), pending);
if (set.getLeft() instanceof ImVarAccess assigned && !assigned.getVar().isGlobal()) {
if (set.getRight() instanceof ImVarAccess copied && !copied.getVar().isGlobal()) {
objectOf.put(assigned.getVar(),
objectOf.computeIfAbsent(copied.getVar(), v -> nextObject[0]++));
} else {
int object = nextObject[0]++;
objectOf.put(assigned.getVar(), object);
if (set.getRight() instanceof ImAlloc) {
allocated.add(object);
}
}
}
}
} else {
forgetReadFields(s, pending);
}
}
if (!dead.isEmpty()) {
stmts.removeIf(dead::contains);
removed += dead.size();
}
}

private static boolean hasStatementLists(ImStmt s) {
return s instanceof ImIf || s instanceof ImLoop || s instanceof ImVarargLoop;
}

/**
* Whether running {@code s} can read any field or leave the list with the first value in place: anything which can
* raise ({@link LuaTraps#mayRaise}, a call among them), or an {@code exitwhen} or {@code return}.
*/
private static boolean isBarrier(ImStmt s) {
return s instanceof ImExitwhen || s instanceof ImReturn || LuaTraps.mayRaise(s);
}

/** Forgets the pending writes of every field {@code e} reads, through whatever object. */
private void forgetReadFields(Element e, Map<Slot, ImSet> pending) {
if (pending.isEmpty()) {
return;
}
e.accept(new Element.DefaultVisitor() {
@Override
public void visit(ImMemberAccess access) {
super.visit(access);
ImVar field = trans.canonical(access.getVar());
pending.keySet().removeIf(slot -> slot.field() == field);
}
});
}

private static boolean isConstant(ImExpr e) {
return e instanceof ImIntVal || e instanceof ImRealVal || e instanceof ImBoolVal
|| e instanceof ImStringVal || e instanceof ImNull;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import de.peeeq.wurstscript.intermediatelang.optimizer.LocalPlayerAwareOptimizerPass;
import de.peeeq.wurstscript.intermediatelang.optimizer.LocalPlayerContextAnalyzer;
import de.peeeq.wurstscript.intermediatelang.optimizer.LocalMerger;
import de.peeeq.wurstscript.intermediatelang.optimizer.RedundantFieldStores;
import de.peeeq.wurstscript.intermediatelang.optimizer.SideEffectAnalyzer;
import de.peeeq.wurstscript.intermediatelang.optimizer.SimpleRewrites;
import de.peeeq.wurstscript.jassIm.*;
Expand Down Expand Up @@ -37,6 +38,9 @@ public class ImOptimizer {
localPasses.add(new BranchMerger());
localPasses.add(new ConstantAndCopyPropagation());
localPasses.add(new UselessFunctionCallsRemover());
// After the passes which use the local-player analysis: a pass which does not use it discards it, and none
// after this one needs it again in the same sweep.
localPasses.add(new RedundantFieldStores());
localPasses.add(new GlobalsInliner());
localPasses.add(new DispatchCheckDeduplicator());
localPasses.add(new SimpleRewrites());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ private static ImExpr wrapTranslation(Element trace, ImTranslator t, ImExpr tran
boolean indexContext) {
ImFunction toIndex = null;
ImFunction fromIndex = null;
FuncDef fromIndexFunc = null;
if (actualType instanceof WurstTypeBoundTypeParam wtb) {
FuncDef fromIndexFunc = wtb.getFromIndex();
fromIndexFunc = wtb.getFromIndex();
if (fromIndexFunc != null) {
fromIndex = t.getFuncFor(fromIndexFunc);
}
Expand All @@ -178,7 +179,10 @@ private static ImExpr wrapTranslation(Element trace, ImTranslator t, ImExpr tran
return wrapLua(trace, t, translated, actualType);
} else if (fromIndex != null) {
// System.out.println(" --> fromIndex");
if(t.isLuaTarget()) {
// Lua normalises the erased value to an int first, except for the conversions which
// answer the same for every value a slot can hold (nil, 0, an index) without it.
if (t.isLuaTarget() && !de.peeeq.wurstscript.translation.lua.translation.ExprTranslation
.printsAsObjectFromIndex(fromIndexFunc)) {
translated = ImFunctionCall(trace, t.ensureIntFunc, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL);
}
// no ensure type necessary here, because the fromIndex function is already type safe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ public <T extends Element> T canonical(T copy) {
@Nullable public ImFunction luaRawFmodIntFunc = null;
@Nullable public ImFunction luaRawFmodRealFunc = null;
@Nullable public ImFunction luaRawFloorModIntFunc = null;
@Nullable public ImFunction luaRawFloorModRealFunc = null;
@Nullable public ImFunction luaRawConcatFunc = null;
/** {@code x or ""}: a string operand which may be nil, made safe for {@link #luaRawConcatFunc}. */
@Nullable public ImFunction luaRawOrEmptyFunc = null;
Expand Down Expand Up @@ -240,7 +241,8 @@ public boolean isTrapFreeLuaIntrinsicCall(ImFunctionCall call) {
|| (divisor instanceof ImRealVal realVal && Double.parseDouble(realVal.getValR()) != 0.0);
return nonZeroDivisor
&& (target == luaRawFloorDivIntFunc || target == luaRawFmodIntFunc
|| target == luaRawFloorModIntFunc || target == luaRawFmodRealFunc);
|| target == luaRawFloorModIntFunc || target == luaRawFmodRealFunc
|| target == luaRawFloorModRealFunc);
}
/**
* Whether {@code f} is one of the Lua keyed-map read stubs, which the backend prints as a table
Expand All @@ -263,7 +265,8 @@ public List<ImFunction> pinnedFunctions() {
List<ImFunction> result = new ArrayList<>();
for (ImFunction f : new ImFunction[]{
ensureIntFunc, ensureBoolFunc, ensureRealFunc, ensureStrFunc, stringConcatFunc,
luaRawFloorDivIntFunc, luaRawFmodIntFunc, luaRawFmodRealFunc, luaRawFloorModIntFunc, luaRawConcatFunc,
luaRawFloorDivIntFunc, luaRawFmodIntFunc, luaRawFmodRealFunc, luaRawFloorModIntFunc,
luaRawFloorModRealFunc, luaRawConcatFunc,
luaRawOrEmptyFunc, luaRawR2IFunc, luaRawToNumberIntFunc, luaRawToNumberRealFunc, luaRawToIntegerFunc,
luaRawToStringFunc, luaIntDivFunc, luaModIntFunc, luaModRealFunc,
debugPrintFunction, errorFunc, genericNewMarker, globalInitFunc}) {
Expand Down Expand Up @@ -465,6 +468,17 @@ private record Bridge(ImMethod bridge, ImMethod inherited) {

private final List<Bridge> bridges = new ArrayList<>();

/** Whether {@link LuaFieldDefaults} wrote the field defaults after the allocations, which the Lua backend needs. */
private boolean luaFieldDefaultsMaterialized = false;

public boolean luaFieldDefaultsMaterialized() {
return luaFieldDefaultsMaterialized;
}

public void setLuaFieldDefaultsMaterialized() {
luaFieldDefaultsMaterialized = true;
}

/**
* Gives {@code bridge} the overrides of {@code inherited} which are below its class as sub-methods, once every
* class is translated: the sub-methods of {@code inherited} come from the translation of its class, which may come
Expand Down
Loading
Loading