diff --git a/compiler/examples/mandelbrot.p8 b/compiler/examples/mandelbrot.p8 index b858e592f..763734a4c 100644 --- a/compiler/examples/mandelbrot.p8 +++ b/compiler/examples/mandelbrot.p8 @@ -23,7 +23,6 @@ _vm_gfx_clearscr(11) _vm_gfx_text(5, 5, 7, "Calculating Mandelbrot Fractal...") - flt(44) for pixely in yoffset to yoffset+height-1 { yy = flt((pixely-yoffset))/height/3.6+0.4 diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 4d55e4ea7..b8d5ab5bb 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -807,6 +807,15 @@ class LiteralValue(val type: DataType, fun fromBoolean(bool: Boolean, position: Position) = LiteralValue(DataType.BYTE, bytevalue = if(bool) 1 else 0, position=position) + fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue { + return when(type) { + DataType.BYTE -> LiteralValue(type, bytevalue = value.toShort(), position = position) + DataType.WORD -> LiteralValue(type, wordvalue = value.toInt(), position = position) + DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position) + else -> throw FatalAstException("non numeric datatype") + } + } + fun optimalNumeric(value: Number, position: Position): LiteralValue { val floatval = value.toDouble() return if(floatval == floor(floatval) && floatval in -32768..65535) { diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index dc394e2b4..232af7ee2 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -268,6 +268,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: val constVal = assignment.value.constValue(namespace) if(constVal!=null) { checkValueTypeAndRange(targetDatatype, null, assignment.value as LiteralValue) + // todo: fix crash here when assignment value is a functioncall sounch as round() } else { val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace) if(sourceDatatype==null) { diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index 36e08f34b..a56dc43ea 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -111,10 +111,10 @@ private fun oneDoubleArgOutputInt(args: List, position: Position, n if(args.size!=1) throw SyntaxError("built-in function requires one floating point argument", position) val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException() - if(constval.type!=DataType.FLOAT) - throw SyntaxError("built-in function requires one floating point argument", position) - - val float = constval.asNumericValue?.toDouble()!! + val float: Double = when(constval.type) { + DataType.BYTE, DataType.WORD, DataType.FLOAT -> constval.asNumericValue!!.toDouble() + else -> throw SyntaxError("built-in function requires one floating point argument", position) + } return numericLiteral(function(float).toInt(), args[0].position) } diff --git a/compiler/src/prog8/optimizing/SimplifyExpressions.kt b/compiler/src/prog8/optimizing/SimplifyExpressions.kt index 4f26b6913..50bff685d 100644 --- a/compiler/src/prog8/optimizing/SimplifyExpressions.kt +++ b/compiler/src/prog8/optimizing/SimplifyExpressions.kt @@ -1,57 +1,19 @@ package prog8.optimizing import prog8.ast.* +import kotlin.math.abs /* todo simplify expression terms: - X*0 -> 0 - X*1 -> X - X*2 -> X << 1 - X*3 -> X + (X << 1) - X*4 -> X << 2 - X*5 -> X + (X << 2) - X*6 -> (X<<1) + (X << 2) - X*7 -> X + (X<<1) + (X << 2) - X*8 -> X << 3 - X*9 -> X + (X<<3) - X*10 -> (X<<1) + (X<<3) - X/1, X//1, X**1 -> just X - X/2, X//2 -> X >> 1 (if X is byte or word) - X/4, X//4 -> X >> 2 (if X is byte or word) - X/8, X//8 -> X >> 3 (if X is byte or word) - X/16, X//16 -> X >> 4 (if X is byte or word) - X/32, X//32 -> X >> 5 (if X is byte or word) - X/64, X//64 -> X >> 6 (if X is byte or word) - X/128, X//128 -> X >> 7 (if X is byte or word) - X / (n>=256) -> 0 (if x is byte) X >> 8 (if X is word) - X // (n>=256) -> 0 (if x is byte) X >> 8 (if X is word) - - X+X -> X << 1 - X << n << m -> X << (n+m) - - 1**X -> 1 - 0**X -> 0 - X*-1 -> unary prefix -X - X**0 -> 1 - X**1 -> X - X**2 -> X*X - X**3 -> X*X*X - X**0.5 -> sqrt(X) - X**-1 -> 1.0/X - X**-2 -> 1.0/X/X - X**-3 -> 1.0/X/X/X - X << 0 -> X X*Y - X -> X*(Y-1) + X*Y - Y -> Y*(X-1) -X + A -> A - X - -X - A -> -(X+A) + X+ (-A) -> X - A X % 1 -> constant 0 (if X is byte/word) X % 2 -> X and 1 (if X is byte/word) - todo expression optimization: remove redundant builtin function calls - todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2) - todo optimize addition with self into shift 1 (A+=A -> A<<=1) todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call) */ @@ -122,18 +84,264 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor { } } "|", "^" -> { - if(leftVal!=null && !leftVal.asBooleanValue) + if(leftVal!=null && !leftVal.asBooleanValue) { + optimizationsDone++ return expr.right - if(rightVal!=null && !rightVal.asBooleanValue) + } + if(rightVal!=null && !rightVal.asBooleanValue) { + optimizationsDone++ return expr.left + } } "&" -> { - if(leftVal!=null && !leftVal.asBooleanValue) + if(leftVal!=null && !leftVal.asBooleanValue) { + optimizationsDone++ return constFalse - if(rightVal!=null && !rightVal.asBooleanValue) + } + if(rightVal!=null && !rightVal.asBooleanValue) { + optimizationsDone++ return constFalse + } } + "*" -> return optimizeMultiplication(expr, leftVal, rightVal) + "/", "//" -> return optimizeDivision(expr, leftVal, rightVal) + "+" -> return optimizeAdd(expr, leftVal, rightVal) + "-" -> return optimizeSub(expr, leftVal, rightVal) + "**" -> return optimizePower(expr, leftVal, rightVal) } return expr } + + private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: LiteralValue?, val rightVal: LiteralValue?) + + private fun reorderAssociative(expr: BinaryExpression, leftVal: LiteralValue?): ReorderedAssociativeBinaryExpr { + val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=") + if(associativeOperators.contains(expr.operator) && leftVal!=null) { + // swap left and right so that right is always the constant + val tmp = expr.left + expr.left = expr.right + expr.right = tmp + optimizationsDone++ + return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace), leftVal) + } + return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace)) + } + + private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression { + if(pleftVal==null && prightVal==null) + return pexpr + + val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal) + if(rightVal!=null) { + // right value is a constant, see if we can optimize + val rightConst: LiteralValue = rightVal + when(rightConst.asNumericValue?.toDouble()) { + 0.0 -> { + // left + optimizationsDone++ + return expr.left + } + } + } + // no need to check for left val constant (because of associativity) + + return expr + } + + private fun optimizeSub(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression { + if(leftVal==null && rightVal==null) + return expr + + if(rightVal!=null) { + // right value is a constant, see if we can optimize + val rightConst: LiteralValue = rightVal + when(rightConst.asNumericValue?.toDouble()) { + 0.0 -> { + // left + optimizationsDone++ + return expr.left + } + } + } + if(leftVal!=null) { + // left value is a constant, see if we can optimize + when(leftVal.asNumericValue?.toDouble()) { + 0.0 -> { + // -right + optimizationsDone++ + return PrefixExpression("-", expr.right, expr.position) + } + } + } + + return expr + } + + private fun optimizePower(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression { + if(leftVal==null && rightVal==null) + return expr + + if(rightVal!=null) { + // right value is a constant, see if we can optimize + val rightConst: LiteralValue = rightVal + when(rightConst.asNumericValue?.toDouble()) { + -3.0 -> { + // -1/(left*left*left) + optimizationsDone++ + return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/", + BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position), + expr.position) + } + -2.0 -> { + // -1/(left*left) + optimizationsDone++ + return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/", + BinaryExpression(expr.left, "*", expr.left, expr.position), + expr.position) + } + -1.0 -> { + // -1/left + optimizationsDone++ + return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/", + expr.left, expr.position) + } + 0.0 -> { + // 1 + optimizationsDone++ + return LiteralValue.fromNumber(1, rightConst.type, expr.position) + } + 0.5 -> { + // sqrt(left) + optimizationsDone++ + return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), listOf(expr.left), expr.position) + } + 1.0 -> { + // left + optimizationsDone++ + return expr.left + } + 2.0 -> { + // left*left + optimizationsDone++ + return BinaryExpression(expr.left, "*", expr.left, expr.position) + } + 3.0 -> { + // left*left*left + optimizationsDone++ + return BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position) + } + } + } + if(leftVal!=null) { + // left value is a constant, see if we can optimize + when(leftVal.asNumericValue?.toDouble()) { + -1.0 -> { + // -1 + optimizationsDone++ + return LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position) + } + 0.0 -> { + // 0 + optimizationsDone++ + return LiteralValue.fromNumber(0, leftVal.type, expr.position) + } + 1.0 -> { + //1 + optimizationsDone++ + return LiteralValue.fromNumber(1, leftVal.type, expr.position) + } + + } + } + + return expr + } + + private fun optimizeDivision(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression { + if(leftVal==null && rightVal==null) + return expr + + if(rightVal!=null) { + // right value is a constant, see if we can optimize + val rightConst: LiteralValue = rightVal + when(rightConst.asNumericValue?.toDouble()) { + -1.0 -> { + // '/' -> -left, '//' -> -ceil(left) + optimizationsDone++ + when(expr.operator) { + "/" -> return PrefixExpression("-", expr.left, expr.position) + "//" -> return PrefixExpression("-", + FunctionCall(IdentifierReference(listOf("ceil"), expr.position), listOf(expr.left), expr.position), + expr.position) + } + } + 1.0 -> { + // '/' -> left, '//' -> floor(left) + optimizationsDone++ + when(expr.operator) { + "/" -> return expr.left + "//" -> return FunctionCall(IdentifierReference(listOf("floor"), expr.position), listOf(expr.left), expr.position) + } + } + } + + if (expr.left.resultingDatatype(namespace) == DataType.BYTE) { + if(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) { + optimizationsDone++ + return LiteralValue(DataType.BYTE, 0, position = expr.position) + } + } + else if (expr.left.resultingDatatype(namespace) == DataType.WORD) { + if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) { + optimizationsDone++ + return LiteralValue(DataType.BYTE, 0, position = expr.position) + } + } + } + + if(leftVal!=null) { + // left value is a constant, see if we can optimize + when(leftVal.asNumericValue?.toDouble()) { + 0.0 -> { + // 0 + optimizationsDone++ + return LiteralValue.fromNumber(0, leftVal.type, expr.position) + } + } + } + + return expr + } + + private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression { + if(pleftVal==null && prightVal==null) + return pexpr + + val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal) + if(rightVal!=null) { + // right value is a constant, see if we can optimize + val leftValue: IExpression = expr.left + val rightConst: LiteralValue = rightVal + when(rightConst.asNumericValue?.toDouble()) { + -1.0 -> { + // -left + optimizationsDone++ + return PrefixExpression("-", leftValue, expr.position) + } + 0.0 -> { + // 0 + optimizationsDone++ + return LiteralValue.fromNumber(0, rightConst.type, expr.position) + } + 1.0 -> { + // left + optimizationsDone++ + return expr.left + } + } + } + // no need to check for left val constant (because of associativity) + + return expr + } } \ No newline at end of file