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 @@ -2,23 +2,33 @@

import com.google.common.collect.Sets;
import de.peeeq.wurstscript.attributes.CompileError;
import de.peeeq.wurstscript.ast.GlobalVarDef;
import de.peeeq.wurstscript.jassIm.*;
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
import de.peeeq.wurstscript.utils.Utils;
import de.peeeq.wurstscript.validation.NamePreservation;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public class GlobalsInliner implements OptimizerPass {
@Override
public int optimize(ImTranslator trans) {
int obsoleteCount = 0;
ImProg prog = trans.getImProg();
prog.clearAttributes(); // TODO only clear read/write attributes
LiteralConstantAnalysis literalConstants = analyzeLiteralConstants(trans, prog);

Set<ImVar> obsoleteVars = Sets.newLinkedHashSet();
for (final ImVar v : prog.getGlobals()) {
Expand Down Expand Up @@ -52,15 +62,21 @@ public int optimize(ImTranslator trans) {
continue;
}

if (v.attrWrites().size() == 1) {
boolean literalConstant = literalConstants.safeConstants.contains(v);
if (v.attrWrites().size() == 1 || literalConstant) {
ImExpr right = null;
ImVarWrite obs = null;
for (ImVarWrite write : v.attrWrites()) {
ImFunction func = write.getNearestFunc();
if (isInInitGlobals(func)) {
right = write.getRight();
obs = write;
break;
if (literalConstant) {
obs = literalConstants.replacementWrites.get(v);
right = obs.getRight();
} else {
for (ImVarWrite write : v.attrWrites()) {
ImFunction func = write.getNearestFunc();
if (isInInitGlobals(func)) {
right = write.getRight();
obs = write;
break;
}
}
}
if (obs == null) {
Expand All @@ -73,7 +89,7 @@ public int optimize(ImTranslator trans) {
v3.replaceBy(replacement.copy());
}
}
if (replacement != null || v.attrReads().size() == 0) {
if ((replacement != null || v.attrReads().size() == 0) && v.attrWrites().size() == 1) {
obsoleteVars.add(v);
}
} else if (v.attrWrites().size() > 1 && !(v.getType() instanceof ImTupleType)) {
Expand Down Expand Up @@ -149,6 +165,10 @@ private ImExpr findReplacement(ImExpr right, ImVarWrite obs) {
return replacement;
}

private static boolean isLiteral(ImExpr expr) {
return expr instanceof ImIntVal || expr instanceof ImRealVal || expr instanceof ImStringVal || expr instanceof ImBoolVal;
}

@Override
public String getName() {
return "Globals Inlined";
Expand All @@ -159,4 +179,272 @@ private static boolean isInInitGlobals(ImFunction func) {
return func != null && func.getName().equals("initGlobals");
}

/**
* A package constant is assigned at runtime in a package initializer. Replacing all reads is
* valid only when no startup path can observe the default value before that emitted assignment.
* Analyze the actual IM startup order once, including transitive calls and function references,
* rather than trying to reconstruct translation and dependency order from source positions.
*/
private static LiteralConstantAnalysis analyzeLiteralConstants(ImTranslator trans, ImProg prog) {
List<ImFunction> initializationOrder = trans.getInitializationOrder();
IdentityHashMap<ImStmt, Integer> statementRanks = new IdentityHashMap<>();
IdentityHashMap<ImVarWrite, Long> writeRanks = new IdentityHashMap<>();
for (int functionRank = 0; functionRank < initializationOrder.size(); functionRank++) {
ImFunction initializer = initializationOrder.get(functionRank);
for (int statementRank = 0; statementRank < initializer.getBody().size(); statementRank++) {
statementRanks.put(initializer.getBody().get(statementRank), statementRank);
}
int[] writeRank = {0};
int currentFunctionRank = functionRank;
initializer.getBody().accept(new ImStmt.DefaultVisitor() {
@Override
public void visit(ImSet write) {
long rank = ((long) currentFunctionRank << 32) | (writeRank[0]++ & 0xffffffffL);
writeRanks.put(write, rank);
super.visit(write);
}
});
}

List<ImVar> candidates = new ArrayList<>();
IdentityHashMap<ImVar, ImVarWrite> replacementWrites = new IdentityHashMap<>();
for (ImVar var : prog.getGlobals()) {
if (!isSourceConstant(var)) {
continue;
}
ImVarWrite replacementWrite = null;
ImExpr replacement = null;
long replacementRank = Long.MAX_VALUE;
boolean eligible = !var.attrWrites().isEmpty();
for (ImVarWrite write : var.attrWrites()) {
ImFunction initializer = write.getNearestFunc();
ImStmt statement = initializer == null ? null
: topLevelStatement((de.peeeq.wurstscript.jassIm.Element) write, initializer);
Integer statementRank = statementRanks.get(statement);
Long writeRank = writeRanks.get(write);
if (statement == null || statementRank == null
|| writeRank == null || !isLiteral(write.getRight())) {
eligible = false;
break;
}
if (replacement == null) {
replacement = write.getRight();
} else if (!replacement.structuralEquals(write.getRight())) {
eligible = false;
break;
}
if (writeRank < replacementRank) {
replacementRank = writeRank;
replacementWrite = write;
}
}
if (eligible) {
candidates.add(var);
replacementWrites.put(var, replacementWrite);
}
}
if (candidates.isEmpty()) {
return new LiteralConstantAnalysis(identitySet(), replacementWrites);
}

IdentityHashMap<ImFunction, Set<Integer>> readsByFunction = new IdentityHashMap<>();
IdentityHashMap<ImStmt, Set<Integer>> readsByStatement = new IdentityHashMap<>();
IdentityHashMap<ImStmt, Set<Integer>> writesByStatement = new IdentityHashMap<>();
IdentityHashMap<ImFunction, Set<Integer>> writesByInitializer = new IdentityHashMap<>();
BitSet unsafe = new BitSet(candidates.size());

for (int i = 0; i < candidates.size(); i++) {
ImVar candidate = candidates.get(i);
for (ImVarRead read : candidate.attrReads()) {
ImFunction function = read.getNearestFunc();
if (function == null) {
unsafe.set(i);
continue;
}
readsByFunction.computeIfAbsent(function, ignored -> new HashSet<>()).add(i);
ImStmt statement = topLevelStatement((de.peeeq.wurstscript.jassIm.Element) read, function);
if (statement != null) {
readsByStatement.computeIfAbsent(statement, ignored -> new HashSet<>()).add(i);
}
}
for (ImVarWrite write : candidate.attrWrites()) {
ImFunction function = write.getNearestFunc();
ImStmt statement = function == null ? null
: topLevelStatement((de.peeeq.wurstscript.jassIm.Element) write, function);
if (statement != null) {
writesByStatement.computeIfAbsent(statement, ignored -> new HashSet<>()).add(i);
writesByInitializer.computeIfAbsent(function, ignored -> new HashSet<>()).add(i);
}
}
}

BitSet pending = new BitSet(candidates.size());
pending.set(0, candidates.size());
Set<ImFunction> reachableFromStartup = identitySet();
ImFunction config = trans.getConfFunc();
if (config != null) {
scanStartupStatements(config.getBody(), pending, unsafe, readsByStatement, writesByStatement,
readsByFunction, reachableFromStartup, null);
}
if (!initializationOrder.isEmpty()) {
scanStartupStatements(initializationOrder.get(0).getBody(), pending, unsafe, readsByStatement,
writesByStatement, readsByFunction, reachableFromStartup, null);
scanMainPrefix(trans, initializationOrder, pending, unsafe, readsByStatement, writesByStatement,
readsByFunction, reachableFromStartup);
}
for (int i = 1; i < initializationOrder.size(); i++) {
ImFunction initializer = initializationOrder.get(i);
scanStartupStatements(initializer.getBody(), pending, unsafe, readsByStatement, writesByStatement,
readsByFunction, reachableFromStartup, writesByInitializer.get(initializer));
}
unsafe.or(pending);

Set<ImVar> safeConstants = identitySet();
for (int i = 0; i < candidates.size(); i++) {
if (!unsafe.get(i)) {
safeConstants.add(candidates.get(i));
}
}
return new LiteralConstantAnalysis(safeConstants, replacementWrites);
}

private static void scanMainPrefix(ImTranslator trans, List<ImFunction> initializationOrder,
BitSet pending, BitSet unsafe,
Map<ImStmt, Set<Integer>> readsByStatement,
Map<ImStmt, Set<Integer>> writesByStatement,
Map<ImFunction, Set<Integer>> readsByFunction,
Set<ImFunction> reachableFromStartup) {
Set<ImFunction> packageInitializers = identitySet();
packageInitializers.addAll(initializationOrder.subList(1, initializationOrder.size()));
for (ImStmt statement : trans.getMainFunc().getBody()) {
Set<ImFunction> usedFunctions = directlyUsedFunctions(statement);
if (!Collections.disjoint(usedFunctions, packageInitializers)) {
return;
}
scanStartupStatements(Collections.singleton(statement), pending, unsafe, readsByStatement,
writesByStatement, readsByFunction, reachableFromStartup, null);
}
}

private static void scanStartupStatements(Collection<ImStmt> statements, BitSet pending, BitSet unsafe,
Map<ImStmt, Set<Integer>> readsByStatement,
Map<ImStmt, Set<Integer>> writesByStatement,
Map<ImFunction, Set<Integer>> readsByFunction,
Set<ImFunction> reachableFromStartup,
@Nullable Set<Integer> writesInAbortableInitializer) {
for (ImStmt statement : statements) {
BitSet reads = new BitSet();
for (int candidate : readsByStatement.getOrDefault(statement, Collections.emptySet())) {
reads.set(candidate);
}
addNewlyReachableReads(statement, reachableFromStartup, readsByFunction, reads);
reads.and(pending);
unsafe.or(reads);
if (writesInAbortableInitializer != null && !isDefinitelyNonAbortingInitializerStatement(statement)) {
for (int candidate : writesInAbortableInitializer) {
if (pending.get(candidate)) {
unsafe.set(candidate);
}
}
}
for (int candidate : writesByStatement.getOrDefault(statement, Collections.emptySet())) {
pending.clear(candidate);
}
}
}

private static void addNewlyReachableReads(ImStmt statement, Set<ImFunction> reachableFromStartup,
Map<ImFunction, Set<Integer>> readsByFunction, BitSet reads) {
ArrayDeque<ImFunction> undiscovered = new ArrayDeque<>();
for (ImFunction function : directlyUsedFunctions(statement)) {
if (function != null && reachableFromStartup.add(function)) {
undiscovered.addLast(function);
}
}
while (!undiscovered.isEmpty()) {
ImFunction function = undiscovered.removeFirst();
for (int candidate : readsByFunction.getOrDefault(function, Collections.emptySet())) {
reads.set(candidate);
}
for (ImFunction callee : function.calcUsedFunctions()) {
if (callee != null && reachableFromStartup.add(callee)) {
undiscovered.addLast(callee);
}
}
}
}

private static boolean isDefinitelyNonAbortingInitializerStatement(ImStmt statement) {
return statement instanceof ImSet set && set.getLeft() instanceof ImVarAccess && isLiteral(set.getRight());
}

private static Set<ImFunction> directlyUsedFunctions(ImStmt statement) {
Set<ImFunction> result = identitySet();
statement.accept(new ImStmt.DefaultVisitor() {
@Override
public void visit(ImFunctionCall call) {
super.visit(call);
result.add(call.getFunc());
}

@Override
public void visit(ImFuncRef ref) {
super.visit(ref);
result.add(ref.getFunc());
}

@Override
public void visit(ImMethodCall call) {
super.visit(call);
if (call.getMethod().getImplementation() != null) {
result.add(call.getMethod().getImplementation());
}
for (ImMethod subMethod : call.getMethod().getSubMethods()) {
if (subMethod.getImplementation() != null) {
result.add(subMethod.getImplementation());
}
}
}
});
return result;
}

@Nullable
@SuppressWarnings("ReferenceEquality")
private static ImStmt topLevelStatement(de.peeeq.wurstscript.jassIm.Element element, ImFunction function) {
de.peeeq.wurstscript.jassIm.Element current = element;
while (current != null && current.getParent() != function.getBody()) {
current = current.getParent();
}
return current instanceof ImStmt ? (ImStmt) current : null;
}

private static boolean isSourceConstant(ImVar var) {
if (!(var.getTrace() instanceof GlobalVarDef)) {
return false;
}
if (var.getName().equals("MagicFunctions_compiletime")
|| var.getName().equals("MagicFunctions_isLua")) {
// These values depend on compiler execution context/backend and are lowered by their
// dedicated paths. They are not ordinary source literals for package-constant folding.
return false;
}
GlobalVarDef global = (GlobalVarDef) var.getTrace();
return global.attrIsConstant() && !global.hasAnnotation("@configurable");
}

private static <T> Set<T> identitySet() {
return Collections.newSetFromMap(new IdentityHashMap<>());
}

private static final class LiteralConstantAnalysis {
private final Set<ImVar> safeConstants;
private final Map<ImVar, ImVarWrite> replacementWrites;

private LiteralConstantAnalysis(Set<ImVar> safeConstants, Map<ImVar, ImVarWrite> replacementWrites) {
this.safeConstants = safeConstants;
this.replacementWrites = replacementWrites;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ public <T extends Element> T canonical(T copy) {

public final Map<WPackage, ImFunction> initFuncMap = new Object2ObjectLinkedOpenHashMap<>();

/** Initializer functions in the exact order emitted by {@link #finishInitFunctions()}. */
private final List<ImFunction> initializationOrder = new ArrayList<>();

/**
* When targeting Lua, package init functions that should be called directly via xpcall
* rather than through the JASS TriggerEvaluate thread-isolation pattern.
Expand Down Expand Up @@ -676,6 +679,8 @@ private void translateCompilationUnit(CompilationUnit cu) {


private void finishInitFunctions() {
initializationOrder.clear();
initializationOrder.add(globalInitFunc);
// init globals, at beginning of main func:
getMainFunc().getBody().add(0, ImFunctionCall(emptyTrace, globalInitFunc, ImTypeArguments(), ImExprs(), false, CallType.NORMAL));

Expand Down Expand Up @@ -744,6 +749,7 @@ private void callInitFunc(Set<WPackage> calledInitializers, WPackage p, @Nullabl
if (initFunc.getBody().size() == 0) {
return;
}
initializationOrder.add(initFunc);
if (isLuaTarget()) {
// In Lua mode, xpcall replaces TriggerEvaluate for error isolation without WC3 handle overhead.
// Record the init function so the Lua translator can wrap it with xpcall.
Expand Down Expand Up @@ -1531,7 +1537,9 @@ private Multimap<ImFunction, ImFunction> getCallRelations() {
public ImFunction getMainFunc() { return mainFunc; }
public ImFunction getConfFunc() { return configFunc; }


public List<ImFunction> getInitializationOrder() {
return Collections.unmodifiableList(initializationOrder);
}

/**
* returns a list of classes and functions implementing funcDef
Expand Down
Loading
Loading