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