fix compiler crash where it used wrong datatype in split assignment

fixes crash for "ubyte bb ;; uword ww ;; bb = not bb or not ww"
This commit is contained in:
Irmen de Jong 2021-11-09 01:13:23 +01:00
parent 4cb383dccb
commit 4937e004b5
11 changed files with 133 additions and 16 deletions

View File

@ -15,6 +15,5 @@
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.property.jvm" level="project" />
</component>
</module>

View File

@ -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"
}
}
}

View File

@ -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

View File

@ -18,7 +18,6 @@
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="codeGeneration" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.property.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
</component>
</module>

View File

@ -46,14 +46,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
if(binExpr.operator in associativeOperators) {
// A = <something-without-A> <associativeoperator> <otherthing-with-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))

View File

@ -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<TypecastExpression>() // casted to word
expr.right shouldBe instanceOf<PrefixExpression>()
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<Assignment>()
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<PrefixExpression>()
(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<PrefixExpression>()
(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
}
})

View File

@ -15,7 +15,6 @@
<orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.property.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
</component>
</module>

View File

@ -62,6 +62,21 @@ sealed class Expression: Node {
else -> other==this
}
}
fun typecastTo(targetDt: DataType, program: Program, sourceDt: DataType?=null, implicit: Boolean=false): Pair<Boolean, Expression> {
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)
}
}

View File

@ -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
}
})

View File

@ -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

View File

@ -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