mirror of
https://github.com/irmen/prog8.git
synced 2025-05-16 01:38:22 +00:00
196 lines
6.7 KiB
Kotlin
196 lines
6.7 KiB
Kotlin
package prog8tests.compiler.codegeneration
|
|
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.engine.spec.tempdir
|
|
import io.kotest.matchers.shouldBe
|
|
import io.kotest.matchers.shouldNotBe
|
|
import prog8.code.ast.PtBlock
|
|
import prog8.code.ast.PtVariable
|
|
import prog8.code.core.BaseDataType
|
|
import prog8.code.core.DataType
|
|
import prog8.code.target.C64Target
|
|
import prog8.code.target.Cx16Target
|
|
import prog8tests.helpers.compileText
|
|
import kotlin.io.path.readBytes
|
|
import kotlin.io.path.readText
|
|
|
|
class TestLibrary: FunSpec({
|
|
|
|
val outputDir = tempdir().toPath()
|
|
|
|
test("library compilation (x16)") {
|
|
val src="""
|
|
%address ${'$'}A050
|
|
%memtop ${'$'}C000
|
|
%output library
|
|
|
|
%import textio
|
|
|
|
|
|
main {
|
|
; Create a jump table as first thing in the library.
|
|
uword[] @shared @nosplit jumptable = [
|
|
; NOTE: the compiler has inserted a single JMP instruction at the start of the 'main' block, that jumps to the start() routine.
|
|
; This is convenient because the rest of the jump table simply follows it,
|
|
; making the first jump neatly be the required initialization routine for the library (initializing variables and BSS region).
|
|
; btw, $4c = opcode for JMP.
|
|
$4c00, &library.func1,
|
|
$4c00, &library.func2,
|
|
]
|
|
|
|
sub start() {
|
|
; has to be here for initialization (BSS, variables init).
|
|
%asm {{
|
|
nop
|
|
}}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
library {
|
|
sub func1() {
|
|
cx16.r0L++
|
|
}
|
|
|
|
sub func2() {
|
|
cx16.r0L--
|
|
}
|
|
}"""
|
|
|
|
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = true)!!
|
|
val ep = result.codegenAst!!.entrypoint()
|
|
val main = ep!!.parent as PtBlock
|
|
main.name shouldBe "p8b_main"
|
|
val jumptable = main.children[0] as PtVariable
|
|
jumptable.name shouldBe "p8v_jumptable"
|
|
jumptable.type shouldBe DataType.arrayFor(BaseDataType.UWORD, false)
|
|
jumptable.arraySize shouldBe 4u
|
|
val bin = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".bin").readBytes().map { it.toUByte() }
|
|
|
|
val loadAddr = 0xa050u
|
|
val startAddr = loadAddr + 0xbu
|
|
val func1Addr = loadAddr + 0x10u
|
|
val func2Addr = loadAddr + 0x13u
|
|
fun msb(addr: UInt) = addr.shr(8).toUByte()
|
|
fun lsb(addr: UInt) = addr.and(255u).toUByte()
|
|
fun offset(addr: UInt) = (addr-loadAddr).toInt()+0 // no PRG header
|
|
|
|
// cx16 library output has no PRG header.
|
|
// first jump table entry; a JMP to start() inserted by prog8 itself
|
|
bin[0] shouldBe 0x4cu // JMP
|
|
bin[1] shouldBe lsb(startAddr)
|
|
bin[2] shouldBe msb(startAddr)
|
|
bin[3] shouldBe 0x00u // 0 padding
|
|
// second jump table entry (first array element!)
|
|
bin[4] shouldBe 0x4cu // JMP
|
|
bin[5] shouldBe lsb(func1Addr)
|
|
bin[6] shouldBe msb(func1Addr)
|
|
bin[7] shouldBe 0x00u // 0 padding
|
|
// third jump table entry (second array element!)
|
|
bin[8] shouldBe 0x4cu // JMP
|
|
bin[9] shouldBe lsb(func2Addr)
|
|
bin[10] shouldBe msb(func2Addr)
|
|
bin[11] shouldNotBe 0x00u // no more padding after jump table
|
|
|
|
// check start() contents
|
|
bin[offset(startAddr)] shouldBe 0x20u // JSR (to clear_bss)
|
|
bin[offset(startAddr)+3] shouldBe 0xeau // NOP
|
|
bin[offset(startAddr)+4] shouldBe 0x60u // RTS
|
|
|
|
// check func1
|
|
bin[offset(func1Addr)] shouldBe 0xe6u // INC
|
|
// check func2
|
|
bin[offset(func2Addr)] shouldBe 0xC6u // DEC
|
|
}
|
|
|
|
test("library compilation (c64)") {
|
|
val src="""
|
|
%address ${'$'}8050
|
|
%memtop ${'$'}a000
|
|
%output library
|
|
|
|
%import textio
|
|
|
|
|
|
main {
|
|
; Create a jump table as first thing in the library.
|
|
uword[] @shared @nosplit jumptable = [
|
|
; NOTE: the compiler has inserted a single JMP instruction at the start of the 'main' block, that jumps to the start() routine.
|
|
; This is convenient because the rest of the jump table simply follows it,
|
|
; making the first jump neatly be the required initialization routine for the library (initializing variables and BSS region).
|
|
; btw, ${'$'}4c = opcode for JMP.
|
|
${'$'}4c00, &library.func1,
|
|
${'$'}4c00, &library.func2,
|
|
]
|
|
|
|
sub start() {
|
|
; has to be here for initialization (BSS, variables init).
|
|
%asm {{
|
|
nop
|
|
}}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
library {
|
|
sub func1() {
|
|
cx16.r0L++
|
|
}
|
|
|
|
sub func2() {
|
|
cx16.r0L--
|
|
}
|
|
}"""
|
|
|
|
val result = compileText(C64Target(), true, src, outputDir, writeAssembly = true)!!
|
|
val ep = result.codegenAst!!.entrypoint()
|
|
val main = ep!!.parent as PtBlock
|
|
main.name shouldBe "p8b_main"
|
|
val jumptable = main.children[0] as PtVariable
|
|
jumptable.name shouldBe "p8v_jumptable"
|
|
jumptable.type shouldBe DataType.arrayFor(BaseDataType.UWORD, false)
|
|
jumptable.arraySize shouldBe 4u
|
|
val asm = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm").readText()
|
|
println(asm)
|
|
val bin = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".bin").readBytes().map { it.toUByte() }
|
|
|
|
val loadAddr = 0x8050u
|
|
val startAddr = loadAddr + 0xbu
|
|
val func1Addr = loadAddr + 0x10u
|
|
val func2Addr = loadAddr + 0x14u
|
|
fun msb(addr: UInt) = addr.shr(8).toUByte()
|
|
fun lsb(addr: UInt) = addr.and(255u).toUByte()
|
|
fun offset(addr: UInt) = (addr-loadAddr).toInt()+2
|
|
|
|
// PRG header (C64 library output has this)
|
|
bin[0] shouldBe lsb(loadAddr)
|
|
bin[1] shouldBe msb(loadAddr)
|
|
// first jump table entry; a JMP to start() inserted by prog8 itself
|
|
bin[2] shouldBe 0x4cu // JMP
|
|
bin[3] shouldBe lsb(startAddr)
|
|
bin[4] shouldBe msb(startAddr)
|
|
bin[5] shouldBe 0x00u // 0 padding
|
|
// second jump table entry (first array element!)
|
|
bin[6] shouldBe 0x4cu // JMP
|
|
bin[7] shouldBe lsb(func1Addr)
|
|
bin[8] shouldBe msb(func1Addr)
|
|
bin[9] shouldBe 0x00u // 0 padding
|
|
// third jump table entry (second array element!)
|
|
bin[10] shouldBe 0x4cu // JMP
|
|
bin[11] shouldBe lsb(func2Addr)
|
|
bin[12] shouldBe msb(func2Addr)
|
|
bin[13] shouldNotBe 0x00u // no more padding after jump table
|
|
|
|
// check start() contents
|
|
bin[offset(startAddr)] shouldBe 0x20u // JSR (to clear_bss)
|
|
bin[offset(startAddr)+3] shouldBe 0xeau // NOP
|
|
bin[offset(startAddr)+4] shouldBe 0x60u // RTS
|
|
|
|
// check func1
|
|
bin[offset(func1Addr)] shouldBe 0xeeu // INC abs
|
|
// check func2
|
|
bin[offset(func2Addr)] shouldBe 0xCeu // DEC abs
|
|
}
|
|
}) |