optimizers

This commit is contained in:
Irmen de Jong 2024-02-05 00:53:18 +01:00
parent 32afcbfe42
commit 6b87cbb703
10 changed files with 96 additions and 104 deletions

View File

@ -7,6 +7,7 @@ import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.DataType
import prog8.code.core.IntegerDatatypes
import prog8.code.core.IntegerDatatypesWithBoolean
import prog8.code.core.Position
import kotlin.math.*
@ -69,15 +70,15 @@ class ConstExprEvaluator {
}
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
if(left.type==DataType.UBYTE || left.type==DataType.BOOL) {
if(right.type in IntegerDatatypesWithBoolean) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
} else if(left.type==DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() xor right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type== DataType.LONG) {
} else if(left.type==DataType.LONG) {
if(right.type in IntegerDatatypes) {
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
@ -86,15 +87,15 @@ class ConstExprEvaluator {
}
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
if(left.type==DataType.UBYTE || left.type==DataType.BOOL) {
if(right.type in IntegerDatatypesWithBoolean) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
} else if(left.type==DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() or right.number.toInt() and 65535).toDouble(), left.position)
}
} else if(left.type== DataType.LONG) {
} else if(left.type==DataType.LONG) {
if(right.type in IntegerDatatypes) {
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
@ -103,11 +104,11 @@ class ConstExprEvaluator {
}
private fun bitwiseAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
if(left.type==DataType.UBYTE || left.type==DataType.BOOL) {
if(right.type in IntegerDatatypesWithBoolean) {
return NumericLiteral(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
} else if(left.type==DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteral(DataType.UWORD, (left.number.toInt() and right.number.toInt() and 65535).toDouble(), left.position)
}

View File

@ -36,9 +36,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
if(parent is Assignment) {
val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
val casted = numLiteral.cast(iDt.getOr(DataType.UNDEFINED))
val casted = numLiteral.cast(iDt.getOr(DataType.UNDEFINED), true)
if(casted.isValid) {
return listOf(IAstModification.ReplaceNode(numLiteral, casted.value!!, parent))
return listOf(IAstModification.ReplaceNode(numLiteral, casted.valueOrZero(), parent))
}
}
}
@ -313,23 +313,23 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteral, targetDt: DataType, rangeTo: NumericLiteral, stepLiteral: NumericLiteral?, range: RangeExpression): RangeExpression? {
val fromCast = rangeFrom.cast(targetDt)
val toCast = rangeTo.cast(targetDt)
val fromCast = rangeFrom.cast(targetDt, true)
val toCast = rangeTo.cast(targetDt, true)
if(!fromCast.isValid || !toCast.isValid)
return null
val newStep =
if(stepLiteral!=null) {
val stepCast = stepLiteral.cast(targetDt)
val stepCast = stepLiteral.cast(targetDt, true)
if(stepCast.isValid)
stepCast.value!!
stepCast.valueOrZero()
else
range.step
} else {
range.step
}
return RangeExpression(fromCast.value!!, toCast.value!!, newStep, range.position)
return RangeExpression(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
@ -386,9 +386,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val valueDt = numval.inferType(program)
if(valueDt isnot decl.datatype) {
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
val cast = numval.cast(decl.datatype)
val cast = numval.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.value!!, decl))
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
}

View File

@ -35,12 +35,12 @@ class VarConstantValueTypeAdjuster(
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type == DataType.FLOAT) {
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
} else if(decl.datatype!=DataType.BOOL) {
// cast the numeric literal to the appropriate datatype of the variable if it's not boolean
declConstValue.linkParents(decl)
val cast = declConstValue.cast(decl.datatype)
val cast = declConstValue.cast(decl.datatype, true)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.value!!, decl))
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
}
} catch (x: UndefinedSymbolError) {
@ -292,7 +292,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
val dt = identifier.inferType(program)
if(!dt.isKnown || !dt.isNumeric)
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
return noModifications
try {
@ -313,7 +313,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
}
}
when (cval.type) {
in NumericDatatypes -> {
in NumericDatatypesWithBoolean -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
return listOf(
@ -369,7 +369,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteral
if (litval!=null && litval.type in IntegerDatatypes) {
if (litval!=null && litval.type in IntegerDatatypesWithBoolean) {
val newValue = NumericLiteral(DataType.FLOAT, litval.number, litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
@ -470,12 +470,11 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
// arraysize initializer is a single int, and we know the array size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
}
@ -485,9 +484,12 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
// arraysize initializer is a single value, and we know the array size.
if(numericLv.type!=DataType.BOOL) {
errors.err("initializer value is not a boolean", numericLv.position)
return null
}
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
}
}

View File

@ -19,9 +19,7 @@ import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has?
class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget) : AstWalker() {
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
@ -31,9 +29,9 @@ class ExpressionSimplifier(private val program: Program,
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteral
if (literal != null) {
val newLiteral = literal.cast(typecast.type)
if (newLiteral.isValid && newLiteral.value!! !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.value!!, parent)
val newLiteral = literal.cast(typecast.type, typecast.implicit)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
}
}
@ -255,26 +253,51 @@ class ExpressionSimplifier(private val program: Program,
}
}
// boolvar & 1 --> boolvar
// boolvar & 2 --> false
if(expr.operator=="&" && rightDt in IntegerDatatypes && (leftDt == DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) {
if(rightVal?.number==1.0) {
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
} else if(rightVal?.number!=null && (rightVal.number.toInt() and 1)==0) {
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
}
}
if(leftDt==DataType.BOOL) {
// optimize boolean constant comparisons
// if(expr.operator=="==" && rightVal?.number==0.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
// if(expr.operator=="!=" && rightVal?.number==1.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
if(expr.operator=="==" && rightVal?.number==1.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(expr.operator=="!=" && rightVal?.number==0.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(rightDt==DataType.BOOL && expr.operator in arrayOf("and", "or", "xor")) {
if(leftVal!=null) {
val result = if(leftVal.asBooleanValue) {
when(expr.operator) {
"and" -> expr.right
"or" -> NumericLiteral.fromBoolean(true, expr.position)
"xor" -> PrefixExpression("not", expr.right, expr.position)
else -> throw FatalAstException("weird op")
}
} else {
when(expr.operator) {
"and" -> NumericLiteral.fromBoolean(false, expr.position)
"or" -> expr.right
"xor" -> expr.right
else -> throw FatalAstException("weird op")
}
}
return listOf(IAstModification.ReplaceNode(expr, result, parent))
}
else if(rightVal!=null) {
val result = if(rightVal.asBooleanValue) {
when(expr.operator) {
"and" -> expr.left
"or" -> NumericLiteral.fromBoolean(true, expr.position)
"xor" -> PrefixExpression("not", expr.left, expr.position)
else -> throw FatalAstException("weird op")
}
} else {
when(expr.operator) {
"and" -> NumericLiteral.fromBoolean(false, expr.position)
"or" -> expr.left
"xor" -> expr.left
else -> throw FatalAstException("weird op")
}
}
return listOf(IAstModification.ReplaceNode(expr, result, parent))
}
}
}
// simplify when a term is constant and directly determines the outcome
@ -387,7 +410,6 @@ class ExpressionSimplifier(private val program: Program,
return noModifications
}
private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
val rightB = expr.right as? BinaryExpression
if(rightB!=null) {

View File

@ -3,7 +3,6 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.code.core.CompilationOptions
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
@ -60,8 +59,8 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
return inliner.applyModifications()
}
fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
val opti = ExpressionSimplifier(this, errors, target)
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
val opti = ExpressionSimplifier(this, errors)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -83,7 +83,7 @@ class StatementOptimizer(private val program: Program,
// empty true part? switch with the else part
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = BinaryExpression(ifElse.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, ifElse.condition.position), ifElse.condition.position)
val invertedCondition = invertCondition(ifElse.condition, program)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
@ -445,38 +445,6 @@ class StatementOptimizer(private val program: Program,
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
}
if(whenStmt.condition.inferType(program).isBool) {
if(whenStmt.choices.all { it.values?.size==1 }) {
if (whenStmt.choices.all { it.values!!.single().constValue(program)!!.number in arrayOf(0.0, 1.0) }) {
// it's a when statement on booleans that can just be replaced by an if or if-else.
if (whenStmt.choices.size == 1) {
return if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
replaceWithIf(whenStmt.condition, whenStmt.choices[0].statements, null)
} else {
val notCondition = BinaryExpression(whenStmt.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, whenStmt.condition.position), whenStmt.condition.position)
replaceWithIf(notCondition, whenStmt.choices[0].statements, null)
}
} else if (whenStmt.choices.size == 2) {
var trueBlock: AnonymousScope? = null
var elseBlock: AnonymousScope? = null
if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
trueBlock = whenStmt.choices[0].statements
} else {
elseBlock = whenStmt.choices[0].statements
}
if(whenStmt.choices[1].values!![0].constValue(program)!!.number==1.0) {
trueBlock = whenStmt.choices[1].statements
} else {
elseBlock = whenStmt.choices[1].statements
}
if(trueBlock!=null && elseBlock!=null) {
return replaceWithIf(whenStmt.condition, trueBlock, elseBlock)
}
}
}
}
}
val constantValue = whenStmt.condition.constValue(program)?.number
if(constantValue!=null) {
// when condition is a constant

View File

@ -430,7 +430,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
removeUnusedCode(program, errors,compilerOptions)
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(errors, compilerOptions.compTarget)
val optsDone1 = program.simplifyExpressions(errors)
val optsDone2 = program.optimizeStatements(errors, functions, compilerOptions)
val optsDone3 = program.inlineSubroutines(compilerOptions)
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away

View File

@ -1100,7 +1100,7 @@ internal class AstChecker(private val program: Program,
errors.err("this expression doesn't return a value", typecast.expression.position)
if(typecast.expression is NumericLiteral) {
val castResult = (typecast.expression as NumericLiteral).cast(typecast.type)
val castResult = (typecast.expression as NumericLiteral).cast(typecast.type, typecast.implicit)
if(castResult.isValid)
throw FatalAstException("cast should have been performed in const eval already")
errors.err(castResult.whyFailed!!, typecast.expression.position)
@ -1673,11 +1673,11 @@ internal class AstChecker(private val program: Program,
is IdentifierReference -> it.nameInSource.hashCode() and 0xffff
is TypecastExpression -> {
val constVal = it.expression.constValue(program)
val cast = constVal?.cast(it.type)
val cast = constVal?.cast(it.type, true)
if(cast==null || !cast.isValid)
-9999999
else
cast.value!!.number.toInt()
cast.valueOrZero().number.toInt()
}
else -> -9999999
}

View File

@ -205,9 +205,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return modifications
} else {
fun castLiteral(cvalue2: NumericLiteral): List<IAstModification.ReplaceNode> {
val cast = cvalue2.cast(targettype)
val cast = cvalue2.cast(targettype, true)
return if(cast.isValid)
listOf(IAstModification.ReplaceNode(assignment.value, cast.value!!, assignment))
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
else
emptyList()
}
@ -314,7 +314,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val modifications = mutableListOf<IAstModification>()
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.value
val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(DataType.UWORD, true)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
else
@ -328,7 +328,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val modifications = mutableListOf<IAstModification>()
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.value
val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(DataType.UWORD, true)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
else
@ -349,9 +349,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType))
return noModifications
if (returnValue is NumericLiteral) {
val cast = returnValue.cast(subReturnType)
val cast = returnValue.cast(subReturnType, true)
if(cast.isValid)
returnStmt.value = cast.value
returnStmt.value = cast.valueOrZero()
} else {
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType, returnStmt)
@ -399,12 +399,12 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(sourceDt == requiredType)
return
if(expressionToCast is NumericLiteral && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
val castedValue = expressionToCast.cast(requiredType)
val castedValue = expressionToCast.cast(requiredType, true)
if (castedValue.isValid) {
val signOriginal = sign(expressionToCast.number)
val signCasted = sign(castedValue.value!!.number)
val signCasted = sign(castedValue.valueOrZero().number)
if(signOriginal==signCasted) {
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.value!!, parent)
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
}
return
}

View File

@ -48,9 +48,9 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return listOf(IAstModification.ReplaceNode(typecast, constValue, parent))
if(typecast.expression is NumericLiteral) {
val value = (typecast.expression as NumericLiteral).cast(typecast.type) // TODO: add param typecast.implicit
val value = (typecast.expression as NumericLiteral).cast(typecast.type, typecast.implicit)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.value!!, parent)) // TODO: value.valueOrZero()
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
val sourceDt = typecast.expression.inferType(program)