Optimization on string constants

This commit is contained in:
NeeEoo
2024-04-25 19:03:49 +02:00
parent d38ddbb2b4
commit 238c24501b
7 changed files with 208 additions and 31 deletions
+1
View File
@@ -51,6 +51,7 @@ enum Error
EInvalidPreprocessor( msg : String );
EUnknownVariable( v : String );
EInvalidIterator( v : String );
EInvalidType( t : String );
EInvalidOp( op : String );
EInvalidAccess( f : String );
ECustom( msg : String );
+1
View File
@@ -159,6 +159,7 @@ class Interp {
variables.set("true", true);
variables.set("false", false);
#if !NO_FROM_CHAR_CODE_FIX
// DONT CALL THIS DIRECTLY, USE String.fromCharCode, the preprocessor will call it for you
variables.set("__StringWorkaround__fromCharCode", function(a:Int) { // TODO: make hscript only add this if its used
return String.fromCharCode(a);
});
+180 -19
View File
@@ -6,6 +6,8 @@ import hscript.Parser;
@:access(hscript.Parser)
class Optimizer {
static inline function expr(e:Expr) return Tools.expr(e);
public static function optimize(s:Expr):Expr {
if(s == null)
return null;
@@ -54,8 +56,8 @@ class Optimizer {
var econd = optimize(econd);
var e1 = optimize(e1);
var e2 = optimize(e2);
if(isConstant(econd)) {
var econd = getConstant(econd);
if(isBool(econd)) {
var econd = getBool(econd);
if(econd == true) {
if(e1 == null)
return mk(EBlock([]), s);
@@ -66,6 +68,27 @@ class Optimizer {
return mk(EBlock([e2]), s);
}
}
if(e2 != null && isBool(e1) && isBool(e2)) {
var c1 = getBool(e1);
var c2 = getBool(e2);
if(c1 == false && c2 == true) { // (VAR ? false : true)
return optimize(mk(EUnop("!", true, econd), s));
}
if(c1 == true && c2 == false) { // (VAR ? true : false)
return optimize(mk(Tools.expr(econd), s));
}
if(isConstant(econd)) { // Side effect free
if(c1 == true && c2 == true) { // (CONST ? true : true)
return mk(convertConstant(true), s);
}
if(c1 == false && c2 == false) { // (CONST ? false : false)
return mk(convertConstant(false), s);
}
// TODO: Check if local variables are used in the condition
// Since they cant have side effects, they can be optimized
}
}
return mk(EIf(econd, e1, e2), s);
case ETernary(econd, e1, e2):
@@ -94,7 +117,7 @@ class Optimizer {
if(c1 == true && c2 == false) { // (VAR ? true : false)
return optimize(mk(Tools.expr(econd), s));
}
if(isConstant(econd)) {
if(isConstant(econd)) { // Side effect free
if(c1 == true && c2 == true) { // (CONST ? true : true)
return mk(convertConstant(true), s);
}
@@ -146,23 +169,86 @@ class Optimizer {
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:
function p(i:Int, t:ConstType, opt:Bool = false):Dynamic {
var p = params[i];
if(t != getConstType(p))
if(opt)
return null;
else
throw Parser.getBaseError(EInvalidType(getTypeName(p)));
return switch(t) {
case CTInt: getInt(p);
case CTFloat: getFloat(p);
case CTBool: getBool(p);
case CTString: getStringConstant(p);
case CTNull: null;
}
}
switch(Tools.expr(e)) {
case EField(expr(_) => EConst(CString(str)), field):
switch(field) {
case "toString":
if(params.length != 0) throw Parser.getBaseError(ECustom("String.toString() takes no arguments"));
return mk(convertConstant(str.toString()), s);
case "toUpperCase":
if(params.length != 0) throw Parser.getBaseError(ECustom("String.toUpperCase() takes no arguments"));
return mk(convertConstant(str.toUpperCase()), s);
case "toLowerCase":
if(params.length != 0) throw Parser.getBaseError(ECustom("String.toLowerCase() takes no arguments"));
return mk(convertConstant(str.toLowerCase()), s);
case "charAt":
var index = p(0, CTInt);
return mk(convertConstant(str.charAt(index)), s);
case "charCodeAt":
var index = p(0, CTInt);
return mk(convertConstant(str.charCodeAt(index)), s);
case "indexOf":
var value = p(0, CTString);
var startIndex = p(1, CTInt, true);
return mk(convertConstant(str.indexOf(value, startIndex)), s);
case "lastIndexOf":
var value = p(0, CTString);
var startIndex = p(1, CTInt, true);
return mk(convertConstant(str.lastIndexOf(value, startIndex)), s);
case "split":
var delimiter = p(0, CTString);
var strArr = str == "" ? [] : str.split(delimiter); // fix platform dependent behavior
return mk(EArrayDecl([for(st in strArr) mk(EConst(CString(st)), s)]), s);
case "substr":
var pos = p(0, CTInt);
var len = p(1, CTInt, true);
return mk(convertConstant(str.substr(pos, len)), s);
case "substring":
var startIndex = p(0, CTInt);
var endIndex = p(1, CTInt, true);
return mk(convertConstant(str.substring(startIndex, endIndex)), s);
}
default:
}
switch(Tools.expr(e)) {
default:
}
return mk(ECall(e, params), s);
case EField(e, f, safe):
e = optimize(e);
switch(Tools.expr(e)) {
case EConst(CString(str)) if(f == "length"):
return mk(convertConstant(str.length), s);
default:
}
return mk(EField(e, f, safe), s);
case EIdent(_) | EConst(_):
@@ -226,6 +312,7 @@ class Optimizer {
}
}
// Maybe convert this to a Lambda.foreach?
var isAllCasesConstant = true;
for(c in cases) {
for(v in c.values) {
@@ -268,19 +355,23 @@ class Optimizer {
};
var constant = Lambda.exists(arr, isConstant);
if(constant) {
return mk(Tools.expr(arr[getConstant(index)]), s);
var index = getInt(index);
if(constant && index != null) {
return mk(Tools.expr(arr[index]), s);
}
}
if(Tools.expr(e).match(EMapDecl(_))) {
var map = switch(Tools.expr(e)) {
case EMapDecl(type, keys, vals): [keys, vals];
case EMapDecl(type, keys, vals): [for(i in 0...keys.length) [keys[i], vals[i]]];
default: null;
};
var constant = Lambda.exists(map, (v) -> isConstant(v[0]) && isConstant(v[1]));
if(constant) {
return mk(Tools.expr(map[getConstant(index)][1]), s);
var idx = Lambda.findIndex(map, (v) -> Type.enumEq(Tools.expr(v[0]), Tools.expr(index)));
if(idx == -1)
return mk(EIdent("null"), s);
return mk(Tools.expr(map[idx][1]), s);
}
}
}
@@ -301,6 +392,8 @@ class Optimizer {
var c1 = getNumber(e1);
if(compareNumber(c1, 0) && op == "+")
return mk(Tools.expr(e2), s);
if(compareNumber(c1, 1) && op == "*")
return mk(Tools.expr(e2), s);
//if(compareNumber(c1, 0) && op == "*")
// return mk(convertConstant(0), s);
}
@@ -311,6 +404,8 @@ class Optimizer {
return mk(Tools.expr(e1), s);
if(op == "/" && compareNumber(c2, 1))
return mk(Tools.expr(e1), s);
if(op == "*" && compareNumber(c2, 1))
return mk(Tools.expr(e1), s);
//if(op == "*" && compareNumber(c2, 0))
// return mk(convertConstant(0), s);
}
@@ -366,6 +461,35 @@ class Optimizer {
}
}
static function getTypeName(e:Expr):String {
if(e == null)
return null;
return switch(Tools.expr(e)) {
case EConst(CInt(_)): "Int";
case EConst(CFloat(_)): "Float";
case EConst(CString(_)): "String";
case EIdent("true") | EIdent("false"): "Bool";
case EIdent("null"): "Null";
case EIdent(_): "Dynamic";
case EParent(e): getTypeName(e);
default: Std.string(Tools.expr(e));
}
}
static function getConstType(e:Expr):ConstType {
if(e == null)
return null;
return switch(Tools.expr(e)) {
case EConst(CInt(_)): CTInt;
case EConst(CFloat(_)): CTFloat;
case EConst(CString(_)): CTString;
case EIdent("true") | EIdent("false"): CTBool;
case EIdent("null"): CTNull;
case EParent(e): getConstType(e);
default: throw "Unknown type " + Tools.expr(e);
}
}
static function isConstant(e:Expr):Bool {
return switch(Tools.expr(e)) {
case EIdent("true") | EIdent("false") | EIdent("null"): true;
@@ -404,15 +528,44 @@ class Optimizer {
static function getBool(e:Expr):Bool {
return switch(Tools.expr(e)) {
case EIdent("true"): true;
case EIdent("false"): true;
case EIdent("false"): false;
case EParent(e): getBool(e);
default: throw "Unknown type " + Tools.expr(e);
}
}
static function getInt(e:Expr):Null<Int> {
if(e == null)
return null;
return switch(Tools.expr(e)) {
case EConst(CInt(value)): value;
case EParent(e): getInt(e);
default: null;
}
}
static function getFloat(e:Expr):Null<Float> {
if(e == null)
return null;
return switch(Tools.expr(e)) {
case EConst(CFloat(value)): value;
case EParent(e): getFloat(e);
default: null;
}
}
static function isString(e:Expr):Bool {
return switch(Tools.expr(e)) {
case EConst(CString(_)): true;
case EParent(e): isString(e);
default: false;
}
}
static function getStringConstant(e:Expr):String {
return switch(Tools.expr(e)) {
case EConst(CString(value)): value;
case EParent(e): getStringConstant(e);
default: null;
}
}
@@ -476,4 +629,12 @@ class Optimizer {
default: null;
}
}
}
enum ConstType {
CTInt;
CTFloat;
CTBool;
CTString;
CTNull;
}
+1
View File
@@ -537,6 +537,7 @@ class Printer {
case EInvalidPreprocessor(str): "Invalid preprocessor (" + str + ")";
case EUnknownVariable(v): "Unknown variable: "+v;
case EInvalidIterator(v): "Invalid iterator: "+v;
case EInvalidType(t): "Invalid type: "+t;
case EInvalidOp(op): "Invalid operator: "+op;
case EInvalidAccess(f): "Invalid access to field " + f;
case ECustom(msg): msg;
+7 -7
View File
@@ -37,10 +37,10 @@ class Util {
var b_args = EnumValueTools.getParameters(cast b);
return deepEqual(a_args, b_args);
case TClass(_):
if (Std.isOfType(a, String) && Std.isOfType(b, String)) {
if ((a is String) && (b is String)) {
return a == b;
}
if (Std.isOfType(a, Array) && Std.isOfType(b, Array)) {
if ((a is Array) && (b is Array)) {
var a = cast(a, Array<Dynamic>);
var b = cast(b, Array<Dynamic>);
if (a.length != b.length) { return false; }
@@ -52,9 +52,9 @@ class Util {
return true;
}
if (Std.isOfType(a, IMap) && Std.isOfType(b, IMap)) {
var a = cast(a, Map<Dynamic, Dynamic>);
var b = cast(b, Map<Dynamic, Dynamic>);
if ((a is IMap) && (b is IMap)) {
var a = cast(a, IMap<Dynamic, Dynamic>);
var b = cast(b, IMap<Dynamic, Dynamic>);
var a_keys = [ for (key in a.keys()) key ];
var b_keys = [ for (key in b.keys()) key ];
a_keys.sort(Reflect.compare);
@@ -68,11 +68,11 @@ class Util {
return true;
}
if (Std.isOfType(a, Date) && Std.isOfType(b, Date)) {
if ((a is Date) && (b is Date)) {
return cast(a, Date).getTime() == cast(b, Date).getTime();
}
if (Std.isOfType(a, haxe.io.Bytes) && Std.isOfType(b, haxe.io.Bytes)) {
if ((a is haxe.io.Bytes) && (b is haxe.io.Bytes)) {
return deepEqual(cast(a, haxe.io.Bytes).getData(), cast(b, haxe.io.Bytes).getData());
}
+1
View File
@@ -83,6 +83,7 @@ class FinalCase extends TestCase {
assertEq("[0=>'hello', 1=>'world']", [0=>'hello', 1=>'world']);
assertEq("[0=>'hello', 1=>'world'][1]", [0=>'hello', 1=>'world'][1]);
assertEq("[0=>'hello', 5=>'world'][1]", [0=>'hello', 5=>'world'][1]);
// Test EOF with preprocessor
}
+17 -5
View File
@@ -151,15 +151,27 @@ class StringCase extends TestCase {
assertEq('String.fromCharCode(101)', String.fromCharCode(101));
assertEq('String.fromCharCode(102)', String.fromCharCode(102));
assertEq('String.fromCharCode(103)', String.fromCharCode(103));
assertEq('var a = 103; String.fromCharCode(a)', String.fromCharCode(test));
assertEq('var a = 103; String.fromCharCode(a)', String.fromCharCode(a));
assertError("'hello ${}'", Parser.getBaseError(EPreset(EMPTY_INTERPOLATION)));
}
static var test = 103;
assertEq('"".charAt(0)', "".charAt(0));
assertEq('"".charAt(1)', "".charAt(1));
assertEq('"".charAt(-1)', "".charAt(-1));
static function fromCharCode(a:Int) {
return String.fromCharCode(a);
headerCode = '';
assertEq("'foo1bar'.charCodeAt(0)", 'foo1bar'.charCodeAt(0));
assertEq("'foo1bar'.charCodeAt(1)", 'foo1bar'.charCodeAt(1));
assertEq("'foo1bar'.charCodeAt(2)", 'foo1bar'.charCodeAt(2));
assertEq("'foo1bar'.charCodeAt(3)", 'foo1bar'.charCodeAt(3));
assertEq("'foo1bar'.charCodeAt(4)", 'foo1bar'.charCodeAt(4));
assertEq("'foo1bar'.charCodeAt(5)", 'foo1bar'.charCodeAt(5));
assertEq("'foo1bar'.charCodeAt(6)", 'foo1bar'.charCodeAt(6));
assertEq("'foo1bar'.charCodeAt(7)", 'foo1bar'.charCodeAt(7));
assertEq("'foo1bar'.charCodeAt(-1)", 'foo1bar'.charCodeAt(-1));
assertEq("'foo1bar'.length", 'foo1bar'.length);
assertEq("'foo1bar'.split('1')", 'foo1bar'.split('1'));
}
override function teardown() {