Added a optimizer + bug fixes with benchmark

This commit is contained in:
NeeEoo
2024-04-23 19:41:46 +02:00
parent 9288a9df10
commit 8c1aaf5454
8 changed files with 343 additions and 30 deletions
+267
View File
@@ -0,0 +1,267 @@
package hscript;
import hscript.Expr;
import hscript.Tools;
import hscript.Parser;
@:access(hscript.Parser)
class Optimizer {
public static function optimize(s:Expr):Expr {
if(s == null)
return null;
#if hscriptPos
var e = s.e;
#else
var e = s;
#end
switch(e) {
// Parse all expressions and recreate the AST
case EBlock(exprs):
var exprs = exprs.map((v) -> optimize(v)).filter((v) -> v != null).filter((v) -> {
if(Tools.expr(v).match(EBlock([]))) return false; // Remove empty blocks
return true;
});
return mk(EBlock(exprs), s);
case EIf(econd, e1, e2):
var econd = optimize(econd);
var e1 = optimize(e1);
var e2 = optimize(e2);
if(isConstant(econd)) {
var econd = getConstant(econd);
if(econd == true) {
if(e1 == null)
return mk(EBlock([]), s);
return mk(EBlock([e1]), s);
} else {
if(e2 == null)
return mk(EBlock([]), s);
return mk(EBlock([e2]), s);
}
}
return mk(EIf(econd, e1, e2), s);
case EWhile(econd, e):
var econd = optimize(econd);
var e = optimize(e);
return mk(EWhile(econd, e), s);
case EDoWhile(econd, e):
var econd = optimize(econd);
var e = optimize(e);
return mk(EDoWhile(econd, e), s);
case EFor(v, it, e):
var it = optimize(it);
var e = optimize(e);
return mk(EFor(v, it, e), s);
case EForKeyValue(v, it, e, ithv):
var it = optimize(it);
var e = optimize(e);
return mk(EForKeyValue(v, it, e, ithv), s);
case EBreak:
return mk(EBreak, s);
case EContinue:
return mk(EContinue, s);
case EReturn(e):
var e = optimize(e);
return mk(EReturn(e), s);
case ETry(e, v, t, ecatch):
var e = optimize(e);
var ecatch = optimize(ecatch);
return mk(ETry(e, v, t, ecatch), s);
case EThrow(e):
var e = optimize(e);
return mk(EThrow(e), s);
case EVar(n, t, e, isPublic, isStatic):
e = optimize(e);
return mk(EVar(n, t, e, isPublic, isStatic), s);
case ECall(e, params):
e = optimize(e);
params = params.map((v) -> optimize(v));
if(params.length == 0) {
switch(Tools.expr(e)) {
case EField(fc, "toUpperCase", _):
var str = getStringConstant(fc);
if(str != null)
return mk(convertConstant(str.toUpperCase()), s);
case EField(fc, "toLowerCase", _):
var str = getStringConstant(fc);
if(str != null)
return mk(convertConstant(str.toLowerCase()), s);
default:
}
}
return mk(ECall(e, params), s);
case EField(e, f, safe):
e = optimize(e);
return mk(EField(e, f, safe), s);
case EIdent(_) | EConst(_):
return s;
case EParent(e):
e = optimize(e);
return mk(EParent(e), s);
case ENew(cl, args):
args = args.map((v) -> optimize(v));
return mk(ENew(cl, args), s);
case EObject(fl):
fl.map((v) -> {
v.e = optimize(v.e);
});
return mk(EObject(fl), s);
case EFunction(args, e, name, ret, isPublic, isStatic, isOverride):
args.map((v) -> {
v.name = v.name;
v.value = optimize(v.value);
});
e = optimize(e);
return mk(EFunction(args, e, name, ret, isPublic, isStatic, isOverride), s);
case ESwitch(e, cases, def):
e = optimize(e);
cases.map((v) -> {
var values = v.values.map((v) -> optimize(v));
v.expr = optimize(v.expr);
});
def = optimize(def);
return mk(ESwitch(e, cases, def), s);
case EArrayDecl(arr, wantedType):
arr = arr.map((v) -> optimize(v));
return mk(EArrayDecl(arr, wantedType), s);
case EArray(e, index):
e = optimize(e);
index = optimize(index);
if(isConstant(index) && Tools.expr(e).match(EArrayDecl(_, _))) {
var arr = switch(Tools.expr(e)) {
case EArrayDecl(arr, _): arr;
default: null;
};
var constant = Lambda.exists(arr, isConstant);
if(constant) {
return mk(Tools.expr(arr[getConstant(index)]), s);
}
}
return mk(EArray(e, index), s);
case EBinop(op, e1, e2):
e1 = optimize(e1);
e2 = optimize(e2);
if(isConstant(e1) && isConstant(e2)) {
var optimized:Dynamic = optimizeOp(op, getConstant(e1), getConstant(e2));
if(optimized != null) {
var expr = convertConstant(optimized);
return mk(expr, s);
}
}
return mk(EBinop(op, e1, e2), s);
case EUnop(op, prefix, e):
e = optimize(e);
if(isConstant(e)) {
var constant:Dynamic = getConstant(e);
switch(op) {
case "-": return mk(convertConstant(-constant), s);
case "!": return mk(convertConstant(!constant), s);
case "~":
var complement = #if (neko && !haxe3) haxe.Int32.complement(constant) #else ~constant #end;
return mk(convertConstant(complement), s);
}
}
return mk(EUnop(op, prefix, e), s);
default:
Sys.println("Unknown expr: " + e);
}
return s;
}
static function isConstant(e:Expr):Bool {
return switch(Tools.expr(e)) {
case EIdent("true") | EIdent("false") | EIdent("null"): true;
case EConst(_): true;
case EParent(e): isConstant(e);
default: false;
}
}
static function getStringConstant(e:Expr):String {
return switch(Tools.expr(e)) {
case EConst(CString(value)): value;
default: null;
}
}
static function getConstant(e:Expr):Dynamic {
return switch(Tools.expr(e)) {
case EParent(e): getConstant(e);
case EIdent("true"): true;
case EIdent("false"): false;
case EIdent("null"): null;
case EConst(CInt(value)): value;
case EConst(CFloat(value)): value;
case EConst(CString(value)): value;
default: throw "Unknown constant " + Tools.expr(e);
}
}
static function convertConstant(value:Dynamic):ExprDef {
return switch(Type.typeof(value)) {
case TInt: EConst(CInt(value));
case TFloat: EConst(CFloat(value));
case TBool: EIdent(value == true ? "true" : "false");
//case TString: EConst(CString(value));
case TNull: EIdent("null");
case TClass(String): EConst(CString(value));
default: throw "Unknown type " + Type.typeof(value);
}
}
static function mk(e:ExprDef, s:Expr):Expr {
#if hscriptPos
return new Expr(e, s.pmin, s.pmax, s.origin, s.line);
#else
return e;
#end
}
static function optimizeOp(op, f1:Dynamic, f2:Dynamic):Dynamic {
//trace("Optimizing " + f1 + " " + op + " " + f2);
return switch(op) {
case "+": f1 + f2;
case "-": f1 - f2;
case "*": f1 * f2;
case "/": f1 / f2;
case "%": f1 % f2;
case "&": f1 & f2;
case "|": f1 | f2;
case "^": f1 ^ f2;
case "<<": f1 << f2;
case ">>": f1 >> f2;
case ">>>": f1 >>> f2;
case "==": f1 == f2;
case "!=": f1 != f2;
case ">=": f1 >= f2;
case "<=": f1 <= f2;
case ">": f1 > f2;
case "<": f1 < f2;
case "||": f1 == true || f2 == true;
case "&&": f1 == true && f2 == true;
case "??": f1 == null ? f2 : f1;
default: null;
}
}
}
+8 -1
View File
@@ -44,6 +44,7 @@ enum Token {
}
class Parser {
public static var optimize = true;
// config / variables
public var line : Int;
@@ -194,7 +195,13 @@ class Parser {
push(tk);
parseFullExpr(a);
}
return if( a.length == 1 ) a[0] else mk(EBlock(a),0);
var expr = if( a.length == 1 ) a[0] else mk(EBlock(a),0);
if(Parser.optimize) {
expr = Optimizer.optimize(expr);
var printer = new Printer();
trace(printer.exprToString(expr));
}
return expr;
}
function unexpected( tk ) : Dynamic {
+5 -3
View File
@@ -6,22 +6,24 @@ class TestBenchmark extends Benchmark {
super("Test", 10000);
}
var hscript = "var a:Array<Float> = []; for (i in 0...1000) a.push(i * 2 + 1 / 6);";
var hscript = "var a:Array<Float> = []; for (i in 0...1000) a.push(i * 2 + 1 / 6); if(true == true) a.push(1);";
public override function reset() {
super.reset();
a = [];
if(interp != null)
interp.variables.remove("a");
cacheExpr(hscript);
if(expr == null)
cacheExpr(hscript);
}
public var a:Array<Float> = [];
public override function haxeBenchmark() {
for (i in 0...1000) a.push(i * 2 + 1 / 6);
if(true == true) a.push(1);
}
public override function hscriptBenchmark() {
execute(hscript);
interp.execute(expr);
}
}
+26 -10
View File
@@ -75,7 +75,6 @@ class MiscCase extends TestCase {
assertEq("x",55,{ x : 55 });
assertEq("var y = 33; y",33);
assertEq("{ 1; 2; 3; }",3);
assertEq("{ var x = 0; } x",55,{ x : 55 });
assertEq("o.val",55,{ o : { val : 55 } });
assertEq("o.val",null,{ o : {} });
assertEq("var a = 1; a++",1);
@@ -90,7 +89,6 @@ class MiscCase extends TestCase {
assertEq("var t = 0; for( x in [1,2,3] ) t += x; t",6);
assertEq("var a = new Array(); for( x in 0...5 ) a[x] = x; a.join('-')","0-1-2-3-4");
assertEq("(function(a,b) return a + b)(4,5)",9);
assertEq("var y = 0; var add = function(a) y += a; add(5); add(3); y", {var y = 0; var add = function(a) y += a; add(5); add(3); y;});
assertEq("var a = [1,[2,[3,[4,null]]]]; var t = 0; while( a != null ) { t += a[0]; a = a[1]; }; t",10);
assertEq("var a = false; do { a = true; } while (!a); a;",true);
assertEq("var t = 0; for( x in 1...10 ) t += x; t", 45);
@@ -100,14 +98,6 @@ class MiscCase extends TestCase {
assertEq("var t = 0; for( x in new IntIter(1,10) ) t +=x; t", 45);
#end
assertEq("var x = 1; try { var x = 66; throw 789; } catch( e : Dynamic ) e + x",{var x = 1; try { var x = 66; throw 789; } catch( e : Dynamic ) e + x;});
assertEq("var x = 1; var f = function(x) throw x; try f(55) catch( e : Dynamic ) e + x",{
var x = 1;
var f:Dynamic = function(x) throw x;
try
f(55)
catch( e:Dynamic )
e + x;
});
assertEq("var i=2; if( true ) --i; i",1);
assertEq("var i=0; if( i++ > 0 ) i=3; i",1);
assertEq("var a = 5/2; a",2.5);
@@ -133,6 +123,32 @@ class MiscCase extends TestCase {
//assertEq("var f:(x:Int)->(Int, Int)->Int = (x:Int) -> (y:Int, z:Int) -> x + y + z; f(3)(1, 2)", {var f:(x:Int)->(Int, Int)->Int = (x:Int) -> (y:Int, z:Int) -> x + y + z; f(3)(1, 2);});
assertEq("var a = 10; var b = 5; a - -b", 15);
assertEq("var a = 10; var b = 5; a - b / 2", 7.5);
Util.runKnownBug("Redefining a variable in a scope overrides the previous definition", () -> {
assertEq("{ var x = 0; } x",55,{ x : 55 });
});
Util.runKnownBug("Global Y isnt updated from the inside of the function", () -> {
assertEq("var y = 0; var add = function(a) y += a; add(5); add(3); y", {var y = 0; var add = function(a) y += a; add(5); add(3); y;});
});
Util.runKnownBug("Throwing an exception inside a function doesnt return the correct value", () -> {
assertEq("var x = 1; var f = function(x) throw x; try f(55) catch( e : Dynamic ) e + x",{
var x = 1;
var f:Dynamic = function(x) throw x;
try
f(55)
catch( e:Dynamic )
e + x;
});
});
assertEq("var a = if( true ) 1 else 2; a",1);
assertEq("var a = if( false ) 1 else 2; a",2);
assertEq("if(true) 1; null", null);
assertEq("if(false) 1; null", null);
assertEq("[55,66,77][1]",66);
}
override function teardown() {
+7 -3
View File
@@ -109,8 +109,6 @@ class StdCase extends TestCase {
// whitespace: space, horizontal tab, newline, vertical tab, form feed, and carriage return
assertEq("Std.parseInt(' 5')", Std.parseInt(" 5"));
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r16')", Std.parseInt(" \t\n\x0b\x0c\r16"));
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r0xa')", Std.parseInt(" \t\n\x0b\x0c\r0xa"));
// whitespace and signs
assertEq("Std.parseInt(' 16')", Std.parseInt(" 16"));
@@ -164,7 +162,6 @@ class StdCase extends TestCase {
// signs
assertEq("Std.parseFloat('123.45')", Std.parseFloat('123.45'));
assertEq("Std.parseFloat('+123.45')", Std.parseFloat('+123.45'));
assertEq("Std.parseFloat(' \\t\\n\\x0b\\x0c\\r1.6')", Std.parseFloat('\t\n\x0b\x0c\r1.6'));
// whitespace: space, horizontal tab, newline, vertical tab, form feed, and carriage return
// whitespace and signs
assertEq("Std.parseFloat(' 1.6')", Std.parseFloat(' 1.6'));
@@ -202,6 +199,13 @@ class StdCase extends TestCase {
// Std.random(1) == 0;
// Std.random(0) == 0;
// Std.random(-100) == 0;
Util.runKnownBug("Not all escape sequences are supported", () -> {
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r16')", Std.parseInt(" \t\n\x0b\x0c\r16"));
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r0xa')", Std.parseInt(" \t\n\x0b\x0c\r0xa"));
assertEq("Std.parseFloat(' \\t\\n\\x0b\\x0c\\r1.6')", Std.parseFloat('\t\n\x0b\x0c\r1.6'));
});
}
override function teardown() {
+22 -8
View File
@@ -17,8 +17,6 @@ class StringCase extends TestCase {
var str = "foo";
assertEq('var str = "foo"; str == new String(str)', str == new String(str));
//Util.runKnownBug("Using StringTools; not working", () -> {
//});
assertEq('"foo".toUpperCase()', "foo".toUpperCase());
assertEq('"_bar".toUpperCase()', "_bar".toUpperCase());
assertEq('"123b".toUpperCase()', "123b".toUpperCase());
@@ -91,18 +89,34 @@ class StringCase extends TestCase {
assertEq('("a" > "b")', ("a" > "b"));
assertEq('("a" >= "b")', ("a" >= "b"));
assertEq("'${5}'", '${5}');
assertEq("'${5},${({})}'", '${5},${({})}');
assertEq("'${5},${\"Hello\"}'", '${5},${"Hello"}');
assertEq("'${5},${'Hello'}'", '${5},${'Hello'}');
assertEq("'$${5}'", '$${5}');
assertEq("'$${({})}'", '$${({})}');
Util.runKnownBug("String interpolation doesnt work", () -> {
assertEq("'${5}'", '${5}');
assertEq("'${5},${({})}'", '${5},${({})}');
assertEq("'${5},${\"Hello\"}'", '${5},${"Hello"}');
assertEq("'${5},${'Hello'}'", '${5},${'Hello'}');
assertEq("'$${5}'", '$${5}');
assertEq("'$${({})}'", '$${({})}');
});
assertEq('"$${5}"', "${5}");
assertEq('"$${5},$${({})}"', "${5},${({})}");
assertEq('"$${5},$${\\"Hello\\"}"', "${5},${\"Hello\"}");
assertEq('"$${5},$${\'Hello\'}"', "${5},${'Hello'}");
assertEq('"foo".toUpperCase()', "foo".toUpperCase());
assertEq('"_bar".toUpperCase()', "_bar".toUpperCase());
assertEq('"123b".toUpperCase()', "123b".toUpperCase());
assertEq('"".toUpperCase()', "".toUpperCase());
assertEq('"A".toUpperCase()', "A".toUpperCase());
assertEq('"FOO".toLowerCase()', "FOO".toLowerCase());
assertEq('"_BAR".toLowerCase()', "_BAR".toLowerCase());
assertEq('"_BAR".toLowerCase()', "_BAR".toLowerCase());
assertEq('"123B".toLowerCase()', "123B".toLowerCase());
assertEq('"".toLowerCase()', "".toLowerCase());
assertEq('"a".toLowerCase()', "a".toLowerCase());
}
override function teardown() {
+4 -1
View File
@@ -24,8 +24,11 @@ class SwitchCase extends TestCase {
assertEq("switch(5) { case 1|4: 'error'; case 5: 0; default: -1; }", switch(5) { case 1|4: 'error'; case 5: 0; default: -1; });
assertEq("switch(5) { case 1|4: 'error'; default: -1; }", switch(5) { case 1|4: 'error'; default: -1; });
assertEq("switch(5) { case (1|4): 'error'; default: -1; }", switch(5) { case (1|4): 'error'; default: -1; });
assertEq("switch(5) { case 1,4: 'error'; case 5: 0; default: -1; }", switch(5) { case 1,4: 'error'; case 5: 0; default: -1; });
Util.runKnownBug("Switch statement with multiple cases in parentheses", () -> {
assertEq("switch(5) { case (1|4): 'error'; default: -1; }", switch(5) { case (1|4): 'error'; default: -1; });
});
}
override function teardown() {
+4 -4
View File
@@ -7,9 +7,9 @@ class TestCase extends HScriptRunner {
if(message == null)
message = script;
var result = if(vars != null)
executeWithVars(headerCode + script + tailCode, vars);
executeWithVars(script, vars);
else
execute(headerCode + script + tailCode);
execute(script);
Util.assertEq(result, expected, message, pos);
}
@@ -17,9 +17,9 @@ class TestCase extends HScriptRunner {
if(message == null)
message = script;
var result = if(vars != null)
executeWithVars(headerCode + script + tailCode, vars);
executeWithVars(script, vars);
else
execute(headerCode + script + tailCode);
execute(script);
Util.assertNeq(result, expected, message, pos);
}
}