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 @@ -90,6 +90,7 @@ public CompiletimeFunctionRunner(
this.translator = tr;
this.imProg = imProg;
globalState = new ProgramStateIO(mapFile, mpqEditor, gui, imProg, true);
initializeBackendConstants();
this.interpreter = new ILInterpreter(imProg, gui, mapFile, globalState);

interpreter.addNativeProvider(new CompiletimeNatives(globalState, projectConfigData, isProd));
Expand All @@ -98,6 +99,14 @@ public CompiletimeFunctionRunner(
this.functionFlag = flag;
}

private void initializeBackendConstants() {
for (ImVar global : imProg.getGlobals()) {
if (global.getName().equals("MagicFunctions_isLua")) {
globalState.setValUntracked(global, ILconstBool.instance(translator.isLuaTarget()));
return;
}
}
}

public void run() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public static ILconst eval(ImVarAccess e, ProgramState globalState, LocalState l
if (r == null) {
List<ImSet> initExpr = globalState.getProg().getGlobalInits().get(var);
if (initExpr != null) {
r = initExpr.get(0).getRight().evaluate(globalState, localState);
r = globalState.evaluateUntracked(initExpr.get(0).getRight(), localState);
} else {
throw new InterpreterException(globalState, "Variable " + var.getName() + " is not initialized.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class ProgramState extends State implements AutoCloseable {
private final Map<String, List<ImTypeArgument>> genericArrayTypeArguments = new HashMap<>();
private final IdentityHashMap<ImVar, Object2ObjectOpenHashMap<String, ILconst>> genericStaticVals = new IdentityHashMap<>();
private final Object2ObjectOpenHashMap<String, ILconst> genericStaticScalarVals = new Object2ObjectOpenHashMap<>();
private int untrackedWriteDepth;

private static boolean containsTypeVariable(ImType type) {
return type.match(new ImType.Matcher<Boolean>() {
Expand Down Expand Up @@ -684,13 +685,18 @@ private static String vid(ImVar v) {

@Override
public void setVal(ImVar v, ILconst val) {
modifiedScalars.add(v);
boolean trackWrite = untrackedWriteDepth == 0;
if (trackWrite) {
modifiedScalars.add(v);
}
String key = genericStaticKey(v);
if (key != null) {
WLogger.trace(() -> "[GENSTATIC] set " + key + " = " + val);
genericStaticScalarVals.put(key, val);
modifiedGenericScalars.add(key);
genericScalarTypeArguments.computeIfAbsent(key, ignored -> genericStaticTypeArguments(v));
if (trackWrite) {
modifiedGenericScalars.add(key);
genericScalarTypeArguments.computeIfAbsent(key, ignored -> genericStaticTypeArguments(v));
}
return;
}
super.setVal(v, val);
Expand Down Expand Up @@ -719,7 +725,7 @@ public void setValUntracked(ImVar v, ILconst val) {
// lazy init from global inits (e.g. foo = 1)
List<ImSet> inits = prog.getGlobalInits().get(v);
if (inits != null && !inits.isEmpty()) {
ILconst initVal = inits.get(inits.size() - 1).getRight().evaluate(this, EMPTY_LOCAL_STATE);
ILconst initVal = evaluateUntracked(inits.get(inits.size() - 1).getRight(), EMPTY_LOCAL_STATE);
genericStaticScalarVals.put(key, initVal);
WLogger.trace(() -> "[GENSTATIC] get " + key + " -> (init) " + initVal);
return initVal;
Expand All @@ -733,6 +739,21 @@ public void setValUntracked(ImVar v, ILconst val) {
return super.getVal(v);
}

/**
* Evaluates a lazy global initializer without recording its side effects for state migration.
* Runtime repeats initializer execution, so replaying those writes would duplicate them.
* Compiletime-only side effects hidden inside an initializer are intentionally unsupported;
* persistent mutations must be performed by an explicit compiletime function instead.
*/
public ILconst evaluateUntracked(ImExpr expr, LocalState localState) {
untrackedWriteDepth++;
try {
return expr.evaluate(this, localState);
Comment thread
Frotty marked this conversation as resolved.
} finally {
untrackedWriteDepth--;
}
}


public boolean isCompiletime() {
return isCompiletime;
Expand All @@ -755,7 +776,7 @@ protected ILconstArray getArray(ImVar v) {
if (inits != null && !inits.isEmpty()) {
final LocalState ls = EMPTY_LOCAL_STATE;
for (int i = 0; i < inits.size(); i++) {
ILconst val = inits.get(i).getRight().evaluate(this, ls);
ILconst val = evaluateUntracked(inits.get(i).getRight(), ls);
r.set(i, val);
}
}
Expand All @@ -774,7 +795,7 @@ protected ILconstArray getArray(ImVar v) {
if (inits != null && !inits.isEmpty()) {
final LocalState ls = EMPTY_LOCAL_STATE;
for (int i = 0; i < inits.size(); i++) {
ILconst val = inits.get(i).getRight().evaluate(this, ls);
ILconst val = evaluateUntracked(inits.get(i).getRight(), ls);
r.set(i, val);
}
}
Expand All @@ -783,6 +804,10 @@ protected ILconstArray getArray(ImVar v) {

@Override
public void setArrayVal(ImVar v, List<Integer> indexes, ILconst val) {
if (untrackedWriteDepth > 0) {
setArrayValUntracked(v, indexes, val);
return;
}
String key = genericStaticKey(v);
super.setArrayVal(v, indexes, val);
if (key != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ static ILconstArray createArrayConstantFromType(ImType vType) {
public void setArrayVal(ImVar v, List<Integer> indexes, ILconst val) {
modifiedArrayIndexes.computeIfAbsent(v, ignored -> new HashSet<>())
.add(Collections.unmodifiableList(new ArrayList<>(indexes)));
setArrayValUntracked(v, indexes, val);
}

protected void setArrayValUntracked(ImVar v, List<Integer> indexes, ILconst val) {
ILconstArray ar = getArray(v);
for (int i = 0; i < indexes.size() - 1; i++) {
ar = (ILconstArray) ar.get(indexes.get(i));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ public void testCompiletimeScalarReplayOnlyWrittenValues() {
" testSuccess()");
}

@Test
public void testLazyScalarInitializerSideEffectsAreNotReplayed() {
test().testLua(true).luaOnly(false).executeProg(true).executeProgOnlyAfterTransforms().runCompiletimeFunctions(true)
.lines("package Test",
"native testSuccess()",
"int counter = 0",
"function bump() returns int",
" counter++",
" return counter",
"int observed = bump()",
"int migrated",
"@compiletime function fill()",
" let _snapshot = observed",
" migrated = 42",
"init",
" if counter == 1 and observed == 1 and migrated == 42",
" testSuccess()");
}

@Test
public void testCompiletimeScalarRuntimeWriteRemainsAuthoritative() {
test().testLua(true).luaOnly(false).executeProg(true).executeProgOnlyAfterTransforms().runCompiletimeFunctions(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,34 @@ public void compiletimeScalarReplaySplittingIsDeterministicAcrossPackages() {
assertEquals("all compiletime scalar values must still be emitted", 4, persistedAssignments);
}

@Test
public void compiletimeInterpreterSeesLuaTarget() {
String compiled = compileLuaWithRunArgs(
"compiletimeInterpreterSeesLuaTarget",
new RunArgs().with("-lua", "-runcompiletimefunctions"),
"package MagicFunctions",
"public constant isLua = false",
"endpackage",
"package Test",
"import MagicFunctions",
"int observedBackend",
"@compiletime function detectBackend()",
" if isLua",
" observedBackend = 1",
" else",
" observedBackend = 2",
"native testSuccess()",
"init",
" if observedBackend == 1",
" testSuccess()"
);

assertTrue("compiletime execution must take the Lua branch:\n" + compiled,
compiled.contains("Test_observedBackend = 1"));
assertFalse("compiletime execution must not persist the Jass branch:\n" + compiled,
compiled.contains("Test_observedBackend = 2"));
}

@Test
public void localPlayerEffectfulBooleanOperandSurvivesOptimization() {
String compiled = compileOptimizedLua(
Expand Down
Loading