From 7cd70acc0153ec238a2b147cbf406e700e100665 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 24 Apr 2026 06:02:16 +0200 Subject: [PATCH] implemented const folding of lmh() and divmod() --- .../optimizer/ConstantFoldingOptimizer.kt | 53 +++---- .../src/prog8/compiler/BuiltinFunctions.kt | 147 +++++++++++++----- compiler/src/prog8/compiler/Compiler.kt | 4 +- compiler/test/TestBuiltinFunctions.kt | 32 ++++ compiler/test/helpers/Dummies.kt | 4 +- .../src/prog8/ast/IBuiltinFunctions.kt | 3 +- .../prog8/ast/expressions/AstExpressions.kt | 12 ++ examples/test.p8 | 10 +- 8 files changed, 186 insertions(+), 79 deletions(-) diff --git a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt index 1736c18a8..b47a496a8 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt @@ -4,10 +4,7 @@ import prog8.ast.* import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.ast.walk.* -import prog8.code.core.AssociativeOperators -import prog8.code.core.BaseDataType -import prog8.code.core.IErrorReporter -import prog8.code.core.isInteger +import prog8.code.core.* import kotlin.math.floor @@ -374,29 +371,33 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors: } override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable { - if(functionCallExpr.target.nameInSource == listOf("lmh")) { - // builtin that returns 3 values instead of 1 - if(functionCallExpr.args.size==1) { - val constArg = functionCallExpr.args[0].constValue(program)?.number?.toLong() - if(constArg!=null) { - val assign = parent as? Assignment - if(assign!=null && assign.target.multi?.size==3) { - val low = constArg and 255 - val mid = (constArg shr 8) and 255 - val high = (constArg shr 16) and 255 - val assignLow = Assignment(assign.target.multi!![0], NumericLiteral(BaseDataType.UBYTE, low.toDouble(), assign.position),AssignmentOrigin.USERCODE, assign.position) - val assignMid = Assignment(assign.target.multi!![1], NumericLiteral(BaseDataType.UBYTE, mid.toDouble(), assign.position), AssignmentOrigin.USERCODE, assign.position) - val assignHigh = Assignment(assign.target.multi!![2], NumericLiteral(BaseDataType.UBYTE, high.toDouble(), assign.position), AssignmentOrigin.USERCODE, assign.position) - return listOf( - AstInsert.after(assign, assignHigh, assign.parent as IStatementContainer), - AstInsert.after(assign, assignMid, assign.parent as IStatementContainer), - AstInsert.after(assign, assignLow, assign.parent as IStatementContainer), - AstRemove(assign, assign.parent as IStatementContainer) - ) - } else { - errors.info("constant lmh expression can be replaced by 3 separate constant byte values", functionCallExpr.position) + val constValues = functionCallExpr.constValues(program) + if (constValues != null) { + if (constValues.size == 1) { + return listOf(AstReplaceNode(functionCallExpr, constValues[0], parent)) + } else { + // Multi-value return + val container = parent.parent as? IStatementContainer ?: return noModifications + if (parent is Assignment && parent.target.multi?.size == constValues.size) { + val mods = mutableListOf() + for (i in constValues.indices.reversed()) { + val assign = Assignment(parent.target.multi!![i], constValues[i], AssignmentOrigin.USERCODE, parent.position) + mods.add(AstInsert.after(parent, assign, container)) } - return noModifications + mods.add(AstRemove(parent, container)) + return mods + } else if (parent is VarDecl && parent.names.size == constValues.size) { + val mods = mutableListOf() + for (i in constValues.indices.reversed()) { + val newDecl = VarDecl.builder(DataType.forDt(constValues[i].type), parent.position) + .copyFrom(parent) + .names(parent.names[i]) + .value(constValues[i]) + .build() + mods.add(AstInsert.after(parent, newDecl, container)) + } + mods.add(AstRemove(parent, container)) + return mods } } } diff --git a/compiler/src/prog8/compiler/BuiltinFunctions.kt b/compiler/src/prog8/compiler/BuiltinFunctions.kt index 26a967fa7..6eb3a4d0b 100644 --- a/compiler/src/prog8/compiler/BuiltinFunctions.kt +++ b/compiler/src/prog8/compiler/BuiltinFunctions.kt @@ -13,59 +13,60 @@ import prog8.code.core.Position import prog8.code.core.isInteger import kotlin.math.* -// NOTE: at this time this machinery only supports 1 return value -private typealias ConstExpressionCaller = (args: List, position: Position, program: Program) -> NumericLiteral - // Maps builtin functions to constant evaluators for compile-time execution -// TODO: add support for builtin functions that return multiple return values (such as divmod and lmh) -internal val constEvaluatorsForBuiltinFuncs: Map = mapOf( - "abs__byte" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() } }, - "abs__word" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() } }, - "abs__long" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() } }, - "abs__float" to { a, p, prg -> oneFloatArgOutputFloat(a, p, prg) { abs(it) } }, - "len" to ::builtinLen, - "sizeof" to ::builtinSizeof, - "offsetof" to ::builtinOffsetof, - "sgn" to ::builtinSgn, - "sqrt__ubyte" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } }, - "sqrt__uword" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } }, - "sqrt__long" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { +internal val constEvaluatorsForBuiltinFuncs: Map, position: Position, program: Program) -> List> = mapOf( + "abs__byte" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() }) }, + "abs__word" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() }) }, + "abs__long" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() }) }, + "abs__float" to { a, p, prg -> listOf(oneFloatArgOutputFloat(a, p, prg) { abs(it) }) }, + "len" to { a, p, prg -> listOf(builtinLen(a, p, prg)) }, + "sizeof" to { a, p, prg -> listOf(builtinSizeof(a, p, prg)) }, + "offsetof" to { a, p, prg -> listOf(builtinOffsetof(a, p, prg)) }, + "sgn" to { a, p, prg -> listOf(builtinSgn(a, p, prg)) }, + "sqrt__ubyte" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) }) }, + "sqrt__uword" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) }) }, + "sqrt__long" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, false) { val value=it.toDouble() if(value<0) throw CannotEvaluateException("sqrt", "argument cannot be negative") else sqrt(value) - } }, - "sqrt__float" to { a, p, prg -> oneFloatArgOutputFloat(a, p, prg) { + }) }, + "sqrt__float" to { a, p, prg -> listOf(oneFloatArgOutputFloat(a, p, prg) { if(it<0) throw CannotEvaluateException("sqrt", "argument cannot be negative") else sqrt(it) - } }, - "lsb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 255).toDouble() } }, - "lsb__long" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 255).toDouble() } }, - "lsw" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 65535).toDouble() } }, - "msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 8 and 255).toDouble()} }, - "msb__long" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 24 and 255).toDouble()} }, - "msw" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 16 and 65535).toDouble()} }, - "mkword" to ::builtinMkword, - "mklong" to ::builtinMklong, - "mklong2" to ::builtinMklong, - "clamp__ubyte" to ::builtinClampUByte, - "clamp__byte" to ::builtinClampByte, - "clamp__uword" to ::builtinClampUWord, - "clamp__word" to ::builtinClampWord, - "clamp__long" to ::builtinClampLong, - "min__ubyte" to ::builtinMinUByte, - "min__byte" to ::builtinMinByte, - "min__uword" to ::builtinMinUWord, - "min__word" to ::builtinMinWord, - "min__long" to ::builtinMinLong, - "max__ubyte" to ::builtinMaxUByte, - "max__byte" to ::builtinMaxByte, - "max__uword" to ::builtinMaxUWord, - "max__word" to ::builtinMaxWord, - "max__long" to ::builtinMaxLong + }) }, + "lsb" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 255).toDouble() }) }, + "lsb__long" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 255).toDouble() }) }, + "lsw" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 65535).toDouble() }) }, + "msb" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 8 and 255).toDouble()}) }, + "msb__long" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 24 and 255).toDouble()}) }, + "msw" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 16 and 65535).toDouble()}) }, + "mkword" to { a, p, prg -> listOf(builtinMkword(a, p, prg)) }, + "mklong" to { a, p, prg -> listOf(builtinMklong(a, p, prg)) }, + "mklong2" to { a, p, prg -> listOf(builtinMklong(a, p, prg)) }, + "clamp__ubyte" to { a, p, prg -> listOf(builtinClampUByte(a, p, prg)) }, + "clamp__byte" to { a, p, prg -> listOf(builtinClampByte(a, p, prg)) }, + "clamp__uword" to { a, p, prg -> listOf(builtinClampUWord(a, p, prg)) }, + "clamp__word" to { a, p, prg -> listOf(builtinClampWord(a, p, prg)) }, + "clamp__long" to { a, p, prg -> listOf(builtinClampLong(a, p, prg)) }, + "min__ubyte" to { a, p, prg -> listOf(builtinMinUByte(a, p, prg)) }, + "min__byte" to { a, p, prg -> listOf(builtinMinByte(a, p, prg)) }, + "min__uword" to { a, p, prg -> listOf(builtinMinUWord(a, p, prg)) }, + "min__word" to { a, p, prg -> listOf(builtinMinWord(a, p, prg)) }, + "min__long" to { a, p, prg -> listOf(builtinMinLong(a, p, prg)) }, + "max__ubyte" to { a, p, prg -> listOf(builtinMaxUByte(a, p, prg)) }, + "max__byte" to { a, p, prg -> listOf(builtinMaxByte(a, p, prg)) }, + "max__uword" to { a, p, prg -> listOf(builtinMaxUWord(a, p, prg)) }, + "max__word" to { a, p, prg -> listOf(builtinMaxWord(a, p, prg)) }, + "max__long" to { a, p, prg -> listOf(builtinMaxLong(a, p, prg)) }, + "lmh" to ::builtinLmh, + "divmod__ubyte" to ::builtinDivmodUByte, + "divmod__uword" to ::builtinDivmodUWord, + "divmod__byte" to ::builtinDivmodByte, + "divmod__word" to ::builtinDivmodWord ) internal fun builtinFunctionReturnTypes(function: String): Array { @@ -397,3 +398,63 @@ private fun builtinClampLong(args: List, position: Position, program return NumericLiteral(BaseDataType.LONG, result, position) } + +private fun builtinLmh(args: List, position: Position, program: Program): List { + if (args.size != 1) + throw SyntaxError("lmh requires 1 argument", position) + val constval = args[0].constValue(program) ?: throw NotConstArgumentException() + val value = constval.number.toLong() + return listOf( + NumericLiteral(BaseDataType.UBYTE, (value and 0xff).toDouble(), position), + NumericLiteral(BaseDataType.UBYTE, ((value shr 8) and 0xff).toDouble(), position), + NumericLiteral(BaseDataType.UBYTE, ((value shr 16) and 0xff).toDouble(), position) + ) +} + +private fun builtinDivmodUByte(args: List, position: Position, program: Program): List { + if (args.size != 2) + throw SyntaxError("divmod requires 2 arguments", position) + val v1 = args[0].constValue(program) ?: throw NotConstArgumentException() + val v2 = args[1].constValue(program) ?: throw NotConstArgumentException() + if (v2.number.toInt() == 0) throw CannotEvaluateException("divmod", "division by zero") + return listOf( + NumericLiteral(BaseDataType.UBYTE, (v1.number.toInt() / v2.number.toInt()).toDouble(), position), + NumericLiteral(BaseDataType.UBYTE, (v1.number.toInt() % v2.number.toInt()).toDouble(), position) + ) +} + +private fun builtinDivmodUWord(args: List, position: Position, program: Program): List { + if (args.size != 2) + throw SyntaxError("divmod requires 2 arguments", position) + val v1 = args[0].constValue(program) ?: throw NotConstArgumentException() + val v2 = args[1].constValue(program) ?: throw NotConstArgumentException() + if (v2.number.toInt() == 0) throw CannotEvaluateException("divmod", "division by zero") + return listOf( + NumericLiteral(BaseDataType.UWORD, (v1.number.toInt() / v2.number.toInt()).toDouble(), position), + NumericLiteral(BaseDataType.UWORD, (v1.number.toInt() % v2.number.toInt()).toDouble(), position) + ) +} + +private fun builtinDivmodByte(args: List, position: Position, program: Program): List { + if (args.size != 2) + throw SyntaxError("divmod requires 2 arguments", position) + val v1 = args[0].constValue(program) ?: throw NotConstArgumentException() + val v2 = args[1].constValue(program) ?: throw NotConstArgumentException() + if (v2.number.toInt() == 0) throw CannotEvaluateException("divmod", "division by zero") + return listOf( + NumericLiteral(BaseDataType.BYTE, (v1.number.toInt() / v2.number.toInt()).toDouble(), position), + NumericLiteral(BaseDataType.BYTE, (v1.number.toInt() % v2.number.toInt()).toDouble(), position) + ) +} + +private fun builtinDivmodWord(args: List, position: Position, program: Program): List { + if (args.size != 2) + throw SyntaxError("divmod requires 2 arguments", position) + val v1 = args[0].constValue(program) ?: throw NotConstArgumentException() + val v2 = args[1].constValue(program) ?: throw NotConstArgumentException() + if (v2.number.toInt() == 0) throw CannotEvaluateException("divmod", "division by zero") + return listOf( + NumericLiteral(BaseDataType.WORD, (v1.number.toInt() / v2.number.toInt()).toDouble(), position), + NumericLiteral(BaseDataType.WORD, (v1.number.toInt() % v2.number.toInt()).toDouble(), position) + ) +} diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 7d8935101..4ad6f1bb4 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -376,9 +376,9 @@ private class BuiltinFunctionsFacade(functions: Map): IBuilt override val names = functions.keys override val purefunctionNames = functions.filter { it.value.pure }.mapTo(mutableSetOf()) { it.key } - override fun constValue(funcName: String, args: List, position: Position): NumericLiteral? { + override fun constValues(funcName: String, args: List, position: Position): List? { if(funcName=="msw" && !args[0].inferType(program).isLong) - return NumericLiteral.optimalInteger(0, position) + return listOf(NumericLiteral.optimalInteger(0, position)) val func = BuiltinFunctions[funcName] if(func!=null) { diff --git a/compiler/test/TestBuiltinFunctions.kt b/compiler/test/TestBuiltinFunctions.kt index c2ba1e934..670c59c95 100644 --- a/compiler/test/TestBuiltinFunctions.kt +++ b/compiler/test/TestBuiltinFunctions.kt @@ -6,6 +6,7 @@ import io.kotest.inspectors.shouldForAll import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldNotContain import prog8.ast.expressions.NumericLiteral import prog8.ast.statements.Assignment import prog8.ast.statements.FunctionCallStatement @@ -171,6 +172,37 @@ main { compileText(C64Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null } + test("divmod and lmh should be compile time folded") { + val src=""" +main { + ubyte @shared d, r, l, m, h + sub start() { + d, r = divmod(100, 13) + l, m, h = lmh(123456) + } +}""" + val result = compileText(C64Target(), true, src, outputDir, writeAssembly = false)!! + val statements = result.compilerAst.entrypoint.statements + + val assignments = statements.filterIsInstance() + val d = assignments.find { it.target.identifier?.nameInSource?.last() == "d" }!! + val r = assignments.find { it.target.identifier?.nameInSource?.last() == "r" }!! + val l = assignments.find { it.target.identifier?.nameInSource?.last() == "l" }!! + val m = assignments.find { it.target.identifier?.nameInSource?.last() == "m" }!! + val h = assignments.find { it.target.identifier?.nameInSource?.last() == "h" }!! + + (d.value as NumericLiteral).number shouldBe 7.0 + (r.value as NumericLiteral).number shouldBe 9.0 + (l.value as NumericLiteral).number shouldBe 64.0 + (m.value as NumericLiteral).number shouldBe 226.0 + (h.value as NumericLiteral).number shouldBe 1.0 + + statements.forEach { + it.toString() shouldNotContain "divmod(" + it.toString() shouldNotContain "lmh(" + } + } + test("sgn") { val src=""" main { diff --git a/compiler/test/helpers/Dummies.kt b/compiler/test/helpers/Dummies.kt index 96b3af4dd..dae835405 100644 --- a/compiler/test/helpers/Dummies.kt +++ b/compiler/test/helpers/Dummies.kt @@ -16,11 +16,11 @@ internal object DummyFunctions : IBuiltinFunctions { override val names: Set = emptySet() override val purefunctionNames: Set = emptySet() - override fun constValue( + override fun constValues( funcName: String, args: List, position: Position, - ): NumericLiteral? = null + ): List? = null override fun returnTypes(funcName: String) = emptyArray() } diff --git a/compilerAst/src/prog8/ast/IBuiltinFunctions.kt b/compilerAst/src/prog8/ast/IBuiltinFunctions.kt index eef56c8eb..f232b4829 100644 --- a/compilerAst/src/prog8/ast/IBuiltinFunctions.kt +++ b/compilerAst/src/prog8/ast/IBuiltinFunctions.kt @@ -8,6 +8,7 @@ import prog8.code.core.Position interface IBuiltinFunctions { val names: Set val purefunctionNames: Set - fun constValue(funcName: String, args: List, position: Position): NumericLiteral? + fun constValue(funcName: String, args: List, position: Position): NumericLiteral? = constValues(funcName, args, position)?.singleOrNull() + fun constValues(funcName: String, args: List, position: Position): List? fun returnTypes(funcName: String): Array } diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 60307421b..81fb8bdfd 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -17,6 +17,10 @@ import kotlin.math.truncate sealed class Expression: Node { abstract override fun copy(): Expression abstract fun constValue(program: Program): NumericLiteral? + open fun constValues(program: Program): List? { + val cv = constValue(program) + return if(cv!=null) listOf(cv) else null + } abstract fun accept(visitor: IAstVisitor) abstract fun accept(visitor: AstWalker, parent: Node) abstract fun inferType(program: Program): InferredTypes.InferredType @@ -1634,6 +1638,14 @@ class FunctionCallExpression(override var target: IdentifierReference, override fun constValue(program: Program) = constValue(program, true) + override fun constValues(program: Program): List? { + if (target.nameInSource.size > 1) + return null + val values = program.builtinFunctions.constValues(target.nameInSource[0], args, position) + values?.forEach { it.parent = this.parent } + return values + } + private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteral? { // if the function is a built-in function and the args are consts, should try to const-evaluate! // lenghts of arrays and strings are constants that are determined at compile time! diff --git a/examples/test.p8 b/examples/test.p8 index a5ff67868..3aff021e3 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -4,9 +4,9 @@ main { sub start() { - long @shared lv = $12345678 + const long lv = $12345678 - ubyte l,m,h = lmh(lv+99999) + ubyte l,m,h = lmh(lv+99999) ; TODO should be const-folded and ALL THREE target vars should become consts too txt.print_ubhex(h, true) txt.spc() @@ -16,10 +16,10 @@ main { txt.nl() - ubyte @shared num = 230 - ubyte @shared div = 13 + const ubyte num = 230 + const ubyte div = 13 - ubyte d,r = divmod(num, div) + ubyte d,r = divmod(num, div) ; TODO should be const-folded and BOTH target vars should become consts too txt.print_ub(d) txt.spc() txt.print_ub(r)