From c20d50db5b60c25b123b7f5068290c5099f2e6b0 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 10 Jan 2026 10:30:20 +0800 Subject: [PATCH 1/5] Compilation Optimization --- hscript/Interp.hx | 25 +- hscript/Optimizer.hx | 771 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 794 insertions(+), 2 deletions(-) create mode 100644 hscript/Optimizer.hx diff --git a/hscript/Interp.hx b/hscript/Interp.hx index e6627be8..8230d6c7 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -147,6 +147,16 @@ class Interp { public var allowStaticVariables:Bool = false; public var allowPublicVariables:Bool = false; + public var optimizeEnabled:Bool = true; + public var optimizeLevel:Int = 4; + + public var enableConstantFolding:Bool = true; + public var enableExpressionSimplification:Bool = true; + public var enableDeadCodeElimination:Bool = true; + public var enableBranchOptimization:Bool = true; + + public var optimizerDebug:Bool = true; + // TODO: move this to an external class public var importBlocklist:Array = [ // "flixel.FlxG" @@ -154,6 +164,8 @@ class Interp { var usingHandler:UsingHandler; + var optimizer:Optimizer; + #if hscriptPos var curExpr:Expr; #end @@ -163,6 +175,7 @@ class Interp { declared = []; resetVariables(); initOps(); + optimizer = new Optimizer(); } private function resetVariables():Void { @@ -533,7 +546,15 @@ class Interp { depth = 0; locals = new Map(); declared = []; - return exprReturn(expr); + optimizer.enabled = optimizeEnabled; + optimizer.optimizeLevel = optimizeLevel; + optimizer.enableConstantFolding = enableConstantFolding; + optimizer.enableExpressionSimplification = enableExpressionSimplification; + optimizer.enableDeadCodeElimination = enableDeadCodeElimination; + optimizer.enableBranchOptimization = enableBranchOptimization; + optimizer.debug = optimizerDebug; + var optimizedExpr = optimizer.optimize(expr); + return exprReturn(optimizedExpr); } public var printCallStack:Bool = false; @@ -1651,4 +1672,4 @@ class Interp { return Type.createInstance(c, args); } -} +} \ No newline at end of file diff --git a/hscript/Optimizer.hx b/hscript/Optimizer.hx new file mode 100644 index 00000000..163b8c6b --- /dev/null +++ b/hscript/Optimizer.hx @@ -0,0 +1,771 @@ +package hscript; + +import hscript.Expr; +import StringTools; + +class Optimizer { + public var enabled:Bool = true; + public var optimizeLevel:Int = 2; + + public var enableConstantFolding:Bool = true; + public var enableExpressionSimplification:Bool = true; + public var enableDeadCodeElimination:Bool = true; + public var enableBranchOptimization:Bool = true; + + public var debug:Bool = false; + public var debugPrinter:Printer; + + public function new() { + debugPrinter = new Printer(); + } + + public function optimize(expr:Expr):Expr { + if (!enabled) return expr; + + if (debug) { + trace("=== HScript Optimizer Debug ==="); + trace("Optimization Level: " + optimizeLevel); + trace("Constant Folding: " + enableConstantFolding); + trace("Expression Simplification: " + enableExpressionSimplification); + trace("Dead Code Elimination: " + enableDeadCodeElimination); + trace("Branch Optimization: " + enableBranchOptimization); + trace("\n--- Original Code ---"); + trace(debugPrinter.exprToString(expr)); + } + + var result = expr; + var startTime = Sys.time(); + var passTimes:Array = []; + + for (i in 0...optimizeLevel) { + var passStart = Sys.time(); + var beforeStr = debugPrinter.exprToString(result); + result = optimizeOnce(result); + var afterStr = debugPrinter.exprToString(result); + var passTime = (Sys.time() - passStart) * 1000; + passTimes.push(passTime); + + if (debug) { + var optimizedCount = beforeStr.length - afterStr.length; + trace("\n--- After Pass " + (i + 1) + " (" + Math.round(passTime * 100) / 100 + " ms, reduced " + optimizedCount + " chars) ---"); + trace(afterStr); + } + + if (beforeStr == afterStr) { + if (debug) { + trace("\n--- No further optimizations possible after pass " + (i + 1) + " ---"); + } + break; + } + } + + var totalTime = (Sys.time() - startTime) * 1000; + + if (debug) { + trace("\n--- Optimization Complete ---"); + trace("Total passes: " + passTimes.length); + trace("Total time: " + Math.round(totalTime * 100) / 100 + " ms"); + if (passTimes.length > 1) { + trace("Average per pass: " + Math.round((totalTime / passTimes.length) * 100) / 100 + " ms"); + } + trace("=== End Debug ===\n"); + } + + return result; + } + + private function optimizeOnce(expr:Expr):Expr { + return switch (Tools.expr(expr)) { + case EConst(_): return expr; + case EIdent(_): return expr; + case EPackage(_): return expr; + case EImport(_): return expr; + case EClass(name, fields, extend, interfaces, isFinal): + var optimizedFields = [for (f in fields) optimizeOnce(f)]; + return Tools.mk(EClass(name, optimizedFields, extend, interfaces, isFinal), expr); + case EVar(n, t, e, isPublic, isStatic, isPrivate, isFinal, isInline, get, set, isVar): + var optimizedExpr = e != null ? optimizeOnce(e) : null; + return Tools.mk(EVar(n, t, optimizedExpr, isPublic, isStatic, isPrivate, isFinal, isInline, get, set, isVar), expr); + case EParent(e): + var optimized = optimizeOnce(e); + return Tools.mk(EParent(optimized), expr); + case EBlock(exprs): + var optimizedExprs = [for (e in exprs) optimizeOnce(e)]; + var cleanedExprs = enableDeadCodeElimination ? removeDeadCode(optimizedExprs) : optimizedExprs; + if (cleanedExprs.length == 1) { + return cleanedExprs[0]; + } else { + return Tools.mk(EBlock(cleanedExprs), expr); + } + case EField(e, f, s): + var optimized = optimizeOnce(e); + return Tools.mk(EField(optimized, f, s), expr); + case EBinop(op, e1, e2): + var optimized1 = optimizeOnce(e1); + var optimized2 = optimizeOnce(e2); + var folded = enableConstantFolding ? tryFoldConstant(op, optimized1, optimized2) : null; + if (folded != null) { + return folded; + } else { + var simplified = enableExpressionSimplification ? trySimplifyBinop(op, optimized1, optimized2) : null; + if (simplified != null) { + return simplified; + } else { + return Tools.mk(EBinop(op, optimized1, optimized2), expr); + } + } + case EUnop(op, prefix, e): + var optimized = optimizeOnce(e); + var folded = enableConstantFolding ? tryFoldUnop(op, prefix, optimized) : null; + if (folded != null) { + return folded; + } else { + var simplified = enableExpressionSimplification ? trySimplifyUnop(op, prefix, optimized) : null; + if (simplified != null) { + return simplified; + } else { + return Tools.mk(EUnop(op, prefix, optimized), expr); + } + } + case ECall(e, params): + var optimizedExpr = optimizeOnce(e); + var optimizedParams = [for (p in params) optimizeOnce(p)]; + return Tools.mk(ECall(optimizedExpr, optimizedParams), expr); + case EIf(cond, e1, e2): + var optimizedCond = optimizeOnce(cond); + var optimizedE1 = optimizeOnce(e1); + var optimizedE2 = e2 != null ? optimizeOnce(e2) : null; + + if (enableBranchOptimization) { + var optimized = tryOptimizeIf(optimizedCond, optimizedE1, optimizedE2); + if (optimized != null) { + return optimized; + } + } + return Tools.mk(EIf(optimizedCond, optimizedE1, optimizedE2), expr); + case EWhile(cond, e): + var optimizedCond = optimizeOnce(cond); + var optimizedBody = optimizeOnce(e); + return Tools.mk(EWhile(optimizedCond, optimizedBody), expr); + case EDoWhile(cond, e): + var optimizedCond = optimizeOnce(cond); + var optimizedBody = optimizeOnce(e); + return Tools.mk(EDoWhile(optimizedCond, optimizedBody), expr); + case EFor(v, it, e, ithv): + var optimizedIt = optimizeOnce(it); + var optimizedBody = optimizeOnce(e); + return Tools.mk(EFor(v, optimizedIt, optimizedBody, ithv), expr); + case EBreak: return expr; + case EContinue: return expr; + case EFunction(args, e, name, ret, isPublic, isStatic, isOverride, isPrivate, isFinal, isInline): + var optimizedBody = optimizeOnce(e); + return Tools.mk(EFunction(args, optimizedBody, name, ret, isPublic, isStatic, isOverride, isPrivate, isFinal, isInline), expr); + case EReturn(e): + var optimized = e != null ? optimizeOnce(e) : null; + return Tools.mk(EReturn(optimized), expr); + case EArray(e, index): + var optimizedExpr = optimizeOnce(e); + var optimizedIndex = optimizeOnce(index); + return Tools.mk(EArray(optimizedExpr, optimizedIndex), expr); + case EArrayDecl(exprs, wantedType): + var optimizedExprs = [for (e in exprs) optimizeOnce(e)]; + return Tools.mk(EArrayDecl(optimizedExprs, wantedType), expr); + case ENew(cl, params, paramType): + var optimizedParams = [for (p in params) optimizeOnce(p)]; + return Tools.mk(ENew(cl, optimizedParams, paramType), expr); + case EThrow(e): + var optimized = optimizeOnce(e); + return Tools.mk(EThrow(optimized), expr); + case ETry(e, v, t, ecatch): + var optimizedTry = optimizeOnce(e); + var optimizedCatch = optimizeOnce(ecatch); + return Tools.mk(ETry(optimizedTry, v, t, optimizedCatch), expr); + case EObject(fl): + var optimizedFields = [for (f in fl) {name: f.name, e: optimizeOnce(f.e)}]; + var objectFields:Array = []; + for (f in optimizedFields) { + objectFields.push({name: f.name, e: f.e}); + } + return Tools.mk(EObject(objectFields), expr); + case ETernary(cond, e1, e2): + var optimizedCond = optimizeOnce(cond); + var optimizedE1 = optimizeOnce(e1); + var optimizedE2 = optimizeOnce(e2); + + if (enableBranchOptimization) { + var optimized = tryOptimizeTernary(optimizedCond, optimizedE1, optimizedE2); + if (optimized != null) { + return optimized; + } + } + return Tools.mk(ETernary(optimizedCond, optimizedE1, optimizedE2), expr); + case ESwitch(e, cases, defaultExpr): + var optimizedExpr = optimizeOnce(e); + var optimizedCases = [for (c in cases) { + values: [for (v in c.values) optimizeOnce(v)], + expr: optimizeOnce(c.expr) + }]; + var switchCases:Array = []; + for (c in optimizedCases) { + switchCases.push({values: c.values, expr: c.expr}); + } + var optimizedDefault = defaultExpr != null ? optimizeOnce(defaultExpr) : null; + return Tools.mk(ESwitch(optimizedExpr, switchCases, optimizedDefault), expr); + case EMeta(name, args, e): + var optimizedArgs = args != null ? [for (a in args) optimizeOnce(a)] : null; + var optimizedExpr = optimizeOnce(e); + return Tools.mk(EMeta(name, optimizedArgs, optimizedExpr), expr); + case ECheckType(e, t): + var optimized = optimizeOnce(e); + return Tools.mk(ECheckType(optimized, t), expr); + case EEnum(en, isAbstract): + return Tools.mk(EEnum(en, isAbstract), expr); + case ECast(e, t): + var optimized = optimizeOnce(e); + return Tools.mk(ECast(optimized, t), expr); + case ERegex(e, f): + return Tools.mk(ERegex(e, f), expr); + } + } + + private function tryFoldConstant(op:String, e1:Expr, e2:Expr):Null { + var c1 = getConstValue(e1); + var c2 = getConstValue(e2); + + if (c1 == null || c2 == null) return null; + + var result:Dynamic = null; + + try { + switch (op) { + case "+": result = c1 + c2; + case "-": result = c1 - c2; + case "*": result = c1 * c2; + case "/": + if (c2 == 0) return null; + result = c1 / c2; + case "%": + if (c2 == 0) return null; + result = c1 % c2; + case "&": result = c1 & c2; + case "|": result = c1 | c2; + case "^": result = c1 ^ c2; + case "<<": result = c1 << c2; + case ">>": result = c1 >> c2; + case ">>>": result = c1 >>> c2; + case "==": result = c1 == c2; + case "!=": result = c1 != c2; + case ">": result = c1 > c2; + case "<": result = c1 < c2; + case ">=": result = c1 >= c2; + case "<=": result = c1 <= c2; + case "&&": result = (c1 == true) && (c2 == true); + case "||": result = (c1 == true) || (c2 == true); + default: return null; + } + } catch (e:Dynamic) { + return null; + } + + return makeConst(result, e1); + } + + private function tryFoldUnop(op:String, prefix:Bool, e:Expr):Null { + var c = getConstValue(e); + if (c == null) return null; + + var result:Dynamic = null; + + try { + switch (op) { + case "!": result = !(c == true); + case "-": result = -c; + case "~": result = ~c; + default: return null; + } + } catch (e:Dynamic) { + return null; + } + + return makeConst(result, e); + } + + private function trySimplifyBinop(op:String, e1:Expr, e2:Expr):Null { + var c1 = getConstValue(e1); + var c2 = getConstValue(e2); + + switch (op) { + case "+": + if (c1 != null && c1 == 0 && !isStringExpr(e1)) return e2; + if (c2 != null && c2 == 0 && !isStringExpr(e2)) return e1; + + if (c1 != null && c2 != null) { + return makeConst(c1 + c2, e1); + } + + if (c1 != null) { + switch (Tools.expr(e2)) { + case EBinop("+", e2_1, e2_2): + var c2_1 = getConstValue(e2_1); + var c2_2 = getConstValue(e2_2); + if (c2_1 != null && c2_2 != null) { + var combinedConst = c1 + c2_1 + c2_2; + return Tools.mk(EBinop("+", e2_1, makeConst(c2_2 + c1, e2_2)), e2); + } + if (c2_1 != null) { + var combinedConst = c1 + c2_1; + return Tools.mk(EBinop("+", makeConst(combinedConst, e2_1), e2_2), e2); + } + case EBinop("-", e2_1, e2_2): + var c2_1 = getConstValue(e2_1); + var c2_2 = getConstValue(e2_2); + if (c2_1 != null && c2_2 != null) { + var combinedConst = c1 - c2_1 + c2_2; + return Tools.mk(EBinop("+", e2_1, makeConst(c2_2 + c1, e2_2)), e2); + } + if (c2_1 != null) { + var combinedConst = c1 - c2_1; + return Tools.mk(EBinop("+", makeConst(combinedConst, e2_1), e2_2), e2); + } + default: + } + } + + if (c2 != null) { + switch (Tools.expr(e1)) { + case EBinop("+", e1_1, e1_2): + var c1_1 = getConstValue(e1_1); + var c1_2 = getConstValue(e1_2); + if (c1_1 != null && c1_2 != null) { + var combinedConst = c1_1 + c1_2 + c2; + return Tools.mk(EBinop("+", e1_1, makeConst(c1_2 + c2, e1_2)), e1); + } + if (c1_2 != null) { + return Tools.mk(EBinop("+", e1, makeConst(c2, e2)), e1); + } + case EBinop("-", e1_1, e1_2): + var c1_1 = getConstValue(e1_1); + var c1_2 = getConstValue(e1_2); + if (c1_1 != null && c1_2 != null) { + var combinedConst = c1_1 - c1_2 + c2; + return Tools.mk(EBinop("-", e1_1, makeConst(c1_2 - c2, e1_2)), e1); + } + if (c1_2 != null) { + if (op == "+") { + return Tools.mk(EBinop("-", e1_1, makeConst(c1_2 - c2, e1_2)), e1); + } else { + return Tools.mk(EBinop("-", e1_1, makeConst(c1_2 + c2, e1_2)), e1); + } + } + case EBinop("*", e1_1, e1_2): + var c1_1 = getConstValue(e1_1); + var c1_2 = getConstValue(e1_2); + if (c1_1 != null && c1_2 != null) { + var combinedConst = c1_1 * c1_2; + return Tools.mk(EBinop("*", e1_1, makeConst(combinedConst, e1_2)), e1); + } + case EBinop("/", e1_1, e1_2): + var c1_1 = getConstValue(e1_1); + var c1_2 = getConstValue(e1_2); + if (c1_1 != null && c1_2 != null) { + var combinedConst = c1_1 / c1_2; + return Tools.mk(EBinop("/", e1_1, makeConst(combinedConst, e1_2)), e1); + } + default: + } + } + case "-": + if (c2 != null && c2 == 0) return e1; + + if (c1 != null && c2 != null) { + return makeConst(c1 - c2, e1); + } + + if (c1 != null) { + switch (Tools.expr(e2)) { + case EBinop("-", e2_1, e2_2): + var c2_1 = getConstValue(e2_1); + var c2_2 = getConstValue(e2_2); + if (c2_1 != null && c2_2 != null) { + var combinedConst = c1 - c2_1 - c2_2; + return Tools.mk(EBinop("-", e2_1, makeConst(c2_2 - c1, e2_2)), e2); + } + if (c2_1 != null) { + var combinedConst = c1 - c2_1; + return Tools.mk(EBinop("-", makeConst(combinedConst, e2_1), e2_2), e2); + } + case EBinop("/", e2_1, e2_2): + var c2_1 = getConstValue(e2_1); + var c2_2 = getConstValue(e2_2); + if (c2_1 != null && c2_2 != null) { + var combinedConst = c1 - c2_1 / c2_2; + return Tools.mk(EBinop("-", e2_1, makeConst(c2_2, e2_2)), e2); + } + if (c2_1 != null) { + var combinedConst = c1 - c2_1; + return Tools.mk(EBinop("-", makeConst(combinedConst, e2_1), e2_2), e2); + } + default: + } + } + + if (c2 != null) { + switch (Tools.expr(e1)) { + case EBinop("+", e1_1, e1_2): + var c1_2 = getConstValue(e1_2); + if (c1_2 != null) { + var combinedConst = c1_2 - c2; + return Tools.mk(EBinop("+", e1_1, makeConst(combinedConst, e1_2)), e1); + } + case EBinop("-", e1_1, e1_2): + var c1_2 = getConstValue(e1_2); + if (c1_2 != null) { + var combinedConst = c1_2 + c2; + return Tools.mk(EBinop("-", e1_1, makeConst(combinedConst, e1_2)), e1); + } + default: + } + } + case "*": + if (c1 != null && c1 == 1) return e2; + if (c2 != null && c2 == 1) return e1; + if ((c1 != null && c1 == 0) || (c2 != null && c2 == 0)) return makeConst(0, e1); + + if (c1 != null && c2 != null) { + return makeConst(c1 * c2, e1); + } + + if (c1 != null) { + switch (Tools.expr(e2)) { + case EBinop("*", e2_1, e2_2): + var c2_1 = getConstValue(e2_1); + var c2_2 = getConstValue(e2_2); + if (c2_1 != null && c2_2 != null) { + var combinedConst = c1 * c2_1 * c2_2; + return Tools.mk(EBinop("*", e2_1, makeConst(c2_2 * c1, e2_2)), e2); + } + if (c2_1 != null) { + var combinedConst = c1 * c2_1; + return Tools.mk(EBinop("*", makeConst(combinedConst, e2_1), e2_2), e2); + } + default: + } + } + + if (c2 != null) { + switch (Tools.expr(e1)) { + case EBinop("*", e1_1, e1_2): + var c1_1 = getConstValue(e1_1); + var c1_2 = getConstValue(e1_2); + if (c1_1 != null && c1_2 != null) { + var combinedConst = c1_1 * c1_2 * c2; + return Tools.mk(EBinop("*", e1_1, makeConst(c1_2 * c2, e1_2)), e1); + } + if (c1_2 != null) { + var combinedConst = c1_2 * c2; + return Tools.mk(EBinop("*", e1_1, makeConst(combinedConst, e1_2)), e1); + } + default: + } + } + case "/": + if (c2 != null && c2 == 1) return e1; + if (c2 != null && c2 != 0) { + var multiplier = 1.0 / c2; + return Tools.mk(EBinop("*", e1, makeConst(multiplier, e2)), e2); + } + + if (c1 != null) { + switch (Tools.expr(e2)) { + case EBinop("*", e2_1, e2_2): + var c2_1 = getConstValue(e2_1); + var c2_2 = getConstValue(e2_2); + if (c2_1 != null && c2_2 != null) { + var combinedConst = c2_1 * c2_2; + var multiplier = 1.0 / combinedConst; + return Tools.mk(EBinop("*", e1, makeConst(multiplier, e2_1)), e2_2); + } + if (c2_1 != null) { + var multiplier = 1.0 / c2_1; + return Tools.mk(EBinop("*", e1, makeConst(multiplier, e2_1)), e2_2); + } + case EBinop("/", e2_1, e2_2): + var c2_1 = getConstValue(e2_1); + var c2_2 = getConstValue(e2_2); + if (c2_1 != null && c2_2 != null) { + var combinedConst = c2_1 / c2_2; + var multiplier = 1.0 / combinedConst; + return Tools.mk(EBinop("*", e1, makeConst(multiplier, e2_1)), e2_2); + } + default: + } + } + + if (c2 != null) { + switch (Tools.expr(e1)) { + case EBinop("*", e1_1, e1_2): + var c1_1 = getConstValue(e1_1); + var c1_2 = getConstValue(e1_2); + if (c1_1 != null && c1_2 != null) { + var combinedConst = c1_1 * c1_2; + var multiplier = 1.0 / c2; + return Tools.mk(EBinop("*", e1_1, makeConst(c1_2 * multiplier, e1_2)), e2); + } + if (c1_2 != null) { + var multiplier = 1.0 / c2; + return Tools.mk(EBinop("/", e1_1, makeConst(c1_2 / c2, e1_2)), e2); + } + case EBinop("/", e1_1, e1_2): + var c1_1 = getConstValue(e1_1); + var c1_2 = getConstValue(e1_2); + if (c1_1 != null && c1_2 != null) { + var combinedConst = c1_1 / c1_2; + var multiplier = 1.0 / combinedConst; + return Tools.mk(EBinop("*", e1_1, makeConst(multiplier, e1_2)), e2); + } + if (c1_2 != null) { + var multiplier = 1.0 / c1_2; + return Tools.mk(EBinop("/", e1_1, makeConst(multiplier, e1_2)), e2); + } + if (c1_1 != null) { + var multiplier = 1.0 / c1_1; + return Tools.mk(EBinop("/", e1_1, makeConst(multiplier, e1_2)), e2); + } + default: + } + } + case "&&": + if (c1 != null && c1 == false) return makeConst(false, e1); + if (c2 != null && c2 == false) return makeConst(false, e2); + if (c1 != null && c1 == true) return e2; + if (c2 != null && c2 == true) return e1; + case "||": + if (c1 != null && c1 == true) return makeConst(true, e1); + if (c2 != null && c2 == true) return makeConst(true, e2); + if (c1 != null && c1 == false) return e2; + if (c2 != null && c2 == false) return e1; + case "%": + if (c2 != null && c2 == 1) return makeConst(0, e1); + if (c1 != null && c2 != null) { + return makeConst(c1 % c2, e1); + } + if (c2 != null && isPowerOfTwo(c2) && c1 != null && c1 >= 0) { + var mask = Std.int(c2) - 1; + return Tools.mk(EBinop("&", e1, makeConst(mask, e2)), e1); + } + } + + return null; + } + + private function trySimplifyUnop(op:String, prefix:Bool, e:Expr):Null { + switch (Tools.expr(e)) { + case EUnop(op2, prefix2, e2): + if (op == op2 && prefix != prefix2) { + return e2; + } + default: + } + + return null; + } + + private function tryOptimizeIf(cond:Expr, e1:Expr, e2:Expr):Null { + var c = getConstValue(cond); + + if (c != null && isPureConstant(cond)) { + if (c == true) { + return e1; + } else { + return e2 != null ? e2 : makeConst(null, cond); + } + } + + if (exprEquals(e1, e2)) { + return e1; + } + + return null; + } + + private function tryOptimizeTernary(cond:Expr, e1:Expr, e2:Expr):Null { + var c = getConstValue(cond); + + if (c != null && isPureConstant(cond)) { + if (c == true) { + return e1; + } else { + return e2; + } + } + + if (exprEquals(e1, e2)) { + return e1; + } + + return null; + } + + private function removeDeadCode(exprs:Array):Array { + var result:Array = []; + var foundTerminator = false; + + for (e in exprs) { + if (foundTerminator) { + if (hasSideEffects(e)) { + result.push(e); + } + } else { + result.push(e); + + if (isUnconditionalTerminator(e)) { + foundTerminator = true; + } + } + } + + return result; + } + + private function isUnconditionalTerminator(expr:Expr):Bool { + return switch (Tools.expr(expr)) { + case EReturn(_): true; + case EThrow(_): true; + default: false; + } + } + + private function isTerminator(expr:Expr):Bool { + return switch (Tools.expr(expr)) { + case EBreak, EContinue, EReturn(_): true; + default: false; + } + } + + private function getConstValue(expr:Expr):Null { + return switch (Tools.expr(expr)) { + case EConst(CInt(v)): v; + case EConst(CFloat(v)): v; + case EConst(CString(v)): v; + default: null; + } + } + + private function makeConst(value:Dynamic, original:Expr):Expr { + if (Std.isOfType(value, Int)) { + return Tools.mk(EConst(CInt(value)), original); + } else if (Std.isOfType(value, Float)) { + return Tools.mk(EConst(CFloat(value)), original); + } else if (Std.isOfType(value, String)) { + return Tools.mk(EConst(CString(value)), original); + } else if (value == null) { + return Tools.mk(EConst(CInt(0)), original); + } else { + return Tools.mk(EConst(CFloat(value)), original); + } + } + + private function exprEquals(e1:Expr, e2:Expr):Bool { + if (e1 == e2) return true; + if (e1 == null || e2 == null) return false; + + return switch ([Tools.expr(e1), Tools.expr(e2)]) { + case [EConst(c1), EConst(c2)]: + switch ([c1, c2]) { + case [CInt(v1), CInt(v2)]: v1 == v2; + case [CFloat(v1), CFloat(v2)]: v1 == v2; + case [CString(v1), CString(v2)]: v1 == v2; + default: false; + } + case [EIdent(v1), EIdent(v2)]: v1 == v2; + default: false; + } + } + + private function hasSideEffects(expr:Expr):Bool { + return switch (Tools.expr(expr)) { + case ECall(_, _): true; + case EBinop("=", _, _): true; + case EBinop(op, _, _) if (StringTools.endsWith(op, "=")): true; + case EUnop("++", _, _) | EUnop("--", _, _): true; + case EField(e, _, _): hasSideEffects(e); + case EArray(e, index): hasSideEffects(e) || hasSideEffects(index); + case ENew(_, _, _): true; + case EThrow(_): true; + case EVar(_, _, e, _, _, _, _, _, _, _, _): + e != null ? hasSideEffects(e) : false; + case EObject(fields): + for (f in fields) { + if (hasSideEffects(f.e)) return true; + } + false; + case EArrayDecl(exprs): + for (e in exprs) { + if (hasSideEffects(e)) return true; + } + false; + case ESwitch(e, cases, defaultExpr): + if (hasSideEffects(e)) return true; + for (c in cases) { + for (v in c.values) { + if (hasSideEffects(v)) return true; + } + if (hasSideEffects(c.expr)) return true; + } + if (defaultExpr != null && hasSideEffects(defaultExpr)) return true; + false; + case EIf(cond, e1, e2): + hasSideEffects(cond) || hasSideEffects(e1) || (e2 != null && hasSideEffects(e2)); + case EWhile(cond, e) | EDoWhile(cond, e): + hasSideEffects(cond) || hasSideEffects(e); + case EFor(_, it, e, _): + hasSideEffects(it) || hasSideEffects(e); + case ETernary(cond, e1, e2): + hasSideEffects(cond) || hasSideEffects(e1) || hasSideEffects(e2); + case EFunction(_, _, _, _, _, _, _, _, _, _): false; + case EConst(_): false; + case EIdent(_): false; + case EParent(e): hasSideEffects(e); + case EBlock(exprs): + for (e in exprs) { + if (hasSideEffects(e)) return true; + } + false; + case EReturn(e): + e != null ? hasSideEffects(e) : false; + case EBreak, EContinue: false; + case ECheckType(e, _): hasSideEffects(e); + case ECast(e, _): hasSideEffects(e); + case EMeta(_, _, e): hasSideEffects(e); + case EPackage(_): false; + case EImport(_): false; + case EClass(_, _, _, _, _): false; + case EEnum(_, _): false; + default: true; + } + } + + private function isPureConstant(expr:Expr):Bool { + return switch (Tools.expr(expr)) { + case EConst(_): true; + case EParent(e): isPureConstant(e); + default: false; + } + } + + private function isStringExpr(expr:Expr):Bool { + return switch (Tools.expr(expr)) { + case EConst(CString(_)): true; + case EParent(e): isStringExpr(e); + case EBinop("+", e1, e2): isStringExpr(e1) || isStringExpr(e2); + default: false; + } + } + + private function isPowerOfTwo(n:Float):Bool { + if (n != Math.floor(n)) return false; + var i = Std.int(n); + if (i <= 0) return false; + return (i & (i - 1)) == 0; + } +} \ No newline at end of file From 39990f30dcc59d7e588246e2a25ce289a4c4d8dc Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 10 Jan 2026 10:31:19 +0800 Subject: [PATCH 2/5] Update Interp.hx --- hscript/Interp.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 8230d6c7..da5eaf39 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -162,9 +162,9 @@ class Interp { // "flixel.FlxG" ]; - var usingHandler:UsingHandler; + public var optimizer:Optimizer; - var optimizer:Optimizer; + var usingHandler:UsingHandler; #if hscriptPos var curExpr:Expr; From efdd6be94552a849f55cb7b0f740b233c24d41c7 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 10 Jan 2026 14:44:04 +0800 Subject: [PATCH 3/5] Add the Optimizer preprocessor directive --- hscript/Interp.hx | 2 +- hscript/Parser.hx | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/hscript/Interp.hx b/hscript/Interp.hx index da5eaf39..ecac0482 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -155,7 +155,7 @@ class Interp { public var enableDeadCodeElimination:Bool = true; public var enableBranchOptimization:Bool = true; - public var optimizerDebug:Bool = true; + public var optimizerDebug:Bool = false; // TODO: move this to an external class public var importBlocklist:Array = [ diff --git a/hscript/Parser.hx b/hscript/Parser.hx index e0a0cbf6..362c7f5c 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -207,6 +207,7 @@ class Parser { push(tk); parseFullExpr(a); } + applyOptimizerSettings(); return if( a.length == 1 ) a[0] else mk(EBlock(a),0); } @@ -2559,6 +2560,31 @@ class Parser { public var preprocesorValues(get, set) : Map; inline function get_preprocesorValues() return preprocessorValues; inline function set_preprocesorValues(v) return preprocessorValues = v; + + function applyOptimizerSettings() { + if( optimizer == null ) return; + + var level = preprocessorValues.get("OPTIMIZE_LEVEL"); + if( level != null ) optimizer.optimizeLevel = level; + + var enabled = preprocessorValues.get("OPTIMIZE_ENABLED"); + if( enabled != null ) optimizer.enabled = enabled; + + var constantFolding = preprocessorValues.get("OPTIMIZE_CONSTANT_FOLDING"); + if( constantFolding != null ) optimizer.enableConstantFolding = constantFolding; + + var simplification = preprocessorValues.get("OPTIMIZE_SIMPLIFICATION"); + if( simplification != null ) optimizer.enableExpressionSimplification = simplification; + + var deadCode = preprocessorValues.get("OPTIMIZE_DEAD_CODE"); + if( deadCode != null ) optimizer.enableDeadCodeElimination = deadCode; + + var branch = preprocessorValues.get("OPTIMIZE_BRANCH"); + if( branch != null ) optimizer.enableBranchOptimization = branch; + + var debug = preprocessorValues.get("OPTIMIZE_DEBUG"); + if( debug != null ) optimizer.debug = debug; + } } @:structInit From cb2c708f08b6ea49a00f7bbf804bf6851e12993e Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 13 Jan 2026 07:49:30 +0800 Subject: [PATCH 4/5] no --- hscript/Interp.hx | 30 +++++++++++++++--------------- hscript/Parser.hx | 38 +++++++------------------------------- 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/hscript/Interp.hx b/hscript/Interp.hx index ecac0482..a9e28e27 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -123,13 +123,13 @@ class Interp { public var warnHandler:Error->Void; public var importFailedCallback:Array->Null->Bool; - public var customClasses:Map; - public var variables:Map; - public var publicVariables:Map; - public var staticVariables:Map; + public var customClasses:StringMap ; + public var variables:StringMap; + public var publicVariables:StringMap; + public var staticVariables:StringMap; // warning can be null - public var locals:Map; + public var locals:StringMap; var binops:StringMapExpr->Dynamic>; var depth:Int = 0; @@ -163,7 +163,7 @@ class Interp { ]; public var optimizer:Optimizer; - + var usingHandler:UsingHandler; #if hscriptPos @@ -171,7 +171,7 @@ class Interp { #end public function new() { - locals = new Map(); + locals = new StringMap(); declared = []; resetVariables(); initOps(); @@ -179,10 +179,10 @@ class Interp { } private function resetVariables():Void { - customClasses = new Map(); - variables = new Map(); - publicVariables = new Map(); - staticVariables = new Map(); + customClasses = new StringMap(); + variables = new StringMap(); + publicVariables = new StringMap(); + staticVariables = new StringMap(); usingHandler = new UsingHandler(); @@ -544,7 +544,7 @@ class Interp { public function execute(expr:Expr):Dynamic { depth = 0; - locals = new Map(); + locals = new StringMap(); declared = []; optimizer.enabled = optimizeEnabled; optimizer.optimizeLevel = optimizeLevel; @@ -969,11 +969,11 @@ class Interp { if (depth == 0) { if(allowStaticVariables && isStatic == true) { if(!staticVariables.exists(n)) // make it so it only sets it once - staticVariables.set(n, locals[n].r); + staticVariables.set(n, locals.get(n).r); } else if(allowPublicVariables && isPublic == true) { - publicVariables.set(n, locals[n].r); + publicVariables.set(n, locals.get(n).r); } else { - variables.set(n, locals[n].r); + variables.set(n, locals.get(n).r); } } return null; diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 362c7f5c..d8b68de9 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -20,6 +20,8 @@ * DEALINGS IN THE SOFTWARE. */ package hscript; + +import haxe.ds.StringMap; import hscript.Expr; using StringTools; @@ -60,13 +62,13 @@ class Parser { public var line : Int; public var opChars : String; public var identChars : String; - public var opPriority : Map; - public var opRightAssoc : Map; + public var opPriority : StringMap; + public var opRightAssoc : StringMap; /** allows to check for #if / #else in code **/ - public var preprocessorValues : Map = new Map(); + public var preprocessorValues : StringMap = new StringMap(); /** activate JSON compatiblity @@ -141,8 +143,8 @@ class Parser { ["->", "??"], ["is"] ]; - opPriority = new Map(); - opRightAssoc = new Map(); + opPriority = new StringMap(); + opRightAssoc = new StringMap(); #if (haxe >= "4.0.0") for(i => p in priorities) for(x in p) { @@ -207,7 +209,6 @@ class Parser { push(tk); parseFullExpr(a); } - applyOptimizerSettings(); return if( a.length == 1 ) a[0] else mk(EBlock(a),0); } @@ -2560,31 +2561,6 @@ class Parser { public var preprocesorValues(get, set) : Map; inline function get_preprocesorValues() return preprocessorValues; inline function set_preprocesorValues(v) return preprocessorValues = v; - - function applyOptimizerSettings() { - if( optimizer == null ) return; - - var level = preprocessorValues.get("OPTIMIZE_LEVEL"); - if( level != null ) optimizer.optimizeLevel = level; - - var enabled = preprocessorValues.get("OPTIMIZE_ENABLED"); - if( enabled != null ) optimizer.enabled = enabled; - - var constantFolding = preprocessorValues.get("OPTIMIZE_CONSTANT_FOLDING"); - if( constantFolding != null ) optimizer.enableConstantFolding = constantFolding; - - var simplification = preprocessorValues.get("OPTIMIZE_SIMPLIFICATION"); - if( simplification != null ) optimizer.enableExpressionSimplification = simplification; - - var deadCode = preprocessorValues.get("OPTIMIZE_DEAD_CODE"); - if( deadCode != null ) optimizer.enableDeadCodeElimination = deadCode; - - var branch = preprocessorValues.get("OPTIMIZE_BRANCH"); - if( branch != null ) optimizer.enableBranchOptimization = branch; - - var debug = preprocessorValues.get("OPTIMIZE_DEBUG"); - if( debug != null ) optimizer.debug = debug; - } } @:structInit From 77b230050e6478c71c490b67c09fe827c1cc157e Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 25 Jan 2026 11:18:55 +0800 Subject: [PATCH 5/5] 1 --- hscript/Interp.hx | 25 +- hscript/Optimizer.hx | 772 ++++++++++++++++++++++++++++++++------ hscript/OptimizerStats.hx | 119 ++++++ hscript/Parser.hx | 6 +- 4 files changed, 802 insertions(+), 120 deletions(-) create mode 100644 hscript/OptimizerStats.hx diff --git a/hscript/Interp.hx b/hscript/Interp.hx index a9e28e27..3518efbd 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -59,6 +59,7 @@ enum abstract ScriptObjectType(UInt8) { class DeclaredVar { public var r:Dynamic; public var depth:Int; + public var isFinal:Bool; } @:structInit @@ -339,6 +340,9 @@ class Interp { var prop:Property = cast l.r; return prop.callSetter(id, v); } else { + if (l.isFinal) { + error(ECustom("Cannot reassign final variable '" + id + "'")); + } l.r = v; if (l.depth == 0) { setVar(id, v); @@ -462,6 +466,9 @@ class Interp { case EIdent(id): var l = locals.get(id); if(l != null) { + if (l.isFinal) { + error(ECustom("Cannot modify final variable '" + id + "'")); + } var v:Dynamic = l.r; var prop:Property = null; if (v is Property) { @@ -554,6 +561,7 @@ class Interp { optimizer.enableBranchOptimization = enableBranchOptimization; optimizer.debug = optimizerDebug; var optimizedExpr = optimizer.optimize(expr); + optimizer.clearCache(); return exprReturn(optimizedExpr); } @@ -963,7 +971,8 @@ class Interp { } var declVar:DeclaredVar = { r: (declProp == null) ? r : declProp, - depth: depth + depth: depth, + isFinal: isFinal }; locals.set(n, declVar); if (depth == 0) { @@ -1093,7 +1102,7 @@ class Interp { me.depth++; me.locals = me.duplicate(capturedLocals); for (i in 0...params.length) - me.locals.set(params[i].name, {r: args[i], depth: depth}); + me.locals.set(params[i].name, {r: args[i], depth: depth, isFinal: false}); var r:Null = null; var oldDecl:Int = declared.length; if (inTry) @@ -1129,7 +1138,7 @@ class Interp { } else { // function-in-function is a local function declared.push({n: name, old: locals.get(name), depth: depth}); - var ref:DeclaredVar = {r: f, depth: depth}; + var ref:DeclaredVar = {r: f, depth: depth, isFinal: false}; locals.set(name, ref); capturedLocals.set(name, ref); // allow self-recursion } @@ -1251,7 +1260,7 @@ class Interp { inTry = oldTry; // declare 'v' declared.push({n: n, old: locals.get(n), depth: depth}); - locals.set(n, {r: err, depth: depth}); + locals.set(n, {r: err, depth: depth, isFinal: false}); var v:Dynamic = expr(ecatch); restore(old); return v; @@ -1293,10 +1302,10 @@ class Interp { case EIdent(n): declared.push({ n: n, - old: {r: locals.get(n), depth: depth}, + old: {r: locals.get(n), depth: depth, isFinal: false}, depth: depth }); - locals.set(n, {r: valParams[i], depth: depth}); + locals.set(n, {r: valParams[i], depth: depth, isFinal: false}); default: } } @@ -1409,8 +1418,8 @@ class Interp { while (_hasNext()) { var next = _next(); if(isKeyValue) - locals.set(ithv, {r: next.key, depth: depth}); - locals.set(n, {r: isKeyValue ? next.value : next, depth: depth}); + locals.set(ithv, {r: next.key, depth: depth, isFinal: false}); + locals.set(n, {r: isKeyValue ? next.value : next, depth: depth, isFinal: false}); if (!loopRun(() -> expr(e))) break; } diff --git a/hscript/Optimizer.hx b/hscript/Optimizer.hx index 163b8c6b..4c5cf904 100644 --- a/hscript/Optimizer.hx +++ b/hscript/Optimizer.hx @@ -1,5 +1,7 @@ package hscript; +import haxe.ds.StringMap; +import haxe.ds.IntMap; import hscript.Expr; import StringTools; @@ -14,15 +16,30 @@ class Optimizer { public var debug:Bool = false; public var debugPrinter:Printer; + + private var optimizationCache:StringMap; + private var exprHashes:IntMap; + private var stats:OptimizerStats; + private var finalConstants:Array; + private var finalConstantIds:StringMap; public function new() { - debugPrinter = new Printer(); + optimizationCache = new StringMap(); + exprHashes = new IntMap(); + stats = new OptimizerStats(); + finalConstants = []; + finalConstantIds = new StringMap(); } public function optimize(expr:Expr):Expr { if (!enabled) return expr; + stats.reset(); + finalConstants = []; + finalConstantIds = new StringMap(); + if (debug) { + debugPrinter = new Printer(); trace("=== HScript Optimizer Debug ==="); trace("Optimization Level: " + optimizeLevel); trace("Constant Folding: " + enableConstantFolding); @@ -34,39 +51,45 @@ class Optimizer { } var result = expr; - var startTime = Sys.time(); - var passTimes:Array = []; for (i in 0...optimizeLevel) { - var passStart = Sys.time(); - var beforeStr = debugPrinter.exprToString(result); + var passStart = haxe.Timer.stamp(); + + var beforeHash = getExprHash(result); result = optimizeOnce(result); - var afterStr = debugPrinter.exprToString(result); - var passTime = (Sys.time() - passStart) * 1000; - passTimes.push(passTime); + var afterHash = getExprHash(result); + var passTime = (haxe.Timer.stamp() - passStart) * 1000; + stats.totalPassTime += passTime; + stats.passTimes.push(passTime); if (debug) { - var optimizedCount = beforeStr.length - afterStr.length; - trace("\n--- After Pass " + (i + 1) + " (" + Math.round(passTime * 100) / 100 + " ms, reduced " + optimizedCount + " chars) ---"); + var optimizedCount = if (beforeHash != afterHash) "changed" else "unchanged"; + var afterStr = debugPrinter.exprToString(result); + trace("\n--- Pass " + (i + 1) + " (" + passTime + " ms, " + optimizedCount + ") ---"); trace(afterStr); } - if (beforeStr == afterStr) { + stats.totalPasses++; + + if (beforeHash == afterHash) { + stats.skippedPasses++; if (debug) { - trace("\n--- No further optimizations possible after pass " + (i + 1) + " ---"); + trace("\n--- No changes in pass " + (i + 1) + ", stopping early ---"); } break; } } - var totalTime = (Sys.time() - startTime) * 1000; + stats.totalTime = stats.totalPassTime; if (debug) { trace("\n--- Optimization Complete ---"); - trace("Total passes: " + passTimes.length); - trace("Total time: " + Math.round(totalTime * 100) / 100 + " ms"); - if (passTimes.length > 1) { - trace("Average per pass: " + Math.round((totalTime / passTimes.length) * 100) / 100 + " ms"); + trace("Total passes: " + stats.totalPasses); + trace("Skipped passes: " + stats.skippedPasses); + trace("Total time: " + stats.totalTime + " ms"); + trace("Cache size: " + Lambda.count(optimizationCache)); + if (stats.totalPasses > 1) { + trace("Average per pass: " + (stats.totalTime / stats.totalPasses) + " ms"); } trace("=== End Debug ===\n"); } @@ -74,158 +97,527 @@ class Optimizer { return result; } - private function optimizeOnce(expr:Expr):Expr { + private function getExprHash(expr:Expr):String { + #if hscriptPos + var id = expr.pmin; + #else + var id = expr.hashCode(); + #end + + if (exprHashes.exists(id)) { + return exprHashes.get(id); + } + + var hash = computeExprHash(expr); + exprHashes.set(id, hash); + return hash; + } + + private function computeExprHash(expr:Expr):String { return switch (Tools.expr(expr)) { - case EConst(_): return expr; - case EIdent(_): return expr; - case EPackage(_): return expr; - case EImport(_): return expr; + case EConst(c): + "C:" + switch(c) { + case CInt(v): "i" + v; + case CFloat(f): "f" + f; + case CString(s): "s" + s.length; + } + case EIdent(id): "I:" + id; + case EPackage(name): "P:" + name; + case EImport(c): "M:" + c; + case EClass(name, fields, extend, interfaces, isFinal): + "CL:" + name + ":" + fields.length; + case EVar(n, t, e, isPublic, isStatic, isPrivate, isFinal, isInline, get, set, isVar): + "V:" + n + ":" + (e != null ? getExprHash(e) : "null"); + case EParent(e): "PR:" + getExprHash(e); + case EBlock(exprs): + "B:[" + [for (e in exprs) getExprHash(e)].join(",") + "]"; + case EField(e, f, s): + "FD:" + getExprHash(e) + ":" + f; + case EBinop(op, e1, e2): + "OP:" + op + ":" + getExprHash(e1) + ":" + getExprHash(e2); + case EUnop(op, prefix, e): + "UN:" + op + ":" + prefix + ":" + getExprHash(e); + case ECall(e, params): + "CLL:" + getExprHash(e) + ":[" + [for (p in params) getExprHash(p)].join(",") + "]"; + case EIf(cond, e1, e2): + "IF:" + getExprHash(cond) + ":" + getExprHash(e1) + ":" + (e2 != null ? getExprHash(e2) : "null"); + case EWhile(cond, e): + "WH:" + getExprHash(cond) + ":" + getExprHash(e); + case EDoWhile(cond, e): + "DW:" + getExprHash(cond) + ":" + getExprHash(e); + case EFor(v, it, e, ithv): + "FR:" + v + ":" + getExprHash(it) + ":" + getExprHash(e); + case EBreak: "BR"; + case EContinue: "CT"; + case EFunction(args, e, name, ret, isPublic, isStatic, isOverride, isPrivate, isFinal, isInline): + "FUN:" + name + ":" + getExprHash(e); + case EReturn(e): + "RET:" + (e != null ? getExprHash(e) : "null"); + case EArray(e, index): + "ARR:" + getExprHash(e) + ":" + getExprHash(index); + case EArrayDecl(exprs): + "AD:[" + [for (e in exprs) getExprHash(e)].join(",") + "]"; + case ENew(cl, params, paramType): + "NEW:" + cl + ":[" + [for (p in params) getExprHash(p)].join(",") + "]"; + case EThrow(e): + "THR:" + getExprHash(e); + case ETry(e, v, t, ecatch): + "TRY:" + getExprHash(e) + ":" + getExprHash(ecatch); + case EObject(fl): + "OBJ:{" + [for (f in fl) f.name + ":" + getExprHash(f.e)].join(",") + "}"; + case ETernary(cond, e1, e2): + "TRN:" + getExprHash(cond) + ":" + getExprHash(e1) + ":" + getExprHash(e2); + case ESwitch(e, cases, defaultExpr): + "SW:" + getExprHash(e) + ":[" + [for (c in cases) + "[" + [for (v in c.values) getExprHash(v)].join(",") + ":" + getExprHash(c.expr) + ].join(",") + "]:" + (defaultExpr != null ? getExprHash(defaultExpr) : "null"); + case EMeta(name, args, e): + "MT:" + name + ":[" + (args != null ? [for (a in args) getExprHash(a)].join(",") : "") + "]:" + getExprHash(e); + case ECheckType(e, t): + "CT:" + getExprHash(e); + case EEnum(en, isAbstract): "EN:" + en.name; + case ECast(e, t): "CST:" + getExprHash(e); + case ERegex(e, f): "RX:" + e; + } + } + + private function optimizeOnce(expr:Expr):Expr { + return optimizeWithCache(expr, 0); + } + + private function optimizeWithCache(expr:Expr, depth:Int):Expr { + var useCache = !debug && depth < 3; + var cacheKey = getCacheKey(expr); + + if (useCache && optimizationCache.exists(cacheKey)) { + stats.cacheHits++; + return optimizationCache.get(cacheKey); + } + + stats.cacheMisses++; + + var result = switch (Tools.expr(expr)) { + case EConst(_): + stats.constCount++; + expr; + case EIdent(_): + stats.identCount++; + switch (Tools.expr(expr)) { + case EIdent(id): + var constId = finalConstantIds.get(id); + if (constId != null) { + stats.finalConstantReplacements++; + return finalConstants[constId]; + } + default: + } + expr; + case EPackage(_): expr; + case EImport(_): expr; case EClass(name, fields, extend, interfaces, isFinal): - var optimizedFields = [for (f in fields) optimizeOnce(f)]; - return Tools.mk(EClass(name, optimizedFields, extend, interfaces, isFinal), expr); + stats.classCount++; + var optimizedFields = [for (f in fields) optimizeWithCache(f, depth + 1)]; + Tools.mk(EClass(name, optimizedFields, extend, interfaces, isFinal), expr); case EVar(n, t, e, isPublic, isStatic, isPrivate, isFinal, isInline, get, set, isVar): - var optimizedExpr = e != null ? optimizeOnce(e) : null; - return Tools.mk(EVar(n, t, optimizedExpr, isPublic, isStatic, isPrivate, isFinal, isInline, get, set, isVar), expr); + stats.varCount++; + var optimizedExpr = e != null ? optimizeWithCache(e, depth + 1) : null; + if (isFinal && optimizedExpr != null && isConstantExpr(optimizedExpr)) { + var constId = finalConstants.length; + finalConstants.push(optimizedExpr); + finalConstantIds.set(n, constId); + } + Tools.mk(EVar(n, t, optimizedExpr, isPublic, isStatic, isPrivate, isFinal, isInline, get, set, isVar), expr); case EParent(e): - var optimized = optimizeOnce(e); - return Tools.mk(EParent(optimized), expr); + stats.parentCount++; + var optimized = optimizeWithCache(e, depth + 1); + Tools.mk(EParent(optimized), expr); case EBlock(exprs): - var optimizedExprs = [for (e in exprs) optimizeOnce(e)]; + stats.blockCount++; + var optimizedExprs = [for (e in exprs) optimizeWithCache(e, depth + 1)]; var cleanedExprs = enableDeadCodeElimination ? removeDeadCode(optimizedExprs) : optimizedExprs; + cleanedExprs = [for (e in cleanedExprs) switch (Tools.expr(e)) { + case EVar(n, _, _, _, _, _, true, _, _, _, _): + if (finalConstantIds.exists(n)) { + stats.deadCodeEliminations++; + continue; + } + e; + default: e; + }]; if (cleanedExprs.length == 1) { - return cleanedExprs[0]; + cleanedExprs[0]; } else { - return Tools.mk(EBlock(cleanedExprs), expr); + Tools.mk(EBlock(cleanedExprs), expr); } case EField(e, f, s): - var optimized = optimizeOnce(e); - return Tools.mk(EField(optimized, f, s), expr); + stats.fieldCount++; + var optimized = optimizeWithCache(e, depth + 1); + Tools.mk(EField(optimized, f, s), expr); case EBinop(op, e1, e2): - var optimized1 = optimizeOnce(e1); - var optimized2 = optimizeOnce(e2); + stats.binopCount++; + var optimized1 = optimizeWithCache(e1, depth + 1); + var optimized2 = optimizeWithCache(e2, depth + 1); var folded = enableConstantFolding ? tryFoldConstant(op, optimized1, optimized2) : null; if (folded != null) { - return folded; + stats.folds++; + folded; } else { var simplified = enableExpressionSimplification ? trySimplifyBinop(op, optimized1, optimized2) : null; if (simplified != null) { - return simplified; + stats.simplifications++; + simplified; + } else if (optimized1 != e1 || optimized2 != e2) { + Tools.mk(EBinop(op, optimized1, optimized2), expr); } else { - return Tools.mk(EBinop(op, optimized1, optimized2), expr); + expr; } } case EUnop(op, prefix, e): - var optimized = optimizeOnce(e); + stats.unopCount++; + var optimized = optimizeWithCache(e, depth + 1); var folded = enableConstantFolding ? tryFoldUnop(op, prefix, optimized) : null; if (folded != null) { - return folded; + stats.folds++; + folded; } else { var simplified = enableExpressionSimplification ? trySimplifyUnop(op, prefix, optimized) : null; if (simplified != null) { - return simplified; + stats.simplifications++; + simplified; + } else if (optimized != e) { + Tools.mk(EUnop(op, prefix, optimized), expr); } else { - return Tools.mk(EUnop(op, prefix, optimized), expr); + expr; } } case ECall(e, params): - var optimizedExpr = optimizeOnce(e); - var optimizedParams = [for (p in params) optimizeOnce(p)]; - return Tools.mk(ECall(optimizedExpr, optimizedParams), expr); + stats.callCount++; + var optimizedExpr = optimizeWithCache(e, depth + 1); + var optimizedParams = [for (p in params) optimizeWithCache(p, depth + 1)]; + var paramsChanged = optimizedParams.length != params.length; + if (!paramsChanged) { + for (i in 0...params.length) { + if (optimizedParams[i] != params[i]) { + paramsChanged = true; + break; + } + } + } + + var callOptimized = tryOptimizeCall(optimizedExpr, optimizedParams); + if (callOptimized != null) { + stats.simplifications++; + return callOptimized; + } + + if (optimizedExpr != e || paramsChanged) { + Tools.mk(ECall(optimizedExpr, optimizedParams), expr); + } else { + expr; + } case EIf(cond, e1, e2): - var optimizedCond = optimizeOnce(cond); - var optimizedE1 = optimizeOnce(e1); - var optimizedE2 = e2 != null ? optimizeOnce(e2) : null; + stats.ifCount++; + var optimizedCond = optimizeWithCache(cond, depth + 1); + var optimizedE1 = optimizeWithCache(e1, depth + 1); + var optimizedE2 = e2 != null ? optimizeWithCache(e2, depth + 1) : null; + + var condOptimized = tryOptimizeCondition(optimizedCond); + if (condOptimized != null) { + optimizedCond = condOptimized; + } if (enableBranchOptimization) { var optimized = tryOptimizeIf(optimizedCond, optimizedE1, optimizedE2); if (optimized != null) { - return optimized; + stats.branchOptimizations++; + optimized; + } else if (optimizedCond != cond || optimizedE1 != e1 || optimizedE2 != e2) { + Tools.mk(EIf(optimizedCond, optimizedE1, optimizedE2), expr); + } else { + expr; } + } else if (optimizedCond != cond || optimizedE1 != e1 || optimizedE2 != e2) { + Tools.mk(EIf(optimizedCond, optimizedE1, optimizedE2), expr); + } else { + expr; } - return Tools.mk(EIf(optimizedCond, optimizedE1, optimizedE2), expr); case EWhile(cond, e): - var optimizedCond = optimizeOnce(cond); - var optimizedBody = optimizeOnce(e); - return Tools.mk(EWhile(optimizedCond, optimizedBody), expr); + stats.whileCount++; + var optimizedCond = optimizeWithCache(cond, depth + 1); + var oldConstants = finalConstants.copy(); + var oldConstantIds = finalConstantIds.copy(); + var optimizedBody = optimizeWithCache(e, depth + 1); + finalConstants = oldConstants; + finalConstantIds = oldConstantIds; + if (optimizedCond != cond || optimizedBody != e) { + Tools.mk(EWhile(optimizedCond, optimizedBody), expr); + } else { + expr; + } case EDoWhile(cond, e): - var optimizedCond = optimizeOnce(cond); - var optimizedBody = optimizeOnce(e); - return Tools.mk(EDoWhile(optimizedCond, optimizedBody), expr); + stats.doWhileCount++; + var oldConstants = finalConstants.copy(); + var oldConstantIds = finalConstantIds.copy(); + var optimizedCond = optimizeWithCache(cond, depth + 1); + var optimizedBody = optimizeWithCache(e, depth + 1); + finalConstants = oldConstants; + finalConstantIds = oldConstantIds; + if (optimizedCond != cond || optimizedBody != e) { + Tools.mk(EDoWhile(optimizedCond, optimizedBody), expr); + } else { + expr; + } case EFor(v, it, e, ithv): - var optimizedIt = optimizeOnce(it); - var optimizedBody = optimizeOnce(e); - return Tools.mk(EFor(v, optimizedIt, optimizedBody, ithv), expr); - case EBreak: return expr; - case EContinue: return expr; + stats.forCount++; + var optimizedIt = optimizeWithCache(it, depth + 1); + var oldConstants = finalConstants.copy(); + var oldConstantIds = finalConstantIds.copy(); + var optimizedBody = optimizeWithCache(e, depth + 1); + finalConstants = oldConstants; + finalConstantIds = oldConstantIds; + if (optimizedIt != it || optimizedBody != e) { + Tools.mk(EFor(v, optimizedIt, optimizedBody, ithv), expr); + } else { + expr; + } + case EBreak: + stats.breakCount++; + expr; + case EContinue: + stats.continueCount++; + expr; case EFunction(args, e, name, ret, isPublic, isStatic, isOverride, isPrivate, isFinal, isInline): - var optimizedBody = optimizeOnce(e); - return Tools.mk(EFunction(args, optimizedBody, name, ret, isPublic, isStatic, isOverride, isPrivate, isFinal, isInline), expr); + stats.functionCount++; + var oldConstants = finalConstants.copy(); + var oldConstantIds = finalConstantIds.copy(); + var optimizedBody = optimizeWithCache(e, depth + 1); + finalConstants = oldConstants; + finalConstantIds = oldConstantIds; + if (optimizedBody != e) { + Tools.mk(EFunction(args, optimizedBody, name, ret, isPublic, isStatic, isOverride, isPrivate, isFinal, isInline), expr); + } else { + expr; + } case EReturn(e): - var optimized = e != null ? optimizeOnce(e) : null; - return Tools.mk(EReturn(optimized), expr); + stats.returnCount++; + var optimized = e != null ? optimizeWithCache(e, depth + 1) : null; + if (optimized != e) { + Tools.mk(EReturn(optimized), expr); + } else { + expr; + } case EArray(e, index): - var optimizedExpr = optimizeOnce(e); - var optimizedIndex = optimizeOnce(index); - return Tools.mk(EArray(optimizedExpr, optimizedIndex), expr); + stats.arrayCount++; + var optimizedExpr = optimizeWithCache(e, depth + 1); + var optimizedIndex = optimizeWithCache(index, depth + 1); + if (optimizedExpr != e || optimizedIndex != index) { + Tools.mk(EArray(optimizedExpr, optimizedIndex), expr); + } else { + expr; + } case EArrayDecl(exprs, wantedType): - var optimizedExprs = [for (e in exprs) optimizeOnce(e)]; - return Tools.mk(EArrayDecl(optimizedExprs, wantedType), expr); + stats.arrayDeclCount++; + var optimizedExprs = [for (e in exprs) optimizeWithCache(e, depth + 1)]; + var changed = optimizedExprs.length != exprs.length; + if (!changed) { + for (i in 0...exprs.length) { + if (optimizedExprs[i] != exprs[i]) { + changed = true; + break; + } + } + } + if (changed) { + Tools.mk(EArrayDecl(optimizedExprs, wantedType), expr); + } else { + expr; + } case ENew(cl, params, paramType): - var optimizedParams = [for (p in params) optimizeOnce(p)]; - return Tools.mk(ENew(cl, optimizedParams, paramType), expr); + stats.newCount++; + var optimizedParams = [for (p in params) optimizeWithCache(p, depth + 1)]; + var changed = optimizedParams.length != params.length; + if (!changed) { + for (i in 0...params.length) { + if (optimizedParams[i] != params[i]) { + changed = true; + break; + } + } + } + if (changed) { + Tools.mk(ENew(cl, optimizedParams, paramType), expr); + } else { + expr; + } case EThrow(e): - var optimized = optimizeOnce(e); - return Tools.mk(EThrow(optimized), expr); + stats.throwCount++; + var optimized = optimizeWithCache(e, depth + 1); + if (optimized != e) { + Tools.mk(EThrow(optimized), expr); + } else { + expr; + } case ETry(e, v, t, ecatch): - var optimizedTry = optimizeOnce(e); - var optimizedCatch = optimizeOnce(ecatch); - return Tools.mk(ETry(optimizedTry, v, t, optimizedCatch), expr); + stats.tryCount++; + var optimizedTry = optimizeWithCache(e, depth + 1); + var optimizedCatch = optimizeWithCache(ecatch, depth + 1); + if (optimizedTry != e || optimizedCatch != ecatch) { + Tools.mk(ETry(optimizedTry, v, t, optimizedCatch), expr); + } else { + expr; + } case EObject(fl): - var optimizedFields = [for (f in fl) {name: f.name, e: optimizeOnce(f.e)}]; - var objectFields:Array = []; - for (f in optimizedFields) { - objectFields.push({name: f.name, e: f.e}); + stats.objectCount++; + var optimizedFields = [for (f in fl) {name: f.name, e: optimizeWithCache(f.e, depth + 1)}]; + var changed = optimizedFields.length != fl.length; + if (!changed) { + for (i in 0...fl.length) { + if (optimizedFields[i].e != fl[i].e) { + changed = true; + break; + } + } + } + if (changed) { + var objectFields:Array = []; + for (f in optimizedFields) { + objectFields.push({name: f.name, e: f.e}); + } + Tools.mk(EObject(objectFields), expr); + } else { + expr; } - return Tools.mk(EObject(objectFields), expr); case ETernary(cond, e1, e2): - var optimizedCond = optimizeOnce(cond); - var optimizedE1 = optimizeOnce(e1); - var optimizedE2 = optimizeOnce(e2); + stats.ternaryCount++; + var optimizedCond = optimizeWithCache(cond, depth + 1); + var optimizedE1 = optimizeWithCache(e1, depth + 1); + var optimizedE2 = optimizeWithCache(e2, depth + 1); if (enableBranchOptimization) { var optimized = tryOptimizeTernary(optimizedCond, optimizedE1, optimizedE2); if (optimized != null) { - return optimized; + stats.branchOptimizations++; + optimized; + } else if (optimizedCond != cond || optimizedE1 != e1 || optimizedE2 != e2) { + Tools.mk(ETernary(optimizedCond, optimizedE1, optimizedE2), expr); + } else { + expr; } + } else if (optimizedCond != cond || optimizedE1 != e1 || optimizedE2 != e2) { + Tools.mk(ETernary(optimizedCond, optimizedE1, optimizedE2), expr); + } else { + expr; } - return Tools.mk(ETernary(optimizedCond, optimizedE1, optimizedE2), expr); case ESwitch(e, cases, defaultExpr): - var optimizedExpr = optimizeOnce(e); + stats.switchCount++; + var optimizedExpr = optimizeWithCache(e, depth + 1); var optimizedCases = [for (c in cases) { - values: [for (v in c.values) optimizeOnce(v)], - expr: optimizeOnce(c.expr) + values: [for (v in c.values) optimizeWithCache(v, depth + 1)], + expr: optimizeWithCache(c.expr, depth + 1) }]; var switchCases:Array = []; for (c in optimizedCases) { switchCases.push({values: c.values, expr: c.expr}); } - var optimizedDefault = defaultExpr != null ? optimizeOnce(defaultExpr) : null; - return Tools.mk(ESwitch(optimizedExpr, switchCases, optimizedDefault), expr); + var optimizedDefault = defaultExpr != null ? optimizeWithCache(defaultExpr, depth + 1) : null; + + var changed = optimizedExpr != e || optimizedDefault != defaultExpr; + if (!changed) { + for (i in 0...cases.length) { + var oldCase = cases[i]; + var newCase = optimizedCases[i]; + if (oldCase.values.length != newCase.values.length || oldCase.expr != newCase.expr) { + changed = true; + break; + } + for (j in 0...oldCase.values.length) { + if (oldCase.values[j] != newCase.values[j]) { + changed = true; + break; + } + } + if (changed) break; + } + } + + if (changed) { + Tools.mk(ESwitch(optimizedExpr, switchCases, optimizedDefault), expr); + } else { + expr; + } case EMeta(name, args, e): - var optimizedArgs = args != null ? [for (a in args) optimizeOnce(a)] : null; - var optimizedExpr = optimizeOnce(e); - return Tools.mk(EMeta(name, optimizedArgs, optimizedExpr), expr); + stats.metaCount++; + var optimizedArgs = args != null ? [for (a in args) optimizeWithCache(a, depth + 1)] : null; + var optimizedExpr = optimizeWithCache(e, depth + 1); + var argsChanged = args != null && optimizedArgs.length == args.length; + if (argsChanged) { + for (i in 0...args.length) { + if (optimizedArgs[i] != args[i]) { + argsChanged = false; + break; + } + } + } + if (optimizedExpr != e || (args != null && !argsChanged)) { + Tools.mk(EMeta(name, optimizedArgs, optimizedExpr), expr); + } else { + expr; + } case ECheckType(e, t): - var optimized = optimizeOnce(e); - return Tools.mk(ECheckType(optimized, t), expr); - case EEnum(en, isAbstract): - return Tools.mk(EEnum(en, isAbstract), expr); + stats.checkTypeCount++; + var optimized = optimizeWithCache(e, depth + 1); + if (optimized != e) { + Tools.mk(ECheckType(optimized, t), expr); + } else { + expr; + } + case EEnum(en, isAbstract): + stats.enumCount++; + expr; case ECast(e, t): - var optimized = optimizeOnce(e); - return Tools.mk(ECast(optimized, t), expr); - case ERegex(e, f): - return Tools.mk(ERegex(e, f), expr); + stats.castCount++; + var optimized = optimizeWithCache(e, depth + 1); + if (optimized != e) { + Tools.mk(ECast(optimized, t), expr); + } else { + expr; + } + case ERegex(e, f): + stats.regexCount++; + expr; } + + if (useCache) { + optimizationCache.set(cacheKey, result); + } + + return result; + } + + private inline function getCacheKey(expr:Expr):String { + #if hscriptPos + return expr.pmin + ":" + expr.pmax; + #else + return Std.string(expr.hashCode()); + #end + } + + public function clearCache():Void { + optimizationCache = new StringMap(); + exprHashes = new IntMap(); + stats.cacheHits = 0; + stats.cacheMisses = 0; + } + + public function dispose():Void { + optimizationCache = null; + exprHashes = null; + debugPrinter = null; + stats = null; + } + + public function getStats():OptimizerStats { + return stats; } private function tryFoldConstant(op:String, e1:Expr, e2:Expr):Null { @@ -553,6 +945,22 @@ class Optimizer { var mask = Std.int(c2) - 1; return Tools.mk(EBinop("&", e1, makeConst(mask, e2)), e1); } + case "!=": + if (c1 != null && c2 != null) return makeConst(c1 != c2, e1); + if (e1 == e2) return makeConst(false, e1); + case "==": + if (c1 != null && c2 != null) return makeConst(c1 == c2, e1); + if (e1 == e2) return makeConst(true, e1); + case ">": + if (c1 != null && c2 != null) return makeConst(c1 > c2, e1); + case "<": + if (c1 != null && c2 != null) return makeConst(c1 < c2, e1); + case ">=": + if (c1 != null && c2 != null) return makeConst(c1 >= c2, e1); + case "<=": + if (c1 != null && c2 != null) return makeConst(c1 <= c2, e1); + case "is": + if (c1 != null && c2 != null) return makeConst(Std.isOfType(c1, c2), e1); } return null; @@ -567,6 +975,13 @@ class Optimizer { default: } + if (op == "-") { + var c = getConstValue(e); + if (c != null) return makeConst(-c, e); + } + + if (op == "+") return e; + return null; } @@ -606,7 +1021,129 @@ class Optimizer { return null; } - private function removeDeadCode(exprs:Array):Array { + private function tryOptimizeCall(e:Expr, params:Array):Null { + switch (Tools.expr(e)) { + case EField({e: EIdent("Math")}, "abs"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null && c >= 0) return params[0]; + if (c != null) return makeConst(-c, params[0]); + } + case EField({e: EIdent("Math")}, "floor"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.floor(c), params[0]); + } + case EField({e: EIdent("Math")}, "ceil"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.ceil(c), params[0]); + } + case EField({e: EIdent("Math")}, "round"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.round(c), params[0]); + } + case EField({e: EIdent("Math")}, "sqrt"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null && c >= 0) return makeConst(Math.sqrt(c), params[0]); + } + case EField({e: EIdent("Math")}, "pow"): + if (params.length == 2) { + var c1 = getConstValue(params[0]); + var c2 = getConstValue(params[1]); + if (c1 != null && c2 != null) return makeConst(Math.pow(c1, c2), params[0]); + } + case EField({e: EIdent("Math")}, "sin"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.sin(c), params[0]); + } + case EField({e: EIdent("Math")}, "cos"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.cos(c), params[0]); + } + case EField({e: EIdent("Math")}, "tan"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.tan(c), params[0]); + } + case EField({e: EIdent("Math")}, "asin"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.asin(c), params[0]); + } + case EField({e: EIdent("Math")}, "acos"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.acos(c), params[0]); + } + case EField({e: EIdent("Math")}, "atan"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Math.atan(c), params[0]); + } + case EField({e: EIdent("Math")}, "log"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null && c > 0) return makeConst(Math.log(c), params[0]); + } + case EField({e: EIdent("Math")}, "PI"): + return makeConst(Math.PI, e); + case EField({e: EIdent("Math")}, "E"): + return makeConst(2.718281828459045, e); + case EField({e: EIdent("Std")}, "string"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Std.string(c), params[0]); + } + case EField({e: EIdent("Std")}, "int"): + if (params.length == 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(Std.int(c), params[0]); + } + case EField({e: EIdent("StringTools")}, "trim"): + if (params.length >= 1) { + var c = getConstValue(params[0]); + if (c != null) return makeConst(StringTools.trim(c), params[0]); + } + default: + } + return null; + } + + private function tryOptimizeCondition(cond:Expr):Null { + switch (Tools.expr(cond)) { + case EIdent(id): + var constId = finalConstantIds.get(id); + if (constId != null) { + return finalConstants[constId]; + } + case EUnop("!", _, inner): + switch (Tools.expr(inner)) { + case EIdent(id): + var constId = finalConstantIds.get(id); + if (constId != null) { + return Tools.mk(EUnop("!", false, finalConstants[constId]), cond); + } + case EUnop("!", _, inner2): + return inner2; + case EBinop("&&", a, b): + return Tools.mk(EBinop("||", Tools.mk(EUnop("!", false, a), cond), + Tools.mk(EUnop("!", false, b), cond)), cond); + case EBinop("||", a, b): + return Tools.mk(EBinop("&&", Tools.mk(EUnop("!", false, a), cond), + Tools.mk(EUnop("!", false, b), cond)), cond); + default: + } + default: + } + return null; + } + + private inline function removeDeadCode(exprs:Array):Array { var result:Array = []; var foundTerminator = false; @@ -627,7 +1164,7 @@ class Optimizer { return result; } - private function isUnconditionalTerminator(expr:Expr):Bool { + private inline function isUnconditionalTerminator(expr:Expr):Bool { return switch (Tools.expr(expr)) { case EReturn(_): true; case EThrow(_): true; @@ -642,7 +1179,7 @@ class Optimizer { } } - private function getConstValue(expr:Expr):Null { + private inline function getConstValue(expr:Expr):Null { return switch (Tools.expr(expr)) { case EConst(CInt(v)): v; case EConst(CFloat(v)): v; @@ -665,7 +1202,7 @@ class Optimizer { } } - private function exprEquals(e1:Expr, e2:Expr):Bool { + private inline function exprEquals(e1:Expr, e2:Expr):Bool { if (e1 == e2) return true; if (e1 == null || e2 == null) return false; @@ -745,7 +1282,7 @@ class Optimizer { } } - private function isPureConstant(expr:Expr):Bool { + private inline function isPureConstant(expr:Expr):Bool { return switch (Tools.expr(expr)) { case EConst(_): true; case EParent(e): isPureConstant(e); @@ -753,7 +1290,21 @@ class Optimizer { } } - private function isStringExpr(expr:Expr):Bool { + private inline function isConstantExpr(expr:Expr):Bool { + return switch (Tools.expr(expr)) { + case EConst(_): true; + case EParent(e): isConstantExpr(e); + case EBinop(op, e1, e2): + var c1 = getConstValue(e1); + var c2 = getConstValue(e2); + c1 != null && c2 != null; + case EUnop(op, prefix, e): + getConstValue(e) != null; + default: false; + } + } + + private inline function isStringExpr(expr:Expr):Bool { return switch (Tools.expr(expr)) { case EConst(CString(_)): true; case EParent(e): isStringExpr(e); @@ -762,10 +1313,11 @@ class Optimizer { } } - private function isPowerOfTwo(n:Float):Bool { + private inline function isPowerOfTwo(n:Float):Bool { if (n != Math.floor(n)) return false; var i = Std.int(n); if (i <= 0) return false; return (i & (i - 1)) == 0; } + } \ No newline at end of file diff --git a/hscript/OptimizerStats.hx b/hscript/OptimizerStats.hx new file mode 100644 index 00000000..f3852515 --- /dev/null +++ b/hscript/OptimizerStats.hx @@ -0,0 +1,119 @@ +package hscript; + +class OptimizerStats { + public var totalPasses:Int = 0; + public var skippedPasses:Int = 0; + public var totalTime:Float = 0.0; + public var totalPassTime:Float = 0.0; + public var passTimes:Array = []; + + public var cacheHits:Int = 0; + public var cacheMisses:Int = 0; + + public var constCount:Int = 0; + public var identCount:Int = 0; + public var varCount:Int = 0; + public var blockCount:Int = 0; + public var parentCount:Int = 0; + public var fieldCount:Int = 0; + public var binopCount:Int = 0; + public var unopCount:Int = 0; + public var callCount:Int = 0; + public var ifCount:Int = 0; + public var whileCount:Int = 0; + public var doWhileCount:Int = 0; + public var forCount:Int = 0; + public var breakCount:Int = 0; + public var continueCount:Int = 0; + public var functionCount:Int = 0; + public var returnCount:Int = 0; + public var arrayCount:Int = 0; + public var arrayDeclCount:Int = 0; + public var newCount:Int = 0; + public var throwCount:Int = 0; + public var tryCount:Int = 0; + public var objectCount:Int = 0; + public var ternaryCount:Int = 0; + public var switchCount:Int = 0; + public var metaCount:Int = 0; + public var checkTypeCount:Int = 0; + public var enumCount:Int = 0; + public var castCount:Int = 0; + public var regexCount:Int = 0; + public var classCount:Int = 0; + + public var folds:Int = 0; + public var simplifications:Int = 0; + public var branchOptimizations:Int = 0; + public var deadCodeEliminations:Int = 0; + public var finalConstantReplacements:Int = 0; + + public function new() {} + + public function reset():Void { + totalPasses = 0; + skippedPasses = 0; + totalTime = 0.0; + totalPassTime = 0.0; + passTimes = []; + cacheHits = 0; + cacheMisses = 0; + + constCount = 0; + identCount = 0; + varCount = 0; + blockCount = 0; + parentCount = 0; + fieldCount = 0; + binopCount = 0; + unopCount = 0; + callCount = 0; + ifCount = 0; + whileCount = 0; + doWhileCount = 0; + forCount = 0; + breakCount = 0; + continueCount = 0; + functionCount = 0; + returnCount = 0; + arrayCount = 0; + arrayDeclCount = 0; + newCount = 0; + throwCount = 0; + tryCount = 0; + objectCount = 0; + ternaryCount = 0; + switchCount = 0; + metaCount = 0; + checkTypeCount = 0; + enumCount = 0; + castCount = 0; + regexCount = 0; + classCount = 0; + + folds = 0; + simplifications = 0; + branchOptimizations = 0; + deadCodeEliminations = 0; + finalConstantReplacements = 0; + } + + public function getSummary():String { + var totalExprs = constCount + identCount + varCount + blockCount + parentCount + + fieldCount + binopCount + unopCount + callCount + ifCount + whileCount + + doWhileCount + forCount + functionCount + arrayCount + arrayDeclCount + + newCount + throwCount + tryCount + objectCount + ternaryCount + switchCount + + metaCount + checkTypeCount + enumCount + castCount + regexCount + classCount; + + return 'Optimizer Stats: + Passes: $totalPasses (skipped: $skippedPasses) + Time: ${Math.round(totalTime * 100) / 100}ms + Cache: $cacheHits hits, $cacheMisses misses + Expressions processed: $totalExprs + Folds: $folds + Simplifications: $simplifications + Branch optimizations: $branchOptimizations + Dead code eliminations: $deadCodeEliminations + Final constant replacements: $finalConstantReplacements'; + } +} diff --git a/hscript/Parser.hx b/hscript/Parser.hx index d8b68de9..9b672771 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -851,6 +851,7 @@ class Parser { null; } case "var" | "final": + var isFinalVar:Bool = false; if(id == "final") { var nextToken = token(); switch(nextToken) { @@ -866,6 +867,7 @@ class Parser { return str; default: push(nextToken); + isFinalVar = true; } } var ident = getIdent(); @@ -876,7 +878,7 @@ class Parser { var tk = token(); if( tk == TPOpen) { - if( id == "final" ) + if( isFinalVar ) unexpected(tk); var getId = getIdent(); @@ -924,7 +926,7 @@ class Parser { nextType = null; if(isVar) isVar = false; - mk(EVar(ident, t, e, nextIsPublic, nextIsStatic, nextIsPrivate, id == "final", nextIsInline, get, set, oldIsVar), p1, (e == null) ? tokenMax : pmax(e)); + mk(EVar(ident, t, e, nextIsPublic, nextIsStatic, nextIsPrivate, isFinalVar, nextIsInline, get, set, oldIsVar), p1, (e == null) ? tokenMax : pmax(e)); case "while": var econd = parseExpr(); var e = parseExpr();