mirror of
				https://github.com/irmen/prog8.git
				synced 2025-10-31 15:16:13 +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
 | |
|     }
 | |
| }) |