2021-10-20 20:16:26 +00:00
|
|
|
package prog8tests
|
|
|
|
|
2021-11-09 00:13:23 +00:00
|
|
|
import io.kotest.assertions.fail
|
2021-11-08 14:50:29 +00:00
|
|
|
import io.kotest.assertions.withClue
|
2021-11-07 23:16:58 +00:00
|
|
|
import io.kotest.core.spec.style.FunSpec
|
2021-11-08 14:50:29 +00:00
|
|
|
import io.kotest.matchers.shouldBe
|
|
|
|
import io.kotest.matchers.shouldNotBe
|
|
|
|
import io.kotest.matchers.types.instanceOf
|
|
|
|
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
2021-11-01 23:47:01 +00:00
|
|
|
import prog8.ast.Program
|
|
|
|
import prog8.ast.base.DataType
|
|
|
|
import prog8.ast.base.ParentSentinel
|
|
|
|
import prog8.ast.base.Position
|
2021-11-09 00:13:23 +00:00
|
|
|
import prog8.ast.expressions.*
|
2021-11-01 23:47:01 +00:00
|
|
|
import prog8.ast.statements.*
|
2021-11-09 00:13:23 +00:00
|
|
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
2021-10-20 20:16:26 +00:00
|
|
|
import prog8.compiler.target.C64Target
|
2021-11-09 00:13:23 +00:00
|
|
|
import prog8.compilerinterface.*
|
2021-11-01 23:47:01 +00:00
|
|
|
import prog8tests.helpers.DummyFunctions
|
|
|
|
import prog8tests.helpers.DummyMemsizer
|
|
|
|
import prog8tests.helpers.DummyStringEncoder
|
2021-11-09 00:13:23 +00:00
|
|
|
import prog8tests.helpers.ErrorReporterForTests
|
2021-10-20 20:16:26 +00:00
|
|
|
import prog8tests.helpers.assertSuccess
|
|
|
|
import prog8tests.helpers.compileText
|
2021-11-09 02:45:07 +00:00
|
|
|
import prog8tests.helpers.generateAssembly
|
2021-10-20 20:16:26 +00:00
|
|
|
|
2021-11-07 23:16:58 +00:00
|
|
|
class TestOptimization: FunSpec({
|
|
|
|
test("testRemoveEmptySubroutineExceptStart") {
|
2021-10-20 20:16:26 +00:00
|
|
|
val sourcecode = """
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
}
|
|
|
|
sub empty() {
|
|
|
|
; going to be removed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
2021-10-29 22:25:34 +00:00
|
|
|
val toplevelModule = result.program.toplevelModule
|
2021-10-20 20:16:26 +00:00
|
|
|
val mainBlock = toplevelModule.statements.single() as Block
|
2021-10-20 20:50:18 +00:00
|
|
|
val startSub = mainBlock.statements.single() as Subroutine
|
2021-11-08 14:50:29 +00:00
|
|
|
result.program.entrypoint shouldBeSameInstanceAs startSub
|
|
|
|
withClue("only start sub should remain") {
|
|
|
|
startSub.name shouldBe "start"
|
|
|
|
}
|
|
|
|
withClue("compiler has inserted return in empty subroutines") {
|
|
|
|
startSub.statements.single() shouldBe instanceOf<Return>()
|
|
|
|
}
|
2021-10-20 20:16:26 +00:00
|
|
|
}
|
|
|
|
|
2021-11-07 23:16:58 +00:00
|
|
|
test("testDontRemoveEmptySubroutineIfItsReferenced") {
|
2021-10-20 20:16:26 +00:00
|
|
|
val sourcecode = """
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
uword xx = &empty
|
|
|
|
xx++
|
|
|
|
}
|
|
|
|
sub empty() {
|
|
|
|
; should not be removed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
2021-10-29 22:25:34 +00:00
|
|
|
val toplevelModule = result.program.toplevelModule
|
2021-10-20 20:16:26 +00:00
|
|
|
val mainBlock = toplevelModule.statements.single() as Block
|
|
|
|
val startSub = mainBlock.statements[0] as Subroutine
|
|
|
|
val emptySub = mainBlock.statements[1] as Subroutine
|
2021-11-08 14:50:29 +00:00
|
|
|
result.program.entrypoint shouldBeSameInstanceAs startSub
|
|
|
|
startSub.name shouldBe "start"
|
|
|
|
emptySub.name shouldBe "empty"
|
|
|
|
withClue("compiler has inserted return in empty subroutines") {
|
|
|
|
emptySub.statements.single() shouldBe instanceOf<Return>()
|
|
|
|
}
|
2021-10-20 20:16:26 +00:00
|
|
|
}
|
2021-11-01 23:47:01 +00:00
|
|
|
|
2021-11-07 23:16:58 +00:00
|
|
|
test("testGeneratedConstvalueInheritsProperParentLinkage") {
|
2021-11-01 23:47:01 +00:00
|
|
|
val number = NumericLiteralValue(DataType.UBYTE, 11, Position.DUMMY)
|
|
|
|
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
|
|
|
|
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
|
|
|
tc.linkParents(ParentSentinel)
|
2021-11-08 14:50:29 +00:00
|
|
|
tc.parent shouldNotBe null
|
|
|
|
number.parent shouldNotBe null
|
|
|
|
tc shouldBeSameInstanceAs number.parent
|
2021-11-01 23:47:01 +00:00
|
|
|
val constvalue = tc.constValue(program)!!
|
2021-11-08 14:50:29 +00:00
|
|
|
constvalue shouldBe instanceOf<NumericLiteralValue>()
|
|
|
|
constvalue.number.toInt() shouldBe 11
|
|
|
|
constvalue.type shouldBe DataType.BYTE
|
|
|
|
tc shouldBeSameInstanceAs constvalue.parent
|
2021-11-01 23:47:01 +00:00
|
|
|
}
|
|
|
|
|
2021-11-07 23:16:58 +00:00
|
|
|
test("testConstantFoldedAndSilentlyTypecastedForInitializerValues") {
|
2021-11-01 23:47:01 +00:00
|
|
|
val sourcecode = """
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
const ubyte TEST = 10
|
|
|
|
byte x1 = TEST as byte + 1
|
|
|
|
byte x2 = 1 + TEST as byte
|
|
|
|
ubyte y1 = TEST + 1 as byte
|
|
|
|
ubyte y2 = 1 as byte + TEST
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
|
|
|
val mainsub = result.program.entrypoint
|
2021-11-08 14:50:29 +00:00
|
|
|
mainsub.statements.size shouldBe 10
|
2021-11-01 23:47:01 +00:00
|
|
|
val declTest = mainsub.statements[0] as VarDecl
|
|
|
|
val declX1 = mainsub.statements[1] as VarDecl
|
|
|
|
val initX1 = mainsub.statements[2] as Assignment
|
|
|
|
val declX2 = mainsub.statements[3] as VarDecl
|
|
|
|
val initX2 = mainsub.statements[4] as Assignment
|
|
|
|
val declY1 = mainsub.statements[5] as VarDecl
|
|
|
|
val initY1 = mainsub.statements[6] as Assignment
|
|
|
|
val declY2 = mainsub.statements[7] as VarDecl
|
|
|
|
val initY2 = mainsub.statements[8] as Assignment
|
2021-11-08 14:50:29 +00:00
|
|
|
mainsub.statements[9] shouldBe instanceOf<Return>()
|
|
|
|
(declTest.value as NumericLiteralValue).number.toDouble() shouldBe 10.0
|
|
|
|
declX1.value shouldBe null
|
|
|
|
declX2.value shouldBe null
|
|
|
|
declY1.value shouldBe null
|
|
|
|
declY2.value shouldBe null
|
|
|
|
(initX1.value as NumericLiteralValue).type shouldBe DataType.BYTE
|
|
|
|
(initX1.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
|
|
|
|
(initX2.value as NumericLiteralValue).type shouldBe DataType.BYTE
|
|
|
|
(initX2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
|
|
|
|
(initY1.value as NumericLiteralValue).type shouldBe DataType.UBYTE
|
|
|
|
(initY1.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
|
|
|
|
(initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE
|
|
|
|
(initY2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
|
2021-11-01 23:47:01 +00:00
|
|
|
}
|
2021-11-09 00:13:23 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2021-11-09 02:45:07 +00:00
|
|
|
val asm = generateAssembly(result.program, options)
|
|
|
|
asm.valid shouldBe true
|
|
|
|
}
|
|
|
|
|
|
|
|
test("asmgen correctly deals with float typecasting in augmented assignment") {
|
|
|
|
val src="""
|
|
|
|
%option enable_floats
|
|
|
|
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
ubyte ub
|
|
|
|
float ff
|
|
|
|
ff += (ub as float) ; operator doesn't matter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val result1 = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
|
|
|
|
|
|
|
|
val assignYY = result1.program.entrypoint.statements.last() as Assignment
|
|
|
|
assignYY.isAugmentable shouldBe true
|
|
|
|
assignYY.target.identifier!!.nameInSource shouldBe listOf("ff")
|
|
|
|
val value = assignYY.value as BinaryExpression
|
|
|
|
value.operator shouldBe "+"
|
|
|
|
value.left shouldBe IdentifierReference(listOf("ff"), Position.DUMMY)
|
|
|
|
value.right shouldBe instanceOf<TypecastExpression>()
|
|
|
|
|
|
|
|
val asm = generateAssembly(result1.program)
|
2021-11-09 00:13:23 +00:00
|
|
|
asm.valid shouldBe true
|
|
|
|
}
|
2021-11-07 23:16:58 +00:00
|
|
|
})
|