diff --git a/codeGeneration/codeGeneration.iml b/codeGeneration/codeGeneration.iml index af4fb2621..173053009 100644 --- a/codeGeneration/codeGeneration.iml +++ b/codeGeneration/codeGeneration.iml @@ -15,6 +15,5 @@ - \ No newline at end of file diff --git a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AsmAssignment.kt b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AsmAssignment.kt index 6bccbbadc..7a372a3f8 100644 --- a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AsmAssignment.kt +++ b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AsmAssignment.kt @@ -209,7 +209,7 @@ internal class AsmAssignment(val source: AsmAssignSource, if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" } require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) { - "source storage size must be less or equal to target datatype storage size at $position" + "source dt size must be less or equal to target dt size at $position" } } } diff --git a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AugmentableAssignmentAsmGen.kt b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AugmentableAssignmentAsmGen.kt index 14eb722d6..913764843 100644 --- a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AugmentableAssignmentAsmGen.kt +++ b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/assignment/AugmentableAssignmentAsmGen.kt @@ -103,7 +103,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program, throw FatalAstException("assignment should be augmentable $binExpr") } - private fun inplaceModification(target: AsmAssignTarget, operator: String, value: Expression) { + private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) { + + + // the asm-gen code can deal with situations where you want to assign a byte into a word. + // it will create the most optimized code to do this (so it type-extends for us). + // But we can't deal with writing a word into a byte - explicit typeconversion is required + val value = if(program.memsizer.memorySize(origValue.inferType(program).getOr(DataType.UNDEFINED)) > program.memsizer.memorySize(target.datatype)) { + val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position) + typecast.linkParents(origValue.parent) + typecast + } + else { + origValue + } + val valueLv = (value as? NumericLiteralValue)?.number val ident = value as? IdentifierReference val memread = value as? DirectMemoryRead diff --git a/compiler/compiler.iml b/compiler/compiler.iml index 217c09da6..f431d54d5 100644 --- a/compiler/compiler.iml +++ b/compiler/compiler.iml @@ -18,7 +18,6 @@ - \ No newline at end of file diff --git a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt index 288225432..7f7e95c1e 100644 --- a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt +++ b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt @@ -46,14 +46,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o if(binExpr.operator in associativeOperators) { // A = // use the other part of the expression to split. - val assignRight = Assignment(assignment.target, binExpr.right, assignment.position) + val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), program, implicit=true) + val assignRight = Assignment(assignment.target, right, assignment.position) return listOf( IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer), IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr), IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) } } else { - val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position) + val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), program, implicit=true) + val assignLeft = Assignment(assignment.target, left, assignment.position) return listOf( IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer), IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) diff --git a/compiler/test/TestOptimization.kt b/compiler/test/TestOptimization.kt index 44fbda827..e22271f78 100644 --- a/compiler/test/TestOptimization.kt +++ b/compiler/test/TestOptimization.kt @@ -1,5 +1,6 @@ package prog8tests +import io.kotest.assertions.fail import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe @@ -10,13 +11,18 @@ import prog8.ast.Program import prog8.ast.base.DataType import prog8.ast.base.ParentSentinel import prog8.ast.base.Position -import prog8.ast.expressions.NumericLiteralValue -import prog8.ast.expressions.TypecastExpression +import prog8.ast.expressions.* import prog8.ast.statements.* +import prog8.compiler.BeforeAsmGenerationAstChanger import prog8.compiler.target.C64Target +import prog8.compiler.target.c64.C64MachineDefinition +import prog8.compiler.target.cpu6502.codegen.AsmGen +import prog8.compilerinterface.* +import prog8tests.ast.helpers.outputDir import prog8tests.helpers.DummyFunctions import prog8tests.helpers.DummyMemsizer import prog8tests.helpers.DummyStringEncoder +import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.assertSuccess import prog8tests.helpers.compileText @@ -123,4 +129,69 @@ class TestOptimization: FunSpec({ (initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE (initY2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0 } + + test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") { + val src = """ + main { + sub start() { + ubyte bb + uword ww + bb = not bb or not ww ; expression combining ubyte and uword + } + } + """ + val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess() + + // bb = (( not bb as uword) or not ww) + val bbAssign = result.program.entrypoint.statements.last() as Assignment + val expr = bbAssign.value as BinaryExpression + expr.operator shouldBe "or" + expr.left shouldBe instanceOf() // casted to word + expr.right shouldBe instanceOf() + expr.left.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD + expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD + expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE + + val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target) + val changer = BeforeAsmGenerationAstChanger(result.program, + options, + ErrorReporterForTests() + ) + + changer.visit(result.program) + while(changer.applyModifications()>0) { + changer.visit(result.program) + } + + // assignment is now split into: + // bb = not bb + // bb = (bb or not ww) + + val assigns = result.program.entrypoint.statements.filterIsInstance() + val bbAssigns = assigns.filter { it.value !is NumericLiteralValue } + bbAssigns.size shouldBe 2 + println(bbAssigns[0]) + println(bbAssigns[1]) + + bbAssigns[0].target.identifier!!.nameInSource shouldBe listOf("bb") + bbAssigns[0].value shouldBe instanceOf() + (bbAssigns[0].value as PrefixExpression).operator shouldBe "not" + (bbAssigns[0].value as PrefixExpression).expression shouldBe IdentifierReference(listOf("bb"), Position.DUMMY) + bbAssigns[0].value.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE + + bbAssigns[1].target.identifier!!.nameInSource shouldBe listOf("bb") + val bbAssigns1expr = bbAssigns[1].value as BinaryExpression + bbAssigns1expr.operator shouldBe "or" + bbAssigns1expr.left shouldBe IdentifierReference(listOf("bb"), Position.DUMMY) + bbAssigns1expr.right shouldBe instanceOf() + (bbAssigns1expr.right as PrefixExpression).operator shouldBe "not" + (bbAssigns1expr.right as PrefixExpression).expression shouldBe IdentifierReference(listOf("ww"), Position.DUMMY) + bbAssigns1expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE + + val zp = C64MachineDefinition.C64Zeropage(options) + options.compTarget.machine.zeropage=zp + val asmgen = AsmGen(result.program, ErrorReporterForTests(), zp, options, C64Target, outputDir) + val asm = asmgen.compileToAssembly() + asm.valid shouldBe true + } }) diff --git a/compilerAst/compilerAst.iml b/compilerAst/compilerAst.iml index 3d3c487dc..c3fcfe1df 100644 --- a/compilerAst/compilerAst.iml +++ b/compilerAst/compilerAst.iml @@ -15,7 +15,6 @@ - \ No newline at end of file diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 010c81517..034774543 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -62,6 +62,21 @@ sealed class Expression: Node { else -> other==this } } + + fun typecastTo(targetDt: DataType, program: Program, sourceDt: DataType?=null, implicit: Boolean=false): Pair { + require(sourceDt==null || sourceDt!=DataType.UNDEFINED) + require(targetDt!=DataType.UNDEFINED) + if(this is TypecastExpression) { + this.type = targetDt + return Pair(false, this) + } + val exprDt = sourceDt ?: this.inferType(program).getOr(DataType.UNDEFINED) + if(exprDt==targetDt) + return Pair(false, this) + + val typecast = TypecastExpression(this, targetDt, implicit, this.position) + return Pair(true, typecast) + } } diff --git a/compilerAst/test/TestProg8Parser.kt b/compilerAst/test/TestProg8Parser.kt index 2403587fb..f39cf0a5d 100644 --- a/compilerAst/test/TestProg8Parser.kt +++ b/compilerAst/test/TestProg8Parser.kt @@ -1,5 +1,6 @@ package prog8tests.ast +import io.kotest.assertions.fail import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec @@ -654,4 +655,24 @@ class TestProg8Parser: FunSpec( { correctPrios(orAssignmentExpr, "or") correctPrios(xorAssignmentExpr, "xor") } + + test("inferred type correct for binaryexpression") { + val src = SourceCode.Text(""" + main { + ubyte bb + uword ww + ubyte bb2 = not bb or not ww ; expression combining ubyte and uword + } + """) + val module = parseModule(src) + val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder) + module.linkIntoProgram(program) + val bb2 = (module.statements.single() as Block).statements[2] as VarDecl + val expr = bb2.value as BinaryExpression + println(expr) + expr.operator shouldBe "or" + expr.left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE + expr.right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD + expr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE + } }) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 005a98ead..94f947e35 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -5,11 +5,7 @@ For next compiler release (7.3) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - if-statement expression simplification sometimes increases code size (Petaxian) FIX THIS! - add expression simplification to while and until loops as well. -- fix crash about storage size mismatch - ubyte bb - uword ww - bb = not bb or not ww ; CRASHES - +- let typecasting code use expression.typecastTo() Blocked by Commander-x16 v39 release diff --git a/examples/test.p8 b/examples/test.p8 index 5a3d35113..a27510e6b 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,11 +6,12 @@ main { sub start() { - ubyte unused ; TODO FIX : why is this not removed as an unused variable? + ubyte unused ; TODO FIX : why is this not removed as an unused variable? + ubyte @shared unused2 ubyte bb uword ww - bb = not bb or not ww ; TODO FIX COMPILER CRASH (STORAGE SIZE) + ww = not bb or not ww ; TODO WHY DOES THIS USE STACK EVAL ; if not iteration_in_progress or not num_bytes ; return