From d5707b7bf36d0607fd6adeeabaa6cc5b39a94f7a Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 18 Jul 2023 01:24:27 +0200 Subject: [PATCH] rebuilding floating point stack evaluation (using cpu stack) --- .../src/prog8/codegen/cpu6502/AsmGen.kt | 11 +- .../cpu6502/assignment/AnyExprAsmGen.kt | 266 ++++++++++++++++++ .../cpu6502/assignment/AssignmentAsmGen.kt | 19 +- .../src/prog8/optimizer/BinExprSplitter.kt | 4 + compiler/res/prog8lib/c64/floats.asm | 67 ++++- .../src/prog8/buildversion/BuildVersion.kt | 10 +- docs/source/todo.rst | 3 +- examples/cx16/mandelbrot.p8 | 6 + examples/test.p8 | 61 ++-- 9 files changed, 413 insertions(+), 34 deletions(-) create mode 100644 codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AnyExprAsmGen.kt diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index b5dc42ac9..dd1188d33 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -207,7 +207,8 @@ class AsmGen6502Internal ( private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this) private val functioncallAsmGen = FunctionCallAsmGen(program, this) private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage) - private val assignmentAsmGen = AssignmentAsmGen(program, this, allocator) + private val anyExprGen = AnyExprAsmGen(this) + private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator) private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen) fun compileToAssembly(): IAssemblyProgram? { @@ -3013,6 +3014,14 @@ $repeatLabel""") } } + internal fun pushFAC1() { + out(" jsr floats.pushFAC1") + } + + internal fun popFAC1() { + out(" jsr floats.popFAC1") + } + internal fun needAsaveForExpr(arg: PtExpression): Boolean = arg !is PtNumber && arg !is PtIdentifier && (arg !is PtMemoryByte || !arg.isSimple()) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AnyExprAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AnyExprAsmGen.kt new file mode 100644 index 000000000..f5306f7f6 --- /dev/null +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AnyExprAsmGen.kt @@ -0,0 +1,266 @@ +package prog8.codegen.cpu6502.assignment + +import prog8.code.ast.PtBinaryExpression +import prog8.code.core.* +import prog8.codegen.cpu6502.AsmGen6502Internal + +// +// This contains codegen for stack-based evaluation of binary expressions. +// It uses the CPU stack so depth is limited. +// It is called "as a last resort" if the optimized codegen path is unable +// to come up with a special case of the expression. +// +internal class AnyExprAsmGen( + private val asmgen: AsmGen6502Internal +) { + fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { + when(expr.type) { + in ByteDatatypes -> { + if(expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) + return assignByteBinExpr(expr, assign) + if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) { + require(expr.operator in ComparisonOperators) + TODO("words operands comparison -> byte") + } + if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) { + require(expr.operator in ComparisonOperators) + return assignFloatBinExpr(expr, assign) + } + TODO("weird expr operand types") + } + in WordDatatypes -> { + require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) { + "both operands must be words" + } + return assignWordBinExpr(expr, assign) + } + DataType.FLOAT -> { + require(expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) { + "both operands must be floats" + } + return assignFloatBinExpr(expr, assign) + } + else -> throw AssemblyError("weird expression type in assignment") + } + } + + private fun assignWordBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { + when(expr.operator) { + "+" -> { + TODO("word + at ${expr.position}") + } + "-" -> { + TODO("word - at ${expr.position}") + } + "*" -> { + TODO("word * at ${expr.position}") + } + "/" -> { + TODO("word / at ${expr.position}") + } + "<<" -> { + TODO("word << at ${expr.position}") + } + ">>" -> { + TODO("word >> at ${expr.position}") + } + "%" -> { + TODO("word % at ${expr.position}") + } + "&", "and" -> { + TODO("word and at ${expr.position}") + } + "|", "or" -> { + TODO("word or at ${expr.position}") + } + "^", "xor" -> { + TODO("word xor at ${expr.position}") + } + "==" -> { + TODO("word == at ${expr.position}") + } + "!=" -> { + TODO("word != at ${expr.position}") + } + "<" -> { + TODO("word < at ${expr.position}") + } + "<=" -> { + TODO("word <= at ${expr.position}") + } + ">" -> { + TODO("word > at ${expr.position}") + } + ">=" -> { + TODO("word >= at ${expr.position}") + } + else -> return false + } + } + + private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { + when(expr.operator) { + "+" -> { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) + asmgen.out(" pha") + asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) + asmgen.out(" pla | clc | adc P8ZP_SCRATCH_B1") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "-" -> { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) + asmgen.out(" pha") + asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) + asmgen.out(" pla | sec | sbc P8ZP_SCRATCH_B1") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "*" -> { + TODO("byte * at ${expr.position}") + } + "/" -> { + TODO("byte / at ${expr.position}") + } + "<<" -> { + TODO("byte << at ${expr.position}") + } + ">>" -> { + TODO("byte >> at ${expr.position}") + } + "%" -> { + TODO("byte % at ${expr.position}") + } + "&", "and" -> { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) + asmgen.out(" pha") + asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) + asmgen.out(" pla | and P8ZP_SCRATCH_B1") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "|", "or" -> { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) + asmgen.out(" pha") + asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) + asmgen.out(" pla | ora P8ZP_SCRATCH_B1") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "^", "xor" -> { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) + asmgen.out(" pha") + asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) + asmgen.out(" pla | eor P8ZP_SCRATCH_B1") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "==" -> { + TODO("byte == at ${expr.position}") + } + "!=" -> { + TODO("byte != at ${expr.position}") + } + "<" -> { + TODO("byte < at ${expr.position}") + } + "<=" -> { + TODO("byte <= at ${expr.position}") + } + ">" -> { + TODO("byte > at ${expr.position}") + } + ">=" -> { + TODO("byte >= at ${expr.position}") + } + else -> return false + } + } + + private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { + when(expr.operator) { + "+" -> { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true) + if(!expr.right.isSimple()) asmgen.pushFAC1() + asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true) + if(!expr.right.isSimple()) asmgen.popFAC1() + asmgen.out(" jsr floats.FADDT") + asmgen.assignRegister(RegisterOrPair.FAC1, assign.target) + return true + } + "-" -> { + asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true) + if(!expr.left.isSimple()) asmgen.pushFAC1() + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true) + if(!expr.left.isSimple()) asmgen.popFAC1() + asmgen.out(" jsr floats.FSUBT") + asmgen.assignRegister(RegisterOrPair.FAC1, assign.target) + return true + } + "*" -> { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true) + if(!expr.right.isSimple()) asmgen.pushFAC1() + asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true) + if(!expr.right.isSimple()) asmgen.popFAC1() + asmgen.out(" jsr floats.FMULTT") + asmgen.assignRegister(RegisterOrPair.FAC1, assign.target) + return true + } + "/" -> { + asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true) + if(!expr.left.isSimple()) asmgen.pushFAC1() + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true) + if(!expr.left.isSimple()) asmgen.popFAC1() + asmgen.out(" jsr floats.FDIVT") + asmgen.assignRegister(RegisterOrPair.FAC1, assign.target) + return true + } + "==" -> { + setupFloatComparisonFAC1vsVarAY(expr) + asmgen.out(" jsr floats.var_fac1_equal_f") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "!=" -> { + setupFloatComparisonFAC1vsVarAY(expr) + asmgen.out(" jsr floats.var_fac1_notequal_f") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "<" -> { + setupFloatComparisonFAC1vsVarAY(expr) + asmgen.out(" jsr floats.var_fac1_less_f") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + ">" -> { + setupFloatComparisonFAC1vsVarAY(expr) + asmgen.out(" jsr floats.var_fac1_greater_f") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + "<=" -> { + setupFloatComparisonFAC1vsVarAY(expr) + asmgen.out(" jsr floats.var_fac1_lesseq_f") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + ">=" -> { + setupFloatComparisonFAC1vsVarAY(expr) + asmgen.out(" jsr floats.var_fac1_greatereq_f") + asmgen.assignRegister(RegisterOrPair.A, assign.target) + return true + } + else -> TODO("float expression operator ${expr.operator}") + } + return false + } + + private fun setupFloatComparisonFAC1vsVarAY(expr: PtBinaryExpression) { + asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true) + if(!expr.right.isSimple()) asmgen.pushFAC1() + asmgen.assignExpressionToVariable(expr.right, "floats.floats_temp_var", DataType.FLOAT) + if(!expr.right.isSimple()) asmgen.popFAC1() + asmgen.out(" lda #floats.floats_temp_var") + } +} \ No newline at end of file diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index 436b6c602..9e21cbcb2 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -9,6 +9,7 @@ import prog8.codegen.cpu6502.returnsWhatWhere internal class AssignmentAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal, + private val anyExprGen: AnyExprAsmGen, private val allocator: VariableAllocator) { private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator) @@ -411,7 +412,7 @@ internal class AssignmentAsmGen(private val program: PtProgram, } if(expr.type !in IntegerDatatypes) - return false + return anyExprGen.assignAnyExpressionUsingStack(expr, assign) if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) return optimizedLogicalOrBitwiseExpr(expr, assign.target) @@ -428,7 +429,7 @@ internal class AssignmentAsmGen(private val program: PtProgram, if(expr.operator=="%") return optimizedRemainderExpr(expr, assign.target) - return false + return anyExprGen.assignAnyExpressionUsingStack(expr, assign) } private fun optimizedRemainderExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean { @@ -1579,6 +1580,8 @@ internal class AssignmentAsmGen(private val program: PtProgram, } if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) { + if(target.register==RegisterOrPair.FAC2) + asmgen.pushFAC1() when(valueDt) { DataType.UBYTE -> { assignExpressionToRegister(value, RegisterOrPair.Y, false) @@ -1599,7 +1602,8 @@ internal class AssignmentAsmGen(private val program: PtProgram, else -> throw AssemblyError("invalid dt") } if(target.register==RegisterOrPair.FAC2) { - asmgen.out(" jsr floats.MOVEF") + asmgen.out(" jsr floats.MOVEF") + asmgen.popFAC1() } return } @@ -2091,8 +2095,9 @@ internal class AssignmentAsmGen(private val program: PtProgram, } internal fun assignFAC2float(target: AsmAssignTarget) { - asmgen.out(" jsr floats.MOVFA") // fac2 -> fac1 - assignFAC1float(target) + asmgen.out(" jsr floats.MOVFA") + if(target.register != RegisterOrPair.FAC1) + assignFAC1float(target) } internal fun assignFAC1float(target: AsmAssignTarget) { @@ -2121,7 +2126,9 @@ internal class AssignmentAsmGen(private val program: PtProgram, } TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte") TargetStorageKind.REGISTER -> { - if (target.register!! != RegisterOrPair.FAC1) + if(target.register==RegisterOrPair.FAC2) + asmgen.out(" jsr floats.MOVAF") + else if (target.register!! != RegisterOrPair.FAC1) throw AssemblyError("can't assign Fac1 float to another register") } } diff --git a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt index 9b9618b10..3b57e03ba 100644 --- a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt +++ b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt @@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.code.core.AugmentAssignmentOperators import prog8.code.core.CompilationOptions +import prog8.code.core.DataType import prog8.code.target.VMTarget @@ -21,6 +22,9 @@ class BinExprSplitter(private val program: Program, private val options: Compila if(options.compTarget.name == VMTarget.NAME) return noModifications // don't split expressions when targeting the vm codegen, it handles nested expressions well + if(assignment.value.inferType(program) istype DataType.FLOAT) + return noModifications + val binExpr = assignment.value as? BinaryExpression if (binExpr != null) { diff --git a/compiler/res/prog8lib/c64/floats.asm b/compiler/res/prog8lib/c64/floats.asm index 8ee4c97d3..e118a0824 100644 --- a/compiler/res/prog8lib/c64/floats.asm +++ b/compiler/res/prog8lib/c64/floats.asm @@ -208,7 +208,6 @@ var_fac1_greater_f .proc .pend var_fac1_greatereq_f .proc - ldx P8ZP_SCRATCH_REG ; -- is the float in FAC1 >= the variable AY? Result in A. Clobbers X. jsr FCOMP cmp #0 @@ -221,6 +220,14 @@ var_fac1_greatereq_f .proc rts .pend +var_fac1_equal_f .proc + ; -- are the floats numbers in FAC1 and the variable AY *not* identical? Result in A. Clobbers X. + jsr FCOMP + and #1 + eor #1 + rts + .pend + var_fac1_notequal_f .proc ; -- are the floats numbers in FAC1 and the variable AY *not* identical? Result in A. Clobbers X. jsr FCOMP @@ -381,3 +388,61 @@ set_array_float .proc ; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1, ; into the 5 bytes pointed to by A/Y. Clobbers A,Y. .pend + + +pushFAC1 .proc + ;-- push floating point in FAC onto the cpu stack + ; save return address + pla + sta P8ZP_SCRATCH_W2 + pla + sta P8ZP_SCRATCH_W2+1 + ldx #floats.floats_temp_var + jsr floats.MOVMF + lda floats.floats_temp_var + pha + lda floats.floats_temp_var+1 + pha + lda floats.floats_temp_var+2 + pha + lda floats.floats_temp_var+3 + pha + lda floats.floats_temp_var+4 + pha + ; re-push return address + lda P8ZP_SCRATCH_W2+1 + pha + lda P8ZP_SCRATCH_W2 + pha + rts + .pend + +popFAC1 .proc + ; -- pop floating point value from cpu stack into FAC1 + ; save return address + pla + sta P8ZP_SCRATCH_W2 + pla + sta P8ZP_SCRATCH_W2+1 + pla + sta floats.floats_temp_var+4 + pla + sta floats.floats_temp_var+3 + pla + sta floats.floats_temp_var+2 + pla + sta floats.floats_temp_var+1 + pla + sta floats.floats_temp_var + lda #floats.floats_temp_var + jsr floats.MOVFM + ; re-push return address + lda P8ZP_SCRATCH_W2+1 + pha + lda P8ZP_SCRATCH_W2 + pha + rts + .pend + diff --git a/compiler/src/prog8/buildversion/BuildVersion.kt b/compiler/src/prog8/buildversion/BuildVersion.kt index cea017ca5..e5e79b7d7 100644 --- a/compiler/src/prog8/buildversion/BuildVersion.kt +++ b/compiler/src/prog8/buildversion/BuildVersion.kt @@ -6,10 +6,10 @@ package prog8.buildversion const val MAVEN_GROUP = "prog8" const val MAVEN_NAME = "compiler" const val VERSION = "9.2-SNAPSHOT" -const val GIT_REVISION = 3974 -const val GIT_SHA = "ab8173637a9939352ddb027ecd67f1c42b63b1fc" -const val GIT_DATE = "2023-07-16T09:15:28Z" +const val GIT_REVISION = 3980 +const val GIT_SHA = "9f247901d484ca36d047cdcf77f5b905ba772f82" +const val GIT_DATE = "2023-07-16T21:45:04Z" const val GIT_BRANCH = "remove_evalstack" -const val BUILD_DATE = "2023-07-16T11:04:18Z" -const val BUILD_UNIX_TIME = 1689505458888L +const val BUILD_DATE = "2023-07-17T23:11:43Z" +const val BUILD_UNIX_TIME = 1689635503506L const val DIRTY = 1 diff --git a/docs/source/todo.rst b/docs/source/todo.rst index f6c13877e..241c17245 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,8 +1,9 @@ TODO ==== -- (branch): fix float expressions codegen, it relied heavily on the evalstack +- (branch): fix float expressions codegen, it relied heavily on the evalstack (mandelbrot example zero division error) - (branch): improve integer expression codegen even more to support even more cases? +- (branch): fully remove BinExprSplitter??? - IR: instructions that do type conversion (SZ etc, CONCAT, SGN) should put the result in a DIFFERENT register. - IR: reduce the number of branch instructions (gradually), replace with CMP(I) + status branch instruction diff --git a/examples/cx16/mandelbrot.p8 b/examples/cx16/mandelbrot.p8 index 58a1d5dba..78f02a87f 100644 --- a/examples/cx16/mandelbrot.p8 +++ b/examples/cx16/mandelbrot.p8 @@ -9,6 +9,7 @@ main { sub start() { txt.print("calculating mandelbrot fractal...\n\n") + cbm.SETTIM(0,0,0) ubyte pixelx ubyte pixely @@ -37,5 +38,10 @@ main { } txt.nl() } + + float duration = (cbm.RDTIM16() as float) / 60 + txt.print("\nfinished in ") + floats.print_f(duration) + txt.print(" seconds!\n") } } diff --git a/examples/test.p8 b/examples/test.p8 index 89dba048d..78f02a87f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,26 +1,47 @@ %import textio +%import floats %zeropage basicsafe -main -{ - sub start() - { - uword uw = 54321 - ubyte ub = 123 - word sw = -12345 - byte sb = -123 +main { + const uword width = 60 + const uword height = 50 + const ubyte max_iter = 16 - txt.print_uw(~ub as uword) ;132 - txt.nl() - txt.print_ub(~uw as ubyte) ;206 - txt.nl() - txt.print_uw(~sb as uword) ;122 - txt.nl() - txt.print_ub(~sw as ubyte) ;56 - txt.nl() - txt.print_w(-sb as word) ;123 - txt.nl() - txt.print_b(-sw as byte) ;57 - txt.nl() + sub start() { + txt.print("calculating mandelbrot fractal...\n\n") + cbm.SETTIM(0,0,0) + + ubyte pixelx + ubyte pixely + + for pixely in 0 to height-1 { + float yy = (pixely as float)/0.40/height - 1.3 + + for pixelx in 0 to width-1 { + float xx = (pixelx as float)/0.32/width - 2.2 + + float xsquared = 0.0 + float ysquared = 0.0 + float x = 0.0 + float y = 0.0 + ubyte iter = 0 + + while iter