|
3 | 3 | import com.google.common.collect.Sets; |
4 | 4 | import de.peeeq.wurstscript.attributes.CompileError; |
5 | 5 | import de.peeeq.wurstscript.ast.GlobalVarDef; |
6 | | -import de.peeeq.wurstscript.ast.InitBlock; |
7 | | -import de.peeeq.wurstscript.ast.CompilationUnit; |
8 | | -import de.peeeq.wurstscript.ast.WImport; |
9 | | -import de.peeeq.wurstscript.ast.WEntity; |
10 | | -import de.peeeq.wurstscript.ast.WPackage; |
11 | 6 | import de.peeeq.wurstscript.jassIm.*; |
12 | 7 | import de.peeeq.wurstscript.translation.imtranslation.ImHelper; |
13 | 8 | import de.peeeq.wurstscript.translation.imtranslation.ImTranslator; |
14 | 9 | import de.peeeq.wurstscript.utils.Utils; |
15 | 10 | import de.peeeq.wurstscript.validation.NamePreservation; |
16 | 11 | import org.jetbrains.annotations.Nullable; |
17 | 12 |
|
| 13 | +import java.util.ArrayDeque; |
18 | 14 | import java.util.ArrayList; |
| 15 | +import java.util.BitSet; |
| 16 | +import java.util.Collection; |
19 | 17 | import java.util.Collections; |
20 | 18 | import java.util.IdentityHashMap; |
21 | 19 | import java.util.List; |
| 20 | +import java.util.Map; |
22 | 21 | import java.util.Optional; |
23 | 22 | import java.util.Set; |
24 | 23 |
|
25 | 24 | public class GlobalsInliner implements OptimizerPass { |
26 | | - private final Set<WPackage> initLaterPackages = Collections.newSetFromMap(new IdentityHashMap<>()); |
27 | | - private boolean initLaterPackagesCollected; |
28 | | - |
| 25 | + @Override |
29 | 26 | public int optimize(ImTranslator trans) { |
30 | 27 | int obsoleteCount = 0; |
31 | 28 | ImProg prog = trans.getImProg(); |
32 | 29 | prog.clearAttributes(); // TODO only clear read/write attributes |
| 30 | + LiteralConstantAnalysis literalConstants = analyzeLiteralConstants(trans, prog); |
33 | 31 |
|
34 | 32 | Set<ImVar> obsoleteVars = Sets.newLinkedHashSet(); |
35 | 33 | for (final ImVar v : prog.getGlobals()) { |
@@ -63,16 +61,21 @@ public int optimize(ImTranslator trans) { |
63 | 61 | continue; |
64 | 62 | } |
65 | 63 |
|
66 | | - boolean literalConstant = isLiteralConstantGlobal(v.getTrace(), prog); |
| 64 | + boolean literalConstant = literalConstants.safeConstants.contains(v); |
67 | 65 | if (v.attrWrites().size() == 1 || literalConstant) { |
68 | 66 | ImExpr right = null; |
69 | 67 | ImVarWrite obs = null; |
70 | | - for (ImVarWrite write : v.attrWrites()) { |
71 | | - ImFunction func = write.getNearestFunc(); |
72 | | - if (isInInitGlobals(func) || (literalConstant && isLiteral(write.getRight()))) { |
73 | | - right = write.getRight(); |
74 | | - obs = write; |
75 | | - break; |
| 68 | + if (literalConstant) { |
| 69 | + obs = literalConstants.replacementWrites.get(v); |
| 70 | + right = obs.getRight(); |
| 71 | + } else { |
| 72 | + for (ImVarWrite write : v.attrWrites()) { |
| 73 | + ImFunction func = write.getNearestFunc(); |
| 74 | + if (isInInitGlobals(func)) { |
| 75 | + right = write.getRight(); |
| 76 | + obs = write; |
| 77 | + break; |
| 78 | + } |
76 | 79 | } |
77 | 80 | } |
78 | 81 | if (obs == null) { |
@@ -176,69 +179,277 @@ private static boolean isInInitGlobals(ImFunction func) { |
176 | 179 | } |
177 | 180 |
|
178 | 181 | /** |
179 | | - * Package globals are initialized by package init functions, rather than initGlobals. |
180 | | - * A source-level constant is immutable, but an earlier initializer in the same |
181 | | - * package may still observe its default value before the constant is assigned. |
182 | | - * Configurable constants stay runtime globals until configuration resolution owns them. |
| 182 | + * A package constant is assigned at runtime in a package initializer. Replacing all reads is |
| 183 | + * valid only when no startup path can observe the default value before that emitted assignment. |
| 184 | + * Analyze the actual IM startup order once, including transitive calls and function references, |
| 185 | + * rather than trying to reconstruct translation and dependency order from source positions. |
183 | 186 | */ |
184 | | - private boolean isLiteralConstantGlobal(de.peeeq.wurstscript.ast.Element trace, ImProg prog) { |
185 | | - if (!(trace instanceof GlobalVarDef)) { |
186 | | - return false; |
| 187 | + private static LiteralConstantAnalysis analyzeLiteralConstants(ImTranslator trans, ImProg prog) { |
| 188 | + List<ImFunction> initializationOrder = trans.getInitializationOrder(); |
| 189 | + IdentityHashMap<ImStmt, Integer> statementRanks = new IdentityHashMap<>(); |
| 190 | + IdentityHashMap<ImVarWrite, Long> writeRanks = new IdentityHashMap<>(); |
| 191 | + for (int functionRank = 0; functionRank < initializationOrder.size(); functionRank++) { |
| 192 | + ImFunction initializer = initializationOrder.get(functionRank); |
| 193 | + for (int statementRank = 0; statementRank < initializer.getBody().size(); statementRank++) { |
| 194 | + statementRanks.put(initializer.getBody().get(statementRank), statementRank); |
| 195 | + } |
| 196 | + int[] writeRank = {0}; |
| 197 | + int currentFunctionRank = functionRank; |
| 198 | + initializer.getBody().accept(new ImStmt.DefaultVisitor() { |
| 199 | + @Override |
| 200 | + public void visit(ImSet write) { |
| 201 | + long rank = ((long) currentFunctionRank << 32) | (writeRank[0]++ & 0xffffffffL); |
| 202 | + writeRanks.put(write, rank); |
| 203 | + super.visit(write); |
| 204 | + } |
| 205 | + }); |
187 | 206 | } |
188 | | - GlobalVarDef global = (GlobalVarDef) trace; |
189 | | - if (!global.attrIsConstant() || global.hasAnnotation("@configurable")) { |
190 | | - return false; |
| 207 | + |
| 208 | + List<ImVar> candidates = new ArrayList<>(); |
| 209 | + IdentityHashMap<ImVar, ImVarWrite> replacementWrites = new IdentityHashMap<>(); |
| 210 | + for (ImVar var : prog.getGlobals()) { |
| 211 | + if (!isSourceConstant(var)) { |
| 212 | + continue; |
| 213 | + } |
| 214 | + ImVarWrite replacementWrite = null; |
| 215 | + ImExpr replacement = null; |
| 216 | + long replacementRank = Long.MAX_VALUE; |
| 217 | + boolean eligible = !var.attrWrites().isEmpty(); |
| 218 | + for (ImVarWrite write : var.attrWrites()) { |
| 219 | + ImFunction initializer = write.getNearestFunc(); |
| 220 | + ImStmt statement = initializer == null ? null |
| 221 | + : topLevelStatement((de.peeeq.wurstscript.jassIm.Element) write, initializer); |
| 222 | + Integer statementRank = statementRanks.get(statement); |
| 223 | + Long writeRank = writeRanks.get(write); |
| 224 | + if (statement == null || statementRank == null |
| 225 | + || writeRank == null || !isLiteral(write.getRight())) { |
| 226 | + eligible = false; |
| 227 | + break; |
| 228 | + } |
| 229 | + if (replacement == null) { |
| 230 | + replacement = write.getRight(); |
| 231 | + } else if (!replacement.structuralEquals(write.getRight())) { |
| 232 | + eligible = false; |
| 233 | + break; |
| 234 | + } |
| 235 | + if (writeRank < replacementRank) { |
| 236 | + replacementRank = writeRank; |
| 237 | + replacementWrite = write; |
| 238 | + } |
| 239 | + } |
| 240 | + if (eligible) { |
| 241 | + candidates.add(var); |
| 242 | + replacementWrites.put(var, replacementWrite); |
| 243 | + } |
191 | 244 | } |
192 | | - WPackage packageOfGlobal = packageOf(global); |
193 | | - if (packageOfGlobal == null) { |
194 | | - return true; |
| 245 | + if (candidates.isEmpty()) { |
| 246 | + return new LiteralConstantAnalysis(identitySet(), replacementWrites); |
195 | 247 | } |
196 | | - if (isInitializedLater(packageOfGlobal)) { |
197 | | - return false; |
| 248 | + |
| 249 | + Set<ImFunction> functions = identitySet(); |
| 250 | + functions.addAll(ImHelper.calculateFunctionsOfProg(prog)); |
| 251 | + functions.addAll(initializationOrder); |
| 252 | + IdentityHashMap<ImFunction, BitSet> readsByFunction = new IdentityHashMap<>(); |
| 253 | + IdentityHashMap<ImStmt, BitSet> readsByStatement = new IdentityHashMap<>(); |
| 254 | + IdentityHashMap<ImStmt, BitSet> writesByStatement = new IdentityHashMap<>(); |
| 255 | + BitSet unsafe = new BitSet(candidates.size()); |
| 256 | + |
| 257 | + for (int i = 0; i < candidates.size(); i++) { |
| 258 | + ImVar candidate = candidates.get(i); |
| 259 | + for (ImVarRead read : candidate.attrReads()) { |
| 260 | + ImFunction function = read.getNearestFunc(); |
| 261 | + if (function == null) { |
| 262 | + unsafe.set(i); |
| 263 | + continue; |
| 264 | + } |
| 265 | + readsByFunction.computeIfAbsent(function, ignored -> new BitSet()).set(i); |
| 266 | + ImStmt statement = topLevelStatement((de.peeeq.wurstscript.jassIm.Element) read, function); |
| 267 | + if (statement != null) { |
| 268 | + readsByStatement.computeIfAbsent(statement, ignored -> new BitSet()).set(i); |
| 269 | + } |
| 270 | + } |
| 271 | + for (ImVarWrite write : candidate.attrWrites()) { |
| 272 | + ImFunction function = write.getNearestFunc(); |
| 273 | + ImStmt statement = function == null ? null |
| 274 | + : topLevelStatement((de.peeeq.wurstscript.jassIm.Element) write, function); |
| 275 | + if (statement != null) { |
| 276 | + writesByStatement.computeIfAbsent(statement, ignored -> new BitSet()).set(i); |
| 277 | + } |
| 278 | + } |
| 279 | + } |
| 280 | + |
| 281 | + IdentityHashMap<ImFunction, Set<ImFunction>> callers = new IdentityHashMap<>(); |
| 282 | + ArrayDeque<ImFunction> undiscovered = new ArrayDeque<>(functions); |
| 283 | + while (!undiscovered.isEmpty()) { |
| 284 | + ImFunction caller = undiscovered.removeFirst(); |
| 285 | + readsByFunction.computeIfAbsent(caller, ignored -> new BitSet()); |
| 286 | + for (ImFunction callee : caller.calcUsedFunctions()) { |
| 287 | + if (callee == null) { |
| 288 | + continue; |
| 289 | + } |
| 290 | + callers.computeIfAbsent(callee, ignored -> identitySet()).add(caller); |
| 291 | + if (functions.add(callee)) { |
| 292 | + undiscovered.addLast(callee); |
| 293 | + } |
| 294 | + } |
| 295 | + } |
| 296 | + |
| 297 | + ArrayDeque<ImFunction> changedFunctions = new ArrayDeque<>(); |
| 298 | + Set<ImFunction> queued = identitySet(); |
| 299 | + for (Map.Entry<ImFunction, BitSet> entry : readsByFunction.entrySet()) { |
| 300 | + if (!entry.getValue().isEmpty()) { |
| 301 | + changedFunctions.addLast(entry.getKey()); |
| 302 | + queued.add(entry.getKey()); |
| 303 | + } |
198 | 304 | } |
199 | | - for (WEntity entity : packageOfGlobal.getElements()) { |
200 | | - if (entity instanceof InitBlock |
201 | | - && entity.attrSource().getLeftPos() < global.attrSource().getLeftPos()) { |
202 | | - return false; |
| 305 | + while (!changedFunctions.isEmpty()) { |
| 306 | + ImFunction callee = changedFunctions.removeFirst(); |
| 307 | + queued.remove(callee); |
| 308 | + BitSet calleeReads = readsByFunction.get(callee); |
| 309 | + for (ImFunction caller : callers.getOrDefault(callee, Collections.emptySet())) { |
| 310 | + BitSet callerReads = readsByFunction.computeIfAbsent(caller, ignored -> new BitSet()); |
| 311 | + int before = callerReads.cardinality(); |
| 312 | + callerReads.or(calleeReads); |
| 313 | + if (callerReads.cardinality() != before && queued.add(caller)) { |
| 314 | + changedFunctions.addLast(caller); |
| 315 | + } |
203 | 316 | } |
204 | 317 | } |
205 | | - for (ImVar other : prog.getGlobals()) { |
206 | | - if (other.getTrace() instanceof GlobalVarDef |
207 | | - && packageOf((GlobalVarDef) other.getTrace()) == packageOfGlobal |
208 | | - && other.getTrace().attrSource().getLeftPos() < global.attrSource().getLeftPos() |
209 | | - && !other.attrWrites().isEmpty()) { |
210 | | - return false; |
| 318 | + |
| 319 | + BitSet pending = new BitSet(candidates.size()); |
| 320 | + pending.set(0, candidates.size()); |
| 321 | + ImFunction config = trans.getConfFunc(); |
| 322 | + if (config != null) { |
| 323 | + scanStartupStatements(config.getBody(), pending, unsafe, readsByStatement, writesByStatement, |
| 324 | + readsByFunction); |
| 325 | + } |
| 326 | + if (!initializationOrder.isEmpty()) { |
| 327 | + scanStartupStatements(initializationOrder.get(0).getBody(), pending, unsafe, readsByStatement, |
| 328 | + writesByStatement, readsByFunction); |
| 329 | + scanMainPrefix(trans, initializationOrder, pending, unsafe, readsByStatement, writesByStatement, |
| 330 | + readsByFunction); |
| 331 | + } |
| 332 | + for (int i = 1; i < initializationOrder.size(); i++) { |
| 333 | + ImFunction initializer = initializationOrder.get(i); |
| 334 | + scanStartupStatements(initializer.getBody(), pending, unsafe, readsByStatement, writesByStatement, |
| 335 | + readsByFunction); |
| 336 | + } |
| 337 | + unsafe.or(pending); |
| 338 | + |
| 339 | + Set<ImVar> safeConstants = identitySet(); |
| 340 | + for (int i = 0; i < candidates.size(); i++) { |
| 341 | + if (!unsafe.get(i)) { |
| 342 | + safeConstants.add(candidates.get(i)); |
211 | 343 | } |
212 | 344 | } |
213 | | - return true; |
| 345 | + return new LiteralConstantAnalysis(safeConstants, replacementWrites); |
214 | 346 | } |
215 | 347 |
|
216 | | - @Nullable |
217 | | - private static WPackage packageOf(GlobalVarDef global) { |
218 | | - de.peeeq.wurstscript.ast.Element element = global; |
219 | | - while (element != null && !(element instanceof WPackage)) { |
220 | | - element = element.getParent(); |
| 348 | + private static void scanMainPrefix(ImTranslator trans, List<ImFunction> initializationOrder, |
| 349 | + BitSet pending, BitSet unsafe, |
| 350 | + Map<ImStmt, BitSet> readsByStatement, |
| 351 | + Map<ImStmt, BitSet> writesByStatement, |
| 352 | + Map<ImFunction, BitSet> readsByFunction) { |
| 353 | + Set<ImFunction> packageInitializers = identitySet(); |
| 354 | + packageInitializers.addAll(initializationOrder.subList(1, initializationOrder.size())); |
| 355 | + for (ImStmt statement : trans.getMainFunc().getBody()) { |
| 356 | + Set<ImFunction> usedFunctions = directlyUsedFunctions(statement); |
| 357 | + if (!Collections.disjoint(usedFunctions, packageInitializers)) { |
| 358 | + return; |
| 359 | + } |
| 360 | + scanStartupStatements(Collections.singleton(statement), pending, unsafe, readsByStatement, |
| 361 | + writesByStatement, readsByFunction); |
221 | 362 | } |
222 | | - return (WPackage) element; |
223 | 363 | } |
224 | 364 |
|
225 | | - private boolean isInitializedLater(WPackage target) { |
226 | | - if (!initLaterPackagesCollected) { |
227 | | - collectInitLaterPackages(target); |
228 | | - initLaterPackagesCollected = true; |
| 365 | + private static void scanStartupStatements(Collection<ImStmt> statements, BitSet pending, BitSet unsafe, |
| 366 | + Map<ImStmt, BitSet> readsByStatement, |
| 367 | + Map<ImStmt, BitSet> writesByStatement, |
| 368 | + Map<ImFunction, BitSet> readsByFunction) { |
| 369 | + for (ImStmt statement : statements) { |
| 370 | + BitSet reads = readsByStatement.containsKey(statement) |
| 371 | + ? (BitSet) readsByStatement.get(statement).clone() : new BitSet(); |
| 372 | + for (ImFunction used : directlyUsedFunctions(statement)) { |
| 373 | + BitSet functionReads = readsByFunction.get(used); |
| 374 | + if (functionReads != null) { |
| 375 | + reads.or(functionReads); |
| 376 | + } |
| 377 | + } |
| 378 | + reads.and(pending); |
| 379 | + unsafe.or(reads); |
| 380 | + BitSet writes = writesByStatement.get(statement); |
| 381 | + if (writes != null) { |
| 382 | + pending.andNot(writes); |
| 383 | + } |
229 | 384 | } |
230 | | - return initLaterPackages.contains(target); |
231 | 385 | } |
232 | 386 |
|
233 | | - private void collectInitLaterPackages(WPackage target) { |
234 | | - for (CompilationUnit unit : target.getModel()) { |
235 | | - for (WPackage candidate : unit.getPackages()) { |
236 | | - for (WImport imported : candidate.getImports()) { |
237 | | - if (imported.getIsInitLater() && imported.attrImportedPackage() instanceof WPackage importedPackage) { |
238 | | - initLaterPackages.add(importedPackage); |
| 387 | + private static Set<ImFunction> directlyUsedFunctions(ImStmt statement) { |
| 388 | + Set<ImFunction> result = identitySet(); |
| 389 | + statement.accept(new ImStmt.DefaultVisitor() { |
| 390 | + @Override |
| 391 | + public void visit(ImFunctionCall call) { |
| 392 | + super.visit(call); |
| 393 | + result.add(call.getFunc()); |
| 394 | + } |
| 395 | + |
| 396 | + @Override |
| 397 | + public void visit(ImFuncRef ref) { |
| 398 | + super.visit(ref); |
| 399 | + result.add(ref.getFunc()); |
| 400 | + } |
| 401 | + |
| 402 | + @Override |
| 403 | + public void visit(ImMethodCall call) { |
| 404 | + super.visit(call); |
| 405 | + if (call.getMethod().getImplementation() != null) { |
| 406 | + result.add(call.getMethod().getImplementation()); |
| 407 | + } |
| 408 | + for (ImMethod subMethod : call.getMethod().getSubMethods()) { |
| 409 | + if (subMethod.getImplementation() != null) { |
| 410 | + result.add(subMethod.getImplementation()); |
239 | 411 | } |
240 | 412 | } |
241 | 413 | } |
| 414 | + }); |
| 415 | + return result; |
| 416 | + } |
| 417 | + |
| 418 | + @Nullable |
| 419 | + @SuppressWarnings("ReferenceEquality") |
| 420 | + private static ImStmt topLevelStatement(de.peeeq.wurstscript.jassIm.Element element, ImFunction function) { |
| 421 | + de.peeeq.wurstscript.jassIm.Element current = element; |
| 422 | + while (current != null && current.getParent() != function.getBody()) { |
| 423 | + current = current.getParent(); |
| 424 | + } |
| 425 | + return current instanceof ImStmt ? (ImStmt) current : null; |
| 426 | + } |
| 427 | + |
| 428 | + private static boolean isSourceConstant(ImVar var) { |
| 429 | + if (!(var.getTrace() instanceof GlobalVarDef)) { |
| 430 | + return false; |
| 431 | + } |
| 432 | + if (var.getName().equals("MagicFunctions_compiletime") |
| 433 | + || var.getName().equals("MagicFunctions_isLua")) { |
| 434 | + // These values depend on compiler execution context/backend and are lowered by their |
| 435 | + // dedicated paths. They are not ordinary source literals for package-constant folding. |
| 436 | + return false; |
| 437 | + } |
| 438 | + GlobalVarDef global = (GlobalVarDef) var.getTrace(); |
| 439 | + return global.attrIsConstant() && !global.hasAnnotation("@configurable"); |
| 440 | + } |
| 441 | + |
| 442 | + private static <T> Set<T> identitySet() { |
| 443 | + return Collections.newSetFromMap(new IdentityHashMap<>()); |
| 444 | + } |
| 445 | + |
| 446 | + private static final class LiteralConstantAnalysis { |
| 447 | + private final Set<ImVar> safeConstants; |
| 448 | + private final Map<ImVar, ImVarWrite> replacementWrites; |
| 449 | + |
| 450 | + private LiteralConstantAnalysis(Set<ImVar> safeConstants, Map<ImVar, ImVarWrite> replacementWrites) { |
| 451 | + this.safeConstants = safeConstants; |
| 452 | + this.replacementWrites = replacementWrites; |
242 | 453 | } |
243 | 454 | } |
244 | 455 |
|
|
0 commit comments