const optimizer now knows about a bunch of library functions, such as math.*

This commit is contained in:
Irmen de Jong 2023-12-28 19:58:45 +01:00
parent 078bfefe41
commit a0594cbce3
11 changed files with 204 additions and 24 deletions

View File

@ -27,6 +27,7 @@ dependencies {
implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}

View File

@ -11,5 +11,6 @@
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>

View File

@ -1,17 +1,19 @@
package prog8.optimizer
import prog8.ast.Program
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.DataType
import prog8.code.core.IntegerDatatypes
import prog8.code.core.Position
import kotlin.math.*
class ConstExprEvaluator {
fun evaluate(left: NumericLiteral, operator: String, right: NumericLiteral): Expression {
fun evaluate(left: NumericLiteral, operator: String, right: NumericLiteral): NumericLiteral {
try {
return when(operator) {
"+" -> plus(left, right)
@ -40,7 +42,7 @@ class ConstExprEvaluator {
}
}
private fun shiftedright(left: NumericLiteral, amount: NumericLiteral): Expression {
private fun shiftedright(left: NumericLiteral, amount: NumericLiteral): NumericLiteral {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left >> $amount", left.position)
val result =
@ -51,7 +53,7 @@ class ConstExprEvaluator {
return NumericLiteral(left.type, result.toDouble(), left.position)
}
private fun shiftedleft(left: NumericLiteral, amount: NumericLiteral): Expression {
private fun shiftedleft(left: NumericLiteral, amount: NumericLiteral): NumericLiteral {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.number.toInt().shl(amount.number.toInt())
@ -218,4 +220,154 @@ class ConstExprEvaluator {
else -> throw ExpressionError(error, left.position)
}
}
fun evaluate(call: FunctionCallExpression, program: Program): NumericLiteral? {
if(call.target.nameInSource.size!=2)
return null // likely a builtin function, or user function, these get evaluated elsewhere
val constArgs = call.args.mapNotNull { it.constValue(program) }
if(constArgs.size!=call.args.size)
return null
return when(call.target.nameInSource[0]) {
"math" -> evalMath(call, constArgs)
"floats" -> evalFloats(call, constArgs)
"string" -> evalString(call, constArgs)
else -> null
}
}
private fun evalFloats(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
val result= when(func.target.nameInSource[1]) {
"pow" -> args[0].number.pow(args[1].number)
"sin" -> sin(args[0].number)
"cos" -> cos(args[0].number)
"tan" -> tan(args[0].number)
"atan" -> atan(args[0].number)
"ln" -> ln(args[0].number)
"log2" -> log2(args[0].number)
"rad" -> args[0].number/360.0 * 2 * PI
"deg" -> args[0].number/ 2 / PI * 360.0
"round" -> round(args[0].number)
"floor" -> floor(args[0].number)
"ceil" -> ceil(args[0].number)
"minf", "min" -> min(args[0].number, args[1].number)
"maxf", "max" -> max(args[0].number, args[1].number)
"clampf", "clamp" -> {
var value = args[0].number
val minimum = args[1].number
val maximum = args[2].number
if(value<minimum)
value=minimum
if(value<maximum)
value
else
maximum
}
else -> null
}
return if(result==null)
null
else
NumericLiteral(DataType.FLOAT, result, func.position)
}
private fun evalMath(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
return when(func.target.nameInSource[1]) {
"sin8u" -> {
val value = truncate(128.0 + 127.5 * sin(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"cos8u" -> {
val value = truncate(128.0 + 127.5 * cos(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"sin8" -> {
val value = truncate(127.0 * sin(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"cos8" -> {
val value = truncate(127.0 * cos(args.single().number / 256.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"sinr8u" -> {
val value = truncate(128.0 + 127.5 * sin(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"cosr8u" -> {
val value = truncate(128.0 + 127.5 * cos(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"sinr8" -> {
val value = truncate(127.0 * sin(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"cosr8" -> {
val value = truncate(127.0 * cos(args.single().number / 180.0 * 2 * PI))
NumericLiteral(DataType.BYTE, value, func.position)
}
"log2" -> {
val value = truncate(log2(args.single().number))
NumericLiteral(DataType.UBYTE, value, func.position)
}
"log2w" -> {
val value = truncate(log2(args.single().number))
NumericLiteral(DataType.UWORD, value, func.position)
}
"atan2" -> {
val x1f = args[0].number
val y1f = args[1].number
val x2f = args[2].number
val y2f = args[3].number
var radians = atan2(y2f-y1f, x2f-x1f)
if(radians<0)
radians+=2*PI
NumericLiteral(DataType.UWORD, floor(radians/2.0/PI*256.0), func.position)
}
"diff" -> {
val n1 = args[0].number
val n2 = args[1].number
val value = if(n1>n2) n1-n2 else n2-n1
NumericLiteral(DataType.UBYTE, value, func.position)
}
"diffw" -> {
val n1 = args[0].number
val n2 = args[1].number
val value = if(n1>n2) n1-n2 else n2-n1
NumericLiteral(DataType.UWORD, value, func.position)
}
else -> null
}
}
private fun evalString(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
return when(func.target.nameInSource[1]) {
"isdigit" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 48..57, func.position)
}
"isupper" -> {
// shifted petscii has 2 ranges that contain the upper case letters... 97-122 and 193-218
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 97..122 || char in 193..218, func.position)
}
"islower" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 65..90, func.position)
}
"isletter" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 65..90 || char in 97..122 || char in 193..218, func.position)
}
"isspace" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in arrayOf(32, 13, 9, 10, 141, 160), func.position)
}
"isprint" -> {
val char = args[0].number.toInt()
NumericLiteral.fromBoolean(char in 32..127 || char>=160, func.position)
}
else -> null
}
}
}

View File

@ -319,8 +319,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val constvalue = functionCallExpr.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
else
noModifications
else {
val const2 = evaluator.evaluate(functionCallExpr, program)
return if(const2!=null)
listOf(IAstModification.ReplaceNode(functionCallExpr, const2, parent))
else
noModifications
}
}
override fun after(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> {

View File

@ -399,7 +399,7 @@ fail clc ; yes, no match found, return with c=0
}
asmsub isupper(ubyte petsciichar @A) -> bool @Pc {
; shifted petscii has 2 ranges that contain the upper case letters...
; shifted petscii has 2 ranges that contain the upper case letters... 97-122 and 193-218
%asm {{
cmp #97
bcs +

View File

@ -899,4 +899,27 @@ main {
(expr.left as? IdentifierReference)?.nameInSource shouldBe listOf("yy")
(expr.right as? NumericLiteral)?.number shouldBe 10.0
}
test("advanced const folding of known library functions") {
val src="""
%import floats
%import math
%import string
main {
sub start() {
float fl = 1.2 ; no other assignments
cx16.r0L = string.isdigit(math.diff(119, floats.floor(floats.deg(fl)) as ubyte))
cx16.r1L = string.isletter(math.diff(119, floats.floor(floats.deg(1.2)) as ubyte))
}
}"""
val result = compileText(Cx16Target(), true, src, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 3
(st[0] as VarDecl).type shouldBe VarDeclType.CONST
val assignv1 = (st[1] as Assignment).value
val assignv2 = (st[2] as Assignment).value
(assignv1 as NumericLiteral).number shouldBe 1.0
(assignv2 as NumericLiteral).number shouldBe 0.0
}
})

View File

@ -72,7 +72,7 @@ Language features
- Floating point math is supported on select compiler targets.
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- Identifiers can contain Unicode Letters, so ``knäckebröd``, ``приблизительно``, ``見せしめ`` and ``π`` are all valid identifiers.
- High-level code optimizations, such as const-folding (zero-allocation constants that are optimized away in expressions), expression and statement simplifications/rewriting.
- Advanced code optimizations, such as const-folding (zero-allocation constants that are optimized away in expressions), expression and statement simplifications/rewriting.
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 as defined on the Commander X16, also on the other machines.
- Support for low level system features such as Vera Fx hardware word multiplication on the Commander X16

View File

@ -441,7 +441,11 @@ Special types: const and memory-mapped
When using ``const``, the value of the 'variable' cannot be changed; it has become a compile-time constant value instead.
You'll have to specify the initial value expression. This value is then used
by the compiler everywhere you refer to the constant (and no memory is allocated
for the constant itself). This is only valid for the simple numeric types (byte, word, float).
for the constant itself). Onlythe simple numeric types (byte, word, float) can be defined as a constant.
If something is defined as a constant, very efficient code can usually be generated from it.
Variables on the other hand can't be optimized as much, need memory, and more code to manipulate them.
Note that a subset of the library routines in the ``math``, ``string`` and ``floats`` modules are recognised in
compile time expressions. For example, the compiler knows what ``math.sin8u(12)`` is and replaces it with the computed result.
When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,
rather than being newly allocated. The initial value (mandatory) must be a valid

View File

@ -467,7 +467,8 @@ Constants
All variables can be assigned new values unless you use the ``const`` keyword.
The initial value must be known at compile time (it must be a compile time constant expression).
This is only valid for the simple numeric types (byte, word, float)::
Only the simple numeric types (byte, word, float) can be defined as a constant::
const byte max_age = 99

View File

@ -2,7 +2,6 @@
TODO
====
- add functions to const eval that we know how to evaluate (math.*, etc)
- [on branch: shortcircuit] investigate McCarthy evaluation again? this may also reduce code size perhaps for things like if a>4 or a<2 ....
...
@ -49,6 +48,7 @@ Compiler:
Libraries:
- get rid of the "f" suffix of several funtions in floats_functions (breaking change)
- once a VAL_1 implementation is merged into the X16 kernal properly, remove all the workarounds in cx16 floats.parse_f() . Prototype parse routine in examples/cx16/floatparse.p8
- fix the problems in atari target, and flesh out its libraries.
- c128 target: make syslib more complete (missing kernal routines)?

View File

@ -1,19 +1,12 @@
%import textio
%import floats
%import math
%import string
%zeropage basicsafe
main {
sub start() {
ubyte x = 10
ubyte y = 2
txt.print_ub(5<x and x<=20)
txt.nl()
txt.print_ub(5<x and x<=9)
txt.nl()
txt.print_ub(5<x<=9)
txt.nl()
txt.print_ub(5<(x-y)<=9<y)
txt.nl()
txt.print_ub(5<(x-y)<=9<(y+40))
txt.nl()
float fl = 1.2 ; no other assignments
cx16.r0L = string.isdigit(math.diff(119, floats.floor(floats.deg(fl)) as ubyte))
cx16.r1L = string.isletter(math.diff(119, floats.floor(floats.deg(1.2)) as ubyte))
}
}