Files
prog8/compiler/test/codegeneration/TestNewCodegenBugs.kt
T

191 lines
7.3 KiB
Kotlin

package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.target.C64Target
import prog8tests.helpers.compileText
import prog8tests.helpers.simulate
import java.nio.file.Files
import kotlin.io.path.readText
class TestNewCodegenBugs : FunSpec({
val outputDir = Files.createTempDirectory("prog8test")
test("float memory += / -= constant (ADDIM/SUBIM) compiles and emits correct sequences") {
val src = $$"""
%option no_sysinit
%launcher none
%import floats
main {
&ubyte result = $0340
sub start() {
float @shared f = 1.0
f += 0.5
f -= 0.25
f += 10.0
f -= 0.25
if f == 11.0 {
result = 1
} else {
result = 2
}
}
}
""".trimIndent()
// with optimize=false the whole floats library (incl. atan2's `atn += pi`)
// is kept alive, which is what originally triggered the ADDIM crash
val compileResult = compileText(C64Target(), false, src, outputDir, newCodegen = true, writeAssembly = true)!!
val asm = compileResult.compilationOptions.outputDir.resolve(compileResult.compilerAst.name + ".asm").readText()
val lines = asm.lines().map { it.trim() }
// each ADDIM emits MOVFM + FADD + MOVMF, each SUBIM emits push/pop + FSUBT + MOVMF
fun sequenceAfter(marker: String, window: Int, instruction: String): Boolean =
lines.withIndex().any { (i, line) ->
line.contains(marker) && lines.drop(i + 1).take(window).any { it == instruction }
}
sequenceAfter("; addim.f", 10, "jsr floats.FADD") shouldBe true
sequenceAfter("; subim.f", 14, "jsr floats.FSUBT") shouldBe true
}
test("BSS struct instance and union instance assemble and work correctly") {
val src = $$"""
%option no_sysinit
%launcher none
main {
struct S {
uword w
}
union U {
uword a
uword b
}
sub start() {
^^S s = ^^S : []
^^U u = ^^U : []
s^^.w = $1234
u^^.b = $beef
&ubyte result = $0340
if s^^.w == $1234 and u^^.b == $beef {
result = 1
} else {
result = 2
}
}
}
""".trimIndent()
val compileResult = compileText(C64Target(), false, src, outputDir, newCodegen = true, writeAssembly = true)!!
// every generated code reference must use the emitted prog8_struct_instances_bss block
val asm = compileResult.compilationOptions.outputDir.resolve(compileResult.compilerAst.name + ".asm").readText()
val lines = asm.lines().map { it.trim() }
lines.any { it == "prog8_struct_instances_bss .block" } shouldBe true
lines.none { !it.startsWith(";") && it.contains("prog8_struct_instances.main_") } shouldBe true
val machine = compileResult.simulate()
machine.assertMemory(0x340, 1)
}
test("initialized struct instance keeps float and long fields intact") {
val src = $$"""
%option no_sysinit
%launcher none
%import floats
main {
struct P {
float x
ubyte y
}
struct L {
long n
ubyte b
}
sub start() {
^^P p = ^^P : [1.5, 42]
^^L l = ^^L : [123456, 7]
&ubyte result = $0340
if p^^.y == 42 and p^^.x == 1.5 and l^^.n == 123456 and l^^.b == 7 {
result = 1
} else {
result = 2
}
}
}
""".trimIndent()
val compileResult = compileText(C64Target(), false, src, outputDir, newCodegen = true, writeAssembly = true)!!
val asm = compileResult.compilationOptions.outputDir.resolve(compileResult.compilerAst.name + ".asm").readText()
val lines = asm.lines().map { it.trim() }
lines.any { it.contains(".byte") && it.contains("[$81, $40, $00, $00, $00]") } shouldBe true
lines.any { it.contains(".dint") && it.contains("123456") } shouldBe true
}
test("multiple status flag returns in one call don't clobber each other") {
val src = $$"""
%option no_sysinit
%launcher none
main {
inline asmsub flaga(ubyte arg @A) -> bool @Pc, bool @Pz, bool @Pn {
%asm {{
cmp #0
}}
}
inline asmsub flagb(ubyte arg @A) -> bool @Pc, bool @Pz, bool @Pn {
%asm {{
cmp #1
}}
}
&ubyte r1 = $0340
&ubyte r2 = $0341
&ubyte r3 = $0342
&ubyte r4 = $0343
sub start() {
bool c
bool z
bool n
c, z, n = flaga(0) ; C=1 Z=1 N=0
r1 = 0
if c r1 += 1
if z r1 += 2
if n r1 += 4
c, z, n = flaga($80) ; C=1 Z=0 N=1
r2 = 0
if c r2 += 1
if z r2 += 2
if n r2 += 4
c, z, n = flagb(0) ; C=0 Z=0 N=1
r3 = 0
if c r3 += 1
if z r3 += 2
if n r3 += 4
c, z, n = flagb(1) ; C=1 Z=1 N=0
r4 = 0
if c r4 += 1
if z r4 += 2
if n r4 += 4
}
}
""".trimIndent()
// the status extraction of one flag must not affect the other flags
val expected = listOf(3, 5, 4, 3)
for (optimize in listOf(false, true)) {
val newResult = compileText(C64Target(), optimize, src, outputDir, newCodegen = true, writeAssembly = true)!!
val newMachine = newResult.simulate()
newMachine.assertMemory(0x340, expected[0])
newMachine.assertMemory(0x341, expected[1])
newMachine.assertMemory(0x342, expected[2])
newMachine.assertMemory(0x343, expected[3])
// the legacy codegen is the reference implementation and must produce the same values
val legacyResult = compileText(C64Target(), optimize, src, outputDir, newCodegen = false, writeAssembly = false)!!
val legacyMachine = legacyResult.simulate()
legacyMachine.assertMemory(0x340, expected[0])
legacyMachine.assertMemory(0x341, expected[1])
legacyMachine.assertMemory(0x342, expected[2])
legacyMachine.assertMemory(0x343, expected[3])
}
}
})