mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
191 lines
7.3 KiB
Kotlin
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])
|
|
}
|
|
}
|
|
})
|