mirror of
https://github.com/irmen/prog8.git
synced 2025-06-20 06:23:34 +00:00
Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
475e927178 | |||
ca7932c4f0 | |||
8ab47d3321 | |||
def7e87151 | |||
27568c2bef | |||
0694a187d7 | |||
832601b36b | |||
578969c34c | |||
d1d0115aed | |||
c89e6ebfab | |||
ca1089b881 | |||
a1d04f2aad | |||
bf0604133c | |||
a82b2da16e | |||
f2273c0acc | |||
17bedac96c | |||
4831fad27a | |||
5e896cf582 | |||
add3491c57 | |||
f470576822 | |||
10760a53a8 | |||
eee805183c | |||
b8fb391022 | |||
3c698f1584 | |||
2fad52d684 | |||
ec64a68a71 | |||
db55562f6a | |||
d8409a9d2b | |||
0d0ce6eec1 | |||
483f313eda | |||
7b6c742178 | |||
d4a35ba6ff | |||
68b112837a | |||
e2f20ebf94 | |||
f870e4965a | |||
7ebcb219d6 | |||
c21913a66b | |||
77e956a29f | |||
08275c406a | |||
2931e1b87b | |||
153b422496 | |||
0f6a6d6fea | |||
91fdb3e2d4 | |||
d8e87bd881 | |||
922033c1b2 | |||
df1793efbf | |||
836a2700f2 | |||
8f3aaf77a1 | |||
00c059e5b1 | |||
f4f355c74a | |||
b465fc5aaf | |||
2d78eaa48d | |||
d08451bccc | |||
d8e785aed0 | |||
267b6f49b5 | |||
e6688f4b9d | |||
9d7b9771c2 | |||
136a9a39de | |||
3dcf628fdb | |||
e614e9787a |
@ -8,10 +8,6 @@ import prog8.code.core.*
|
||||
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||
*/
|
||||
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
||||
fun print() = printIndented(0)
|
||||
|
||||
override fun printProperties() { }
|
||||
|
||||
/**
|
||||
* The table as a flat mapping of scoped names to the StNode.
|
||||
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
||||
@ -138,29 +134,6 @@ open class StNode(val name: String,
|
||||
}
|
||||
}
|
||||
|
||||
fun printIndented(indent: Int) {
|
||||
print(" ".repeat(indent))
|
||||
when(type) {
|
||||
StNodeType.GLOBAL -> print("SYMBOL-TABLE:")
|
||||
StNodeType.BLOCK -> print("(B) ")
|
||||
StNodeType.SUBROUTINE -> print("(S) ")
|
||||
StNodeType.LABEL -> print("(L) ")
|
||||
StNodeType.STATICVAR -> print("(V) ")
|
||||
StNodeType.MEMVAR -> print("(M) ")
|
||||
StNodeType.MEMORYSLAB -> print("(MS) ")
|
||||
StNodeType.CONSTANT -> print("(C) ")
|
||||
StNodeType.BUILTINFUNC -> print("(F) ")
|
||||
StNodeType.ROMSUB -> print("(R) ")
|
||||
}
|
||||
printProperties()
|
||||
println()
|
||||
children.forEach { (_, node) -> node.printIndented(indent+1) }
|
||||
}
|
||||
|
||||
open fun printProperties() {
|
||||
print("$name ")
|
||||
}
|
||||
|
||||
fun add(child: StNode) {
|
||||
children[child.name] = child
|
||||
child.parent = this
|
||||
@ -201,18 +174,11 @@ class StStaticVariable(name: String,
|
||||
require(length == onetimeInitializationStringValue.first.length+1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print("$name dt=$dt zpw=$zpwish bss=$bss")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
|
||||
StNode(name, StNodeType.CONSTANT, position) {
|
||||
override fun printProperties() {
|
||||
print("$name dt=$dt value=$value")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -222,9 +188,6 @@ class StMemVar(name: String,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
position: Position) :
|
||||
StNode(name, StNodeType.MEMVAR, position) {
|
||||
override fun printProperties() {
|
||||
print("$name dt=$dt address=${address.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
class StMemorySlab(
|
||||
@ -234,16 +197,10 @@ class StMemorySlab(
|
||||
position: Position
|
||||
):
|
||||
StNode(name, StNodeType.MEMORYSLAB, position) {
|
||||
override fun printProperties() {
|
||||
print("$name size=$size align=$align")
|
||||
}
|
||||
}
|
||||
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
||||
override fun printProperties() {
|
||||
print(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -253,9 +210,6 @@ class StRomSub(name: String,
|
||||
val returns: List<RegisterOrStatusflag>,
|
||||
position: Position) :
|
||||
StNode(name, StNodeType.ROMSUB, position) {
|
||||
override fun printProperties() {
|
||||
print("$name address=${address.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,7 +20,6 @@ class CompilationOptions(val output: OutputType,
|
||||
var asmQuiet: Boolean = false,
|
||||
var asmListfile: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var keepIR: Boolean = false,
|
||||
var evalStackBaseAddress: UInt? = null,
|
||||
var outputDir: Path = Path(""),
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
|
@ -17,11 +17,11 @@ interface IMachineDefinition {
|
||||
var ESTACK_HI: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
|
||||
val opcodeNames: Set<String>
|
||||
var zeropage: Zeropage
|
||||
val cpu: CpuType
|
||||
var zeropage: Zeropage
|
||||
var golden: GoldenRam
|
||||
|
||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
@ -31,5 +31,7 @@ interface IMachineDefinition {
|
||||
require(evalStackBaseAddress and 255u == 0u)
|
||||
ESTACK_LO = evalStackBaseAddress
|
||||
ESTACK_HI = evalStackBaseAddress + 256u
|
||||
require(ESTACK_LO !in golden.region && ESTACK_HI !in golden.region) { "user-set ESTACK can't be in GOLDEN ram" }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,21 +5,31 @@ import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
|
||||
|
||||
class ZeropageAllocationError(message: String) : Exception(message)
|
||||
class MemAllocationError(message: String) : Exception(message)
|
||||
|
||||
|
||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
||||
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
|
||||
|
||||
abstract fun allocate(name: List<String>,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError>
|
||||
}
|
||||
|
||||
|
||||
abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
|
||||
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
|
||||
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
|
||||
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
|
||||
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
|
||||
|
||||
data class ZpAllocation(val address: UInt, val dt: DataType, val size: Int)
|
||||
|
||||
// the variables allocated into Zeropage.
|
||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||
val allocatedVariables = mutableMapOf<List<String>, ZpAllocation>()
|
||||
val allocatedVariables = mutableMapOf<List<String>, VarAllocation>()
|
||||
|
||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
@ -41,17 +51,16 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||
}
|
||||
|
||||
fun allocate(name: List<String>,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
errors: IErrorReporter
|
||||
): Result<Pair<UInt, Int>, ZeropageAllocationError> {
|
||||
override fun allocate(name: List<String>,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
||||
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return Err(ZeropageAllocationError("zero page usage has been disabled"))
|
||||
return Err(MemAllocationError("zero page usage has been disabled"))
|
||||
|
||||
val size: Int =
|
||||
when (datatype) {
|
||||
@ -72,9 +81,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
else
|
||||
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
|
||||
memsize
|
||||
} else return Err(ZeropageAllocationError("floating point option not enabled"))
|
||||
} else return Err(MemAllocationError("floating point option not enabled"))
|
||||
}
|
||||
else -> return Err(ZeropageAllocationError("cannot put datatype $datatype in zeropage"))
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
synchronized(this) {
|
||||
@ -82,18 +91,18 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
if(size==1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(Pair(makeAllocation(candidate, 1, datatype, name), 1))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||
}
|
||||
return Ok(Pair(makeAllocation(free[0], 1, datatype, name), 1))
|
||||
return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
|
||||
}
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(Pair(makeAllocation(candidate, size, datatype, name), size))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ZeropageAllocationError("no more free space in ZP to allocate $size sequential bytes"))
|
||||
return Err(MemAllocationError("no more free space in ZP to allocate $size sequential bytes"))
|
||||
}
|
||||
|
||||
private fun reserve(range: UIntRange) = free.removeAll(range)
|
||||
@ -103,9 +112,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
free.removeAll(address until address+size.toUInt())
|
||||
if(name.isNotEmpty()) {
|
||||
allocatedVariables[name] = when(datatype) {
|
||||
in NumericDatatypes -> ZpAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
|
||||
DataType.STR -> ZpAllocation(address, datatype, size)
|
||||
in ArrayDatatypes -> ZpAllocation(address, datatype, size)
|
||||
in NumericDatatypes -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
|
||||
DataType.STR -> VarAllocation(address, datatype, size)
|
||||
in ArrayDatatypes -> VarAllocation(address, datatype, size)
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
@ -120,3 +129,37 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
abstract fun allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
|
||||
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
||||
private var nextLocation: UInt = region.first
|
||||
|
||||
override fun allocate(
|
||||
name: List<String>,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
val size: Int =
|
||||
when (datatype) {
|
||||
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
|
||||
DataType.STR, in ArrayDatatypes -> {
|
||||
options.compTarget.memorySize(datatype, numElements!!)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (options.floats) {
|
||||
options.compTarget.memorySize(DataType.FLOAT)
|
||||
} else return Err(MemAllocationError("floating point option not enabled"))
|
||||
}
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
|
||||
val result = Ok(VarAllocation(nextLocation, datatype, size))
|
||||
nextLocation += size.toUInt()
|
||||
result
|
||||
} else
|
||||
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package prog8.code.target.atari
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.c64.normal6502instructions
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@ -19,6 +18,7 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||
|
||||
@ -57,9 +57,8 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = AtariZeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
|
||||
override val opcodeNames = normal6502instructions
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package prog8.code.target.c128
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.c64.normal6502instructions
|
||||
import prog8.code.target.cbm.Mflpt5
|
||||
import java.nio.file.Path
|
||||
|
||||
@ -18,8 +17,8 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@ -47,9 +46,8 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C128Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
|
||||
}
|
||||
|
||||
override val opcodeNames = normal6502instructions
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@ -55,22 +55,9 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, 0xc000u until ESTACK_LO)
|
||||
}
|
||||
|
||||
override val opcodeNames = normal6502instructions
|
||||
}
|
||||
|
||||
|
||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||
internal val normal6502instructions = setOf(
|
||||
"adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
|
||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
|
||||
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
|
||||
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
|
||||
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
|
||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
@ -83,12 +83,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
free.remove((4+reg*2).toUInt())
|
||||
free.remove((5+reg*2).toUInt())
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
||||
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
@ -50,28 +50,16 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
println("\nStarting Commander X16 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
|
||||
val process: Process = processb.start()
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, 0x0600u until 0x0800u)
|
||||
}
|
||||
|
||||
// 65c02 opcodes, these cannot be used as variable or label names
|
||||
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
|
||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
|
||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||
"inc", "inx", "iny", "jmp", "jsr",
|
||||
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
|
||||
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
|
||||
"sec", "sed", "sei",
|
||||
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
|
||||
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
|
||||
"rmb", "smb", "stp", "wai")
|
||||
|
||||
|
||||
}
|
||||
|
@ -58,12 +58,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
package prog8.code.target.virtual
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.CpuType
|
||||
import prog8.code.core.IMachineDefinition
|
||||
import prog8.code.core.Zeropage
|
||||
import prog8.code.core.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isReadable
|
||||
import kotlin.io.path.name
|
||||
@ -20,8 +17,8 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override var ESTACK_LO = 0u // not actually used
|
||||
override var ESTACK_HI = 0u // not actually used
|
||||
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
override lateinit var golden: GoldenRam // not actually used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||
|
||||
@ -47,11 +44,9 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = false
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {}
|
||||
|
||||
override val opcodeNames = emptySet<String>()
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
|
||||
}
|
||||
|
||||
interface IVirtualMachineRunner {
|
||||
fun runProgram(irSource: CharSequence)
|
||||
fun runProgram(irSource: String)
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ class AsmGen(internal val program: Program,
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||
out(
|
||||
"""
|
||||
lda $indexName
|
||||
@ -406,7 +406,7 @@ class AsmGen(internal val program: Program,
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||
out(
|
||||
"""
|
||||
lda $indexName
|
||||
@ -605,7 +605,7 @@ class AsmGen(internal val program: Program,
|
||||
}
|
||||
|
||||
private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
|
||||
require(count in 257..65535)
|
||||
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = program.makeLabel("repeat")
|
||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
|
||||
@ -667,7 +667,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
|
||||
require(count in 2..256)
|
||||
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = program.makeLabel("repeat")
|
||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
|
||||
@ -794,7 +794,7 @@ $repeatLabel lda $counterVar
|
||||
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
|
||||
throw AssemblyError("only else part contains code, shoud have been switched already")
|
||||
|
||||
val jump = stmt.truepart.statements.first() as? Jump
|
||||
val jump = stmt.truepart.statements.firstOrNull() as? Jump
|
||||
if(jump!=null) {
|
||||
// branch with only a jump (goto)
|
||||
val instruction = branchInstruction(stmt.condition, false)
|
||||
@ -812,11 +812,13 @@ $repeatLabel lda $counterVar
|
||||
translate(stmt.elsepart)
|
||||
} else {
|
||||
if(stmt.elsepart.isEmpty()) {
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val elseLabel = program.makeLabel("branch_else")
|
||||
out(" $instruction $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(elseLabel)
|
||||
if(stmt.truepart.isNotEmpty()) {
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val elseLabel = program.makeLabel("branch_else")
|
||||
out(" $instruction $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(elseLabel)
|
||||
}
|
||||
} else {
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val elseLabel = program.makeLabel("branch_else")
|
||||
@ -1068,11 +1070,20 @@ $repeatLabel lda $counterVar
|
||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||
if(saveA)
|
||||
out(" pha")
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
if(saveA)
|
||||
out(" pla")
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
if(ptrAndIndex.second.isSimple) {
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
if(saveA)
|
||||
out(" pla")
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(saveA)
|
||||
out(" pla")
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
@ -1080,9 +1091,16 @@ $repeatLabel lda $counterVar
|
||||
out(" lda (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||
if(ptrAndIndex.second.isSimple) {
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -1576,8 +1594,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -1611,8 +1635,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -1648,8 +1678,15 @@ $repeatLabel lda $counterVar
|
||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.UWORD, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -1687,8 +1724,15 @@ $repeatLabel lda $counterVar
|
||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.WORD, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -1727,8 +1771,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -1764,8 +1814,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.BYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -1807,8 +1863,15 @@ $repeatLabel lda $counterVar
|
||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.UWORD, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2")
|
||||
}
|
||||
|
||||
@ -1851,8 +1914,15 @@ $repeatLabel lda $counterVar
|
||||
if(wordJumpForSimpleLeftOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
if(right.isSimple) {
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.WORD, right)
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -1892,8 +1962,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -1929,8 +2005,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.BYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -1974,8 +2056,15 @@ $repeatLabel lda $counterVar
|
||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.UWORD, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -2022,8 +2111,15 @@ $repeatLabel lda $counterVar
|
||||
return code(asmVariableName(left))
|
||||
}
|
||||
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
if(right.isSimple) {
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.WORD, right)
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -2059,8 +2155,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -2093,8 +2195,14 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.BYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||
out(" pla")
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -2129,8 +2237,15 @@ $repeatLabel lda $counterVar
|
||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
if(right.isSimple) {
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.UWORD, right)
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -2168,8 +2283,15 @@ $repeatLabel lda $counterVar
|
||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.WORD, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -2204,8 +2326,17 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else if(right.isSimple) {
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
out(" cmp P8ZP_SCRATCH_B1 | bne $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
@ -2241,8 +2372,17 @@ $repeatLabel lda $counterVar
|
||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||
return
|
||||
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
} else if(right.isSimple) {
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.A)
|
||||
} else {
|
||||
pushCpuStack(DataType.UBYTE, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
return code("P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
@ -2308,8 +2448,18 @@ $repeatLabel lda $counterVar
|
||||
""")
|
||||
}
|
||||
else -> {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
} else if(right.isSimple) {
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.UWORD, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
out("""
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bne $jumpIfFalseLabel
|
||||
@ -2384,8 +2534,18 @@ $repeatLabel lda $counterVar
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
if(left.isSimple) {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
} else if (right.isSimple) {
|
||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
} else {
|
||||
pushCpuStack(DataType.UWORD, left)
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
restoreRegisterStack(CpuRegister.Y, false)
|
||||
restoreRegisterStack(CpuRegister.A, false)
|
||||
}
|
||||
out("""
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bne +
|
||||
@ -2780,7 +2940,7 @@ $repeatLabel lda $counterVar
|
||||
val parameter = target.subroutineParameter
|
||||
if(parameter!=null) {
|
||||
val sub = parameter.definingSubroutine!!
|
||||
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs" }
|
||||
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs ${sub.position}" }
|
||||
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
|
||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||
if(reg.statusflag!=null) {
|
||||
@ -2866,17 +3026,27 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
internal fun popCpuStack(dt: DataType, targetAsmVarName: String) {
|
||||
when(dt) {
|
||||
in ByteDatatypes -> out(" pla | sta $targetAsmVarName")
|
||||
in WordDatatypes -> out(" pla | sta $targetAsmVarName+1 | pla | sta $targetAsmVarName")
|
||||
else -> throw AssemblyError("can't pop $dt")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun pushCpuStack(dt: DataType, value: Expression) {
|
||||
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
|
||||
if(dt in ByteDatatypes) {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, signed)
|
||||
out(" pha")
|
||||
} else {
|
||||
} else if(dt in WordDatatypes) {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
|
||||
if (isTargetCpu(CpuType.CPU65c02))
|
||||
out(" pha | phy")
|
||||
else
|
||||
out(" pha | tya | pha")
|
||||
} else {
|
||||
throw AssemblyError("can't push $dt")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods= optimizeJsrRts(linesByFour)
|
||||
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
@ -486,8 +486,11 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// jsr Sub + rts -> jmp Sub
|
||||
// rts + jmp -> remove jmp
|
||||
// rts + bxx -> remove bxx
|
||||
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[0].value
|
||||
@ -496,6 +499,28 @@ private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
else if (" rts" in first || "\trts" in first) {
|
||||
if (" jmp" in second || "\tjmp" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bra" in second || "\tbra" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bcc" in second || "\tbcc" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bcs" in second || "\tbcs" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" beq" in second || "\tbeq" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bne" in second || "\tbne" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bmi" in second || "\tbmi" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bpl" in second || "\tbpl" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bvs" in second || "\tbvs" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bvc" in second || "\tbvc" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
@ -254,15 +254,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(arg1.isSimple) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
} else {
|
||||
asmgen.pushCpuStack(DataType.UBYTE, arg1)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(arg1.isSimple) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
} else {
|
||||
asmgen.pushCpuStack(DataType.UBYTE, arg1)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
@ -288,13 +300,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
+""")
|
||||
if(arg1.isSimple) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(DataType.UWORD, arg1)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
@ -306,7 +330,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
if(discardResult || fcall !is BuiltinFunctionCall)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val name = (fcall.args[0] as StringLiteral).value
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
|
||||
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
|
||||
slabname.linkParents(fcall)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
||||
|
@ -124,7 +124,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
|
||||
require(callee.isAsmSubroutine)
|
||||
require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
|
||||
if(callee.parameters.isEmpty())
|
||||
return
|
||||
|
||||
|
@ -202,16 +202,10 @@ internal class ProgramAndVarsGen(
|
||||
asmsubs2asm(block.statements)
|
||||
|
||||
asmgen.out("")
|
||||
asmgen.out("; subroutines in this block")
|
||||
|
||||
// First translate regular statements, and then put the subroutines at the end.
|
||||
// (regular statements = everything except the initialization assignments;
|
||||
// these will be part of the prog8_init_vars init routine generated below)
|
||||
val initializers = blockVariableInitializers.getValue(block)
|
||||
val statements = block.statements.filterNot { it in initializers }
|
||||
val (subroutine, stmts) = statements.partition { it is Subroutine }
|
||||
stmts.forEach { asmgen.translate(it) }
|
||||
subroutine.forEach { asmgen.translate(it) }
|
||||
val notInitializers = block.statements.filterNot { it in initializers }
|
||||
notInitializers.forEach { asmgen.translate(it) }
|
||||
|
||||
if(!options.dontReinitGlobals) {
|
||||
// generate subroutine to initialize block-level (global) variables
|
||||
@ -435,13 +429,13 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private class ZpStringWithInitial(
|
||||
val name: List<String>,
|
||||
val alloc: Zeropage.ZpAllocation,
|
||||
val alloc: MemoryAllocator.VarAllocation,
|
||||
val value: Pair<String, Encoding>
|
||||
)
|
||||
|
||||
private class ZpArrayWithInitial(
|
||||
val name: List<String>,
|
||||
val alloc: Zeropage.ZpAllocation,
|
||||
val alloc: MemoryAllocator.VarAllocation,
|
||||
val value: StArray
|
||||
)
|
||||
|
||||
|
@ -16,7 +16,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
|
||||
private val zeropage = options.compTarget.machine.zeropage
|
||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
|
||||
internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
|
||||
|
||||
init {
|
||||
allocateZeropageVariables()
|
||||
|
@ -207,7 +207,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
||||
|
||||
init {
|
||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
|
||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||
"source dt size must be less or equal to target dt size at $position"
|
||||
}
|
||||
|
@ -27,13 +27,6 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
|
||||
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, origtarget.datatype, origtarget.scope,
|
||||
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
|
||||
} else origtarget
|
||||
}
|
||||
|
||||
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||
if(assign.isAugmentable) {
|
||||
augmentableAsmGen.translate(assign)
|
||||
@ -299,7 +292,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
)
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> augmentableAsmGen.inplaceNegate(assign)
|
||||
"-" -> augmentableAsmGen.inplaceNegate(assign, true)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(assign)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
@ -385,48 +378,60 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
if(!expr.inferType(program).isInteger)
|
||||
return false
|
||||
|
||||
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
|
||||
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
|
||||
expr.left.isSimple && expr.right.isSimple) {
|
||||
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
|
||||
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
|
||||
else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
when (expr.operator) {
|
||||
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
fun simpleLogicalBytesExpr() {
|
||||
// both left and right expression operands are simple.
|
||||
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
|
||||
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
|
||||
else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
when (expr.operator) {
|
||||
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
return true
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
}
|
||||
if(expr.left.inferType(program).isWords && expr.right.inferType(program).isWords &&
|
||||
expr.left.isSimple && expr.right.isSimple) {
|
||||
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
|
||||
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
|
||||
else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
||||
when (expr.operator) {
|
||||
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
|
||||
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
|
||||
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
fun simpleLogicalWordsExpr() {
|
||||
// both left and right expression operands are simple.
|
||||
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
|
||||
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
|
||||
else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
||||
when (expr.operator) {
|
||||
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
|
||||
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
|
||||
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
|
||||
if (expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes) {
|
||||
if (expr.right.isSimple) {
|
||||
simpleLogicalBytesExpr()
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (expr.left.inferType(program).isWords && expr.right.inferType(program).isWords) {
|
||||
if (expr.right.isSimple) {
|
||||
simpleLogicalWordsExpr()
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -622,6 +627,58 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="<<" || expr.operator==">>") {
|
||||
val shifts = expr.right.constValue(program)?.number?.toInt()
|
||||
if(shifts!=null) {
|
||||
val dt = expr.left.inferType(program)
|
||||
if(dt.isBytes && shifts in 0..7) {
|
||||
val signed = dt istype DataType.BYTE
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, signed)
|
||||
if(expr.operator=="<<") {
|
||||
repeat(shifts) {
|
||||
asmgen.out(" asl a")
|
||||
}
|
||||
} else {
|
||||
if(signed && shifts>0) {
|
||||
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||
} else {
|
||||
repeat(shifts) {
|
||||
asmgen.out(" lsr a")
|
||||
}
|
||||
}
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
} else if(dt.isWords && shifts in 0..7) {
|
||||
val signed = dt istype DataType.WORD
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
|
||||
if(expr.operator=="<<") {
|
||||
if(shifts>0) {
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||
repeat(shifts) {
|
||||
asmgen.out(" asl a | rol P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out(" ldy P8ZP_SCRATCH_B1")
|
||||
}
|
||||
} else {
|
||||
if(signed) {
|
||||
// TODO("shift AY >> $shifts signed")
|
||||
return false
|
||||
} else {
|
||||
if(shifts>0) {
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||
repeat(shifts) {
|
||||
asmgen.out(" lsr P8ZP_SCRATCH_B1 | ror a")
|
||||
}
|
||||
asmgen.out(" ldy P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -757,7 +814,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
require(elementDt.isBytes)
|
||||
require(elementDt.isBytes) { "must be byte string ${variable.position}" }
|
||||
val stringVal = variable.value as StringLiteral
|
||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
@ -773,7 +830,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
throw AssemblyError("containment check of floats not supported")
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
require(elementDt.isInteger)
|
||||
require(elementDt.isInteger) { "must be integer array ${variable.position}" }
|
||||
val arrayVal = variable.value as ArrayLiteral
|
||||
val dt = elementDt.getOr(DataType.UNDEFINED)
|
||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||
@ -888,11 +945,9 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
is NumericLiteral -> {
|
||||
val address = (value.addressExpression as NumericLiteral).number.toUInt()
|
||||
assignMemoryByteIntoWord(target, address, null)
|
||||
return
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
|
||||
return
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
|
||||
@ -906,6 +961,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
assignViaExprEval(value.addressExpression)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
|
||||
@ -914,7 +970,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
|
||||
|
||||
// special case optimizations
|
||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
||||
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
|
||||
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
||||
|
||||
@ -992,11 +1048,12 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
|
||||
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
|
||||
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
|
||||
return
|
||||
} else {
|
||||
if(!(valueDt isAssignableTo targetDt)) {
|
||||
if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
|
||||
return if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
|
||||
// word to byte, just take the lsb
|
||||
return assignCastViaLsbFunc(value, target)
|
||||
assignCastViaLsbFunc(value, target)
|
||||
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
|
||||
// word to word, just assign
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
@ -1006,43 +1063,85 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
|
||||
// byte to word, just assign
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
|
||||
}
|
||||
else
|
||||
} else
|
||||
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
|
||||
}
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if(targetDt in IntegerDatatypes && valueDt in IntegerDatatypes && valueDt.isAssignableTo(targetDt)) {
|
||||
require(targetDt in WordDatatypes && valueDt in ByteDatatypes) { "should be byte to word assignment ${origTypeCastExpression.position}"}
|
||||
when(target.kind) {
|
||||
// TargetStorageKind.VARIABLE -> {
|
||||
// This has been handled already earlier on line 961.
|
||||
// // byte to word, just assign to registers first, then assign to variable
|
||||
// assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
|
||||
// assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, targetDt)
|
||||
// return
|
||||
// }
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// byte to word, just assign to registers first, then assign into array
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
// byte to word, just assign to registers
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
|
||||
return
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
// byte to word, just assign to registers first, then push onto stack
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
|
||||
asmgen.out("""
|
||||
sta P8ESTACK_LO,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
return
|
||||
}
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
|
||||
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||
asmgen.out(" jsr floats.FREADUY")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, true)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||
asmgen.out(" jsr floats.FREADSA")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||
asmgen.out(" jsr floats.GIVUAYFAY")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||
asmgen.out(" jsr floats.GIVAYFAY")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
if(target.register==RegisterOrPair.FAC2) {
|
||||
asmgen.out(" jsr floats.MOVEF")
|
||||
}
|
||||
return
|
||||
} else {
|
||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -1199,14 +1298,10 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(
|
||||
" st${
|
||||
regs.toString().lowercase()
|
||||
} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(
|
||||
" st${
|
||||
regs.toString().lowercase()
|
||||
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when(regs) {
|
||||
@ -2004,7 +2099,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
// we make an exception in the type check for assigning something to a register pair AX, AY or XY
|
||||
// these will be correctly typecasted from a byte to a word value here
|
||||
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
|
||||
require(target.datatype in ByteDatatypes)
|
||||
require(target.datatype in ByteDatatypes) { "assign target must be byte type ${target.origAstTarget?.position ?: ""}"}
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -2093,7 +2188,9 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
|
||||
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
|
||||
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes) {
|
||||
"assign target must be word type ${target.origAstTarget?.position ?: ""}"
|
||||
}
|
||||
if(target.datatype==DataType.FLOAT)
|
||||
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
|
||||
|
||||
|
@ -16,14 +16,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
) {
|
||||
fun translate(assign: AsmAssignment) {
|
||||
require(assign.isAugmentable)
|
||||
require(assign.source.kind== SourceStorageKind.EXPRESSION)
|
||||
require(assign.source.kind == SourceStorageKind.EXPRESSION) {
|
||||
"non-expression assign value should be handled elsewhere ${assign.position}"
|
||||
}
|
||||
|
||||
when (val value = assign.source.expression!!) {
|
||||
is PrefixExpression -> {
|
||||
// A = -A , A = +A, A = ~A, A = not A
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(assign)
|
||||
"-" -> inplaceNegate(assign, false)
|
||||
"~" -> inplaceInvert(assign)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
@ -1871,9 +1873,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceNegate(assign: AsmAssignment) {
|
||||
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
|
||||
val target = assign.target
|
||||
when (assign.target.datatype) {
|
||||
val datatype = if(ignoreDatatype) {
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> DataType.BYTE
|
||||
DataType.UWORD, DataType.WORD -> DataType.WORD
|
||||
else -> target.datatype
|
||||
}
|
||||
} else target.datatype
|
||||
when (datatype) {
|
||||
DataType.BYTE -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
|
@ -258,7 +258,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
return result
|
||||
}
|
||||
else if(memory!=null) {
|
||||
require(vmDt== IRDataType.BYTE)
|
||||
require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
|
||||
if(zero) {
|
||||
if(memory.address is PtNumber) {
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
|
||||
|
@ -4,13 +4,18 @@ import prog8.code.StRomSub
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.PassByValueDatatypes
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.intermediate.*
|
||||
|
||||
|
||||
internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
fun translateExpression(expr: PtExpression, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
|
||||
require(codeGen.registers.peekNext() > resultRegister || resultRegister >= SyscallRegisterBase)
|
||||
require(codeGen.registers.peekNext() > resultRegister || resultRegister >= SyscallRegisterBase) {
|
||||
"no more registers for expression ${expr.position}"
|
||||
}
|
||||
|
||||
return when (expr) {
|
||||
is PtMachineRegister -> {
|
||||
@ -586,7 +591,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: IRDataType, resultRegister: Int): IRCodeChunks {
|
||||
require(vmDt!=IRDataType.FLOAT) {"floating-point modulo not supported"}
|
||||
require(vmDt!=IRDataType.FLOAT) {"floating-point modulo not supported ${binExpr.position}"}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val rightResultReg = codeGen.registers.nextFree()
|
||||
if(binExpr.right is PtNumber) {
|
||||
|
@ -75,36 +75,36 @@ class IRCodeGen(
|
||||
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
|
||||
|
||||
irProg.blocks.forEach { block ->
|
||||
if(block.inlineAssembly.isNotEmpty()) {
|
||||
val first = block.inlineAssembly.first()
|
||||
block.children.firstOrNull { it is IRInlineAsmChunk }?.let { first->
|
||||
first as IRInlineAsmChunk
|
||||
if(first.label==null) {
|
||||
val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next)
|
||||
block.inlineAssembly.removeAt(0)
|
||||
block.inlineAssembly.add(0, replacement)
|
||||
block.children.removeAt(0)
|
||||
block.children.add(0, replacement)
|
||||
} else if(first.label != block.name) {
|
||||
throw AssemblyError("first chunk in block has label that differs from block name")
|
||||
}
|
||||
}
|
||||
|
||||
block.subroutines.forEach { sub ->
|
||||
block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
|
||||
if(sub.chunks.isNotEmpty()) {
|
||||
val first = sub.chunks.first()
|
||||
if(first.label==null) {
|
||||
val replacement = when(first) {
|
||||
is IRCodeChunk -> {
|
||||
val replacement = IRCodeChunk(sub.name, first.next)
|
||||
val replacement = IRCodeChunk(sub.label, first.next)
|
||||
replacement.instructions += first.instructions
|
||||
replacement
|
||||
}
|
||||
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.name, first.assembly, first.isIR, first.next)
|
||||
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.name, first.data, first.next)
|
||||
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next)
|
||||
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.label, first.data, first.next)
|
||||
else -> throw AssemblyError("invalid chunk")
|
||||
}
|
||||
sub.chunks.removeAt(0)
|
||||
sub.chunks.add(0, replacement)
|
||||
} else if(first.label != sub.name) {
|
||||
} else if(first.label != sub.label) {
|
||||
val next = if(first is IRCodeChunk) first else null
|
||||
sub.chunks.add(0, IRCodeChunk(sub.name, next))
|
||||
sub.chunks.add(0, IRCodeChunk(sub.label, next))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,7 +116,7 @@ class IRCodeGen(
|
||||
// note: we do still export the memory mapped symbols so a code generator can use those
|
||||
// for instance when a piece of inlined assembly references them.
|
||||
val replacements = mutableListOf<Triple<IRCodeChunkBase, Int, UInt>>()
|
||||
irProg.blocks.asSequence().flatMap { it.subroutines }.flatMap { it.chunks }.forEach { chunk ->
|
||||
irProg.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }.forEach { chunk ->
|
||||
chunk.instructions.withIndex().forEach {
|
||||
(idx, instr) ->
|
||||
val symbolExpr = instr.labelSymbol
|
||||
@ -180,14 +180,12 @@ class IRCodeGen(
|
||||
private fun flattenNestedSubroutines() {
|
||||
// this moves all nested subroutines up to the block scope.
|
||||
// also changes the name to be the fully scoped one, so it becomes unique at the top level.
|
||||
// also moves the start() entrypoint as first subroutine.
|
||||
val flattenedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
||||
val flattenedAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
||||
val removalsSubs = mutableListOf<Pair<PtSub, PtSub>>()
|
||||
val removalsAsmSubs = mutableListOf<Pair<PtSub, PtAsmSub>>()
|
||||
val renameSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
||||
val renameAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
||||
val entrypoint = program.entrypoint()
|
||||
|
||||
fun flattenNestedAsmSub(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
|
||||
val flattened = PtAsmSub(asmsub.scopedName.joinToString("."),
|
||||
@ -236,17 +234,8 @@ class IRCodeGen(
|
||||
renameSubs.forEach { (parent, sub) ->
|
||||
val renamedSub = PtSub(sub.scopedName.joinToString("."), sub.parameters, sub.returntype, sub.inline, sub.position)
|
||||
sub.children.forEach { renamedSub.add(it) }
|
||||
parent.children.remove(sub)
|
||||
if (sub === entrypoint) {
|
||||
// entrypoint sub must be first sub
|
||||
val firstsub = parent.children.withIndex().firstOrNull() { it.value is PtSub || it.value is PtAsmSub }
|
||||
if(firstsub!=null)
|
||||
parent.add(firstsub.index, renamedSub)
|
||||
else
|
||||
parent.add(renamedSub)
|
||||
} else {
|
||||
parent.add(renamedSub)
|
||||
}
|
||||
val subindex = parent.children.indexOf(sub)
|
||||
parent.children[subindex] = renamedSub // keep the order of nodes the same
|
||||
}
|
||||
renameAsmSubs.forEach { (parent, sub) ->
|
||||
val renamedSub = PtAsmSub(sub.scopedName.joinToString("."),
|
||||
@ -260,8 +249,8 @@ class IRCodeGen(
|
||||
|
||||
if(sub.children.isNotEmpty())
|
||||
renamedSub.add(sub.children.single())
|
||||
parent.children.remove(sub)
|
||||
parent.add(renamedSub)
|
||||
val subindex = parent.children.indexOf(sub)
|
||||
parent.children[subindex] = renamedSub // keep the order of nodes the same
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,12 +280,7 @@ class IRCodeGen(
|
||||
}
|
||||
is PtConditionalBranch -> translate(node)
|
||||
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
|
||||
is PtIncludeBinary -> {
|
||||
val data = node.file.readBytes()
|
||||
.drop(node.offset?.toInt() ?: 0)
|
||||
.take(node.length?.toInt() ?: Int.MAX_VALUE)
|
||||
listOf(IRInlineBinaryChunk(null, data.map { it.toUByte() }, null))
|
||||
}
|
||||
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
|
||||
is PtAddressOf,
|
||||
is PtContainmentCheck,
|
||||
is PtMemoryByte,
|
||||
@ -327,8 +311,50 @@ class IRCodeGen(
|
||||
return chunks
|
||||
}
|
||||
|
||||
private fun readBinaryData(node: PtIncludeBinary): Collection<UByte> {
|
||||
return node.file.readBytes()
|
||||
.drop(node.offset?.toInt() ?: 0)
|
||||
.take(node.length?.toInt() ?: Int.MAX_VALUE)
|
||||
.map { it.toUByte() }
|
||||
}
|
||||
|
||||
private fun translate(branch: PtConditionalBranch): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
val goto = branch.trueScope.children.firstOrNull() as? PtJump
|
||||
if(goto is PtJump && branch.falseScope.children.isEmpty()) {
|
||||
// special case the form: if_cc <condition> goto <place>
|
||||
val address = goto.address?.toInt()
|
||||
if(address!=null) {
|
||||
val branchIns = when(branch.condition) {
|
||||
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, value = address)
|
||||
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, value = address)
|
||||
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, value = address)
|
||||
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, value = address)
|
||||
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, value = address)
|
||||
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, value = address)
|
||||
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, value = address)
|
||||
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, value = address)
|
||||
}
|
||||
addInstr(result, branchIns, null)
|
||||
} else {
|
||||
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.targetName.joinToString(".")
|
||||
val branchIns = when(branch.condition) {
|
||||
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
|
||||
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
|
||||
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, labelSymbol = label)
|
||||
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, labelSymbol = label)
|
||||
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, labelSymbol = label)
|
||||
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, labelSymbol = label)
|
||||
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = label)
|
||||
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = label)
|
||||
}
|
||||
addInstr(result, branchIns, null)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
val elseLabel = createLabelName()
|
||||
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
|
||||
val branchIns = when(branch.condition) {
|
||||
@ -338,8 +364,8 @@ class IRCodeGen(
|
||||
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
|
||||
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
|
||||
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
|
||||
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
|
||||
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
|
||||
BranchCondition.VC -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
|
||||
BranchCondition.VS -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
|
||||
}
|
||||
addInstr(result, branchIns, null)
|
||||
result += translateNode(branch.trueScope)
|
||||
@ -877,6 +903,32 @@ class IRCodeGen(
|
||||
val signed = ifElse.condition.left.type in arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val irDt = irType(ifElse.condition.left.type)
|
||||
|
||||
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
|
||||
if(goto!=null && ifElse.elseScope.children.isEmpty()) {
|
||||
// special case the form: if <condition> goto <place>
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val leftReg = registers.nextFree()
|
||||
val rightReg = registers.nextFree()
|
||||
result += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
||||
result += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
|
||||
val opcode = when(ifElse.condition.operator) {
|
||||
"==" -> Opcode.BEQ
|
||||
"!=" -> Opcode.BNE
|
||||
"<" -> Opcode.BLT
|
||||
">" -> Opcode.BGT
|
||||
"<=" -> Opcode.BLE
|
||||
">=" -> Opcode.BGE
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
if(goto.address!=null)
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, value = goto.address?.toInt()), null)
|
||||
else if(goto.generatedLabel!=null)
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.identifier!!.targetName.joinToString(".")), null)
|
||||
return result
|
||||
}
|
||||
|
||||
fun translateNonZeroComparison(): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val elseBranch = when(ifElse.condition.operator) {
|
||||
@ -1095,7 +1147,9 @@ class IRCodeGen(
|
||||
is PtAsmSub -> {
|
||||
if(child.address!=null) {
|
||||
// romsub. No codegen needed: calls to this are jumping straight to the address.
|
||||
require(child.children.isEmpty())
|
||||
require(child.children.isEmpty()) {
|
||||
"romsub should be empty at ${child.position}"
|
||||
}
|
||||
} else {
|
||||
// regular asmsub
|
||||
val assemblyChild = child.children.single() as PtInlineAssembly
|
||||
@ -1106,8 +1160,8 @@ class IRCodeGen(
|
||||
child.name,
|
||||
child.address,
|
||||
child.clobbers,
|
||||
child.parameters.map { Pair(it.first.type, it.second) }, // note: the name of the asmsub param is not used anymore.
|
||||
child.returnTypes.zip(child.retvalRegisters),
|
||||
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.second, it.first.type) }, // note: the name of the asmsub param is not used anymore.
|
||||
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) },
|
||||
asmChunk,
|
||||
child.position
|
||||
)
|
||||
@ -1116,6 +1170,12 @@ class IRCodeGen(
|
||||
is PtInlineAssembly -> {
|
||||
irBlock += IRInlineAsmChunk(null, child.assembly, child.isIR, null)
|
||||
}
|
||||
is PtIncludeBinary -> {
|
||||
irBlock += IRInlineBinaryChunk(null, readBinaryData(child), null)
|
||||
}
|
||||
is PtLabel -> {
|
||||
irBlock += IRCodeChunk(child.name, null)
|
||||
}
|
||||
else -> TODO("weird child node $child")
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import prog8.intermediate.*
|
||||
|
||||
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
fun optimize() {
|
||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
removeEmptyChunks(sub)
|
||||
joinChunks(sub)
|
||||
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||
|
@ -7,15 +7,23 @@ import prog8.intermediate.*
|
||||
|
||||
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
||||
fun optimize(): Int {
|
||||
var numRemoved = removeSimpleUnlinked() + removeUnreachable()
|
||||
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
|
||||
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
sub.chunks.forEach { chunk ->
|
||||
chunk.label?.let { allLabeledChunks[it] = chunk }
|
||||
}
|
||||
}
|
||||
|
||||
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
|
||||
|
||||
// remove empty subs
|
||||
irprog.blocks.forEach { block ->
|
||||
block.subroutines.reversed().forEach { sub ->
|
||||
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
|
||||
if(sub.isEmpty()) {
|
||||
if(!sub.position.file.startsWith(libraryFilePrefix))
|
||||
errors.warn("unused subroutine ${sub.name}", sub.position)
|
||||
block.subroutines.remove(sub)
|
||||
errors.warn("unused subroutine ${sub.label}", sub.position)
|
||||
block.children.remove(sub)
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
@ -32,14 +40,20 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
||||
return numRemoved
|
||||
}
|
||||
|
||||
private fun removeUnreachable(): Int {
|
||||
val reachable = mutableSetOf(irprog.blocks.single { it.name=="main" }.subroutines.single { it.name=="main.start" }.chunks.first())
|
||||
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||
val entrypointSub = irprog.blocks.single { it.name=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||
|
||||
fun grow() {
|
||||
val new = mutableSetOf<IRCodeChunkBase>()
|
||||
reachable.forEach {
|
||||
it.next?.let { next -> new += next }
|
||||
it.instructions.forEach { instr -> instr.branchTarget?.let { target -> new += target} }
|
||||
it.instructions.forEach { instr ->
|
||||
if (instr.branchTarget == null)
|
||||
instr.labelSymbol?.let { label -> allLabeledChunks[label]?.let { chunk -> new += chunk } }
|
||||
else
|
||||
new += instr.branchTarget!!
|
||||
}
|
||||
}
|
||||
reachable += new
|
||||
}
|
||||
@ -55,13 +69,19 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
||||
return removeUnlinkedChunks(reachable)
|
||||
}
|
||||
|
||||
private fun removeSimpleUnlinked(): Int {
|
||||
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
|
||||
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
||||
|
||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
sub.chunks.forEach { chunk ->
|
||||
chunk.next?.let { next -> linkedChunks += next }
|
||||
chunk.instructions.forEach { it.branchTarget?.let { target -> linkedChunks += target } }
|
||||
chunk.instructions.forEach {
|
||||
if(it.branchTarget==null) {
|
||||
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
|
||||
} else {
|
||||
linkedChunks += it.branchTarget!!
|
||||
}
|
||||
}
|
||||
if (chunk.label == "main.start")
|
||||
linkedChunks += chunk
|
||||
}
|
||||
@ -74,7 +94,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
||||
linkedChunks: MutableSet<IRCodeChunkBase>
|
||||
): Int {
|
||||
var numRemoved = 0
|
||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||
if (chunk !in linkedChunks) {
|
||||
if (chunk === sub.chunks[0]) {
|
||||
|
@ -20,7 +20,6 @@ class VmCodeGen(private val program: PtProgram,
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||
val irProgram = irCodeGen.generate()
|
||||
|
||||
// no need to check options.keepIR, as the VM file format *is* the IR file.
|
||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
return makeIRProgram(listOf(chunk))
|
||||
}
|
||||
|
||||
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }
|
||||
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
|
||||
|
||||
test("remove nops") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
|
@ -193,11 +193,15 @@ class ExpressionSimplifier(private val program: Program,
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
else if(expr.operator == ">" && rightVal?.number == 0.0) {
|
||||
// unsigned > 0 --> unsigned != 0
|
||||
return listOf(IAstModification.SetExpression({expr.operator="!="}, expr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
|
||||
@ -206,11 +210,15 @@ class ExpressionSimplifier(private val program: Program,
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
else if(expr.operator == "<=" && rightVal?.number == 0.0) {
|
||||
// unsigned <= 0 --> unsigned==0
|
||||
return listOf(IAstModification.SetExpression({expr.operator="=="}, expr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
// boolvar & 1 --> boolvar
|
||||
@ -592,7 +600,7 @@ class ExpressionSimplifier(private val program: Program,
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
DataType.UWORD -> {
|
||||
if (amount >= 16) {
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
@ -609,6 +617,25 @@ class ExpressionSimplifier(private val program: Program,
|
||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if (amount >= 16) {
|
||||
errors.warn("shift always results in 0", expr.position)
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
}
|
||||
else if(amount==8) {
|
||||
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(DataType.UBYTE, 0.0, expr.position)), expr.position)
|
||||
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
|
||||
}
|
||||
else if (amount > 8) {
|
||||
// same as above but with residual shifts.
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
||||
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +174,9 @@ class Inliner(val program: Program): AstWalker() {
|
||||
|
||||
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
|
||||
if(sub.inline && sub.parameters.isEmpty()) {
|
||||
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1])))
|
||||
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
|
||||
"invalid inline sub at ${sub.position}"
|
||||
}
|
||||
return if(sub.isAsmSubroutine) {
|
||||
// simply insert the asm for the argument-less routine
|
||||
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
|
||||
@ -208,7 +210,9 @@ class Inliner(val program: Program): AstWalker() {
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
|
||||
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
|
||||
require(sub.statements.size==1 || (sub.statements.size==2 && isEmptyReturn(sub.statements[1])))
|
||||
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
|
||||
"invalid inline sub at ${sub.position}"
|
||||
}
|
||||
return if(sub.isAsmSubroutine) {
|
||||
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
|
||||
noModifications
|
||||
|
@ -357,51 +357,17 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// word = msb(word) , word=lsb(word)
|
||||
// word = lsb(word)
|
||||
if(assignment.target.inferType(program).isWords) {
|
||||
var fcall = assignment.value as? FunctionCallExpression
|
||||
if (fcall == null)
|
||||
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
|
||||
if (fcall != null && (fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))) {
|
||||
if (fcall != null && (fcall.target.nameInSource == listOf("lsb"))) {
|
||||
if (fcall.args.single() isSameAs assignment.target) {
|
||||
return if (fcall.target.nameInSource == listOf("lsb")) {
|
||||
// optimize word=lsb(word) ==> word &= $00ff
|
||||
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
|
||||
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
|
||||
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
||||
} else {
|
||||
// optimize word=msb(word) ==> word >>= 8
|
||||
val shift8 = BinaryExpression(fcall.args[0], ">>", NumericLiteral(DataType.UBYTE, 8.0, fcall.position), fcall.position)
|
||||
val newAssign = Assignment(assignment.target, shift8, AssignmentOrigin.OPTIMIZER, fcall.position)
|
||||
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(compTarget.name==VMTarget.NAME)
|
||||
return noModifications
|
||||
|
||||
val returnvalue = returnStmt.value
|
||||
if (returnvalue!=null) {
|
||||
val dt = returnvalue.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(dt!=DataType.UNDEFINED) {
|
||||
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
|
||||
// first assign to intermediary variable, then return that
|
||||
val (returnVarName, _) = program.getTempVar(dt)
|
||||
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, returnvalue, AssignmentOrigin.OPTIMIZER, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||
)
|
||||
// optimize word=lsb(word) ==> word &= $00ff
|
||||
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
|
||||
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
|
||||
return listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
||||
testImplementation project(':intermediate')
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
||||
}
|
||||
|
||||
|
@ -23,5 +23,6 @@
|
||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="module" module-name="intermediate" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
@ -12,9 +12,34 @@ c64 {
|
||||
|
||||
&ubyte COLOR = $00f1 ; cursor color
|
||||
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
|
||||
|
||||
&uword IERROR = $0300
|
||||
&uword IMAIN = $0302
|
||||
&uword ICRNCH = $0304
|
||||
&uword IQPLOP = $0306
|
||||
&uword IGONE = $0308
|
||||
&uword IEVAL = $030a
|
||||
&uword ICRNCH2 = $030c
|
||||
&uword IQPLOP2 = $030e
|
||||
&uword IGONE2 = $0310
|
||||
; $0312 and $0313 are unused.
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword IOPEN = $031a
|
||||
&uword ICLOSE = $031c
|
||||
&uword ICHKIN = $031e
|
||||
&uword ICKOUT = $0320
|
||||
&uword ICLRCH = $0322
|
||||
&uword IBASIN = $0324
|
||||
&uword IBSOUT = $0326
|
||||
&uword ISTOP = $0328
|
||||
&uword IGETIN = $032a
|
||||
&uword ICLALL = $032c
|
||||
&uword IEXMON = $032e
|
||||
&uword ILOAD = $0330
|
||||
&uword ISAVE = $0332
|
||||
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
@ -11,9 +11,36 @@ c64 {
|
||||
|
||||
&ubyte COLOR = $0286 ; cursor color
|
||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
|
||||
&uword IERROR = $0300
|
||||
&uword IMAIN = $0302
|
||||
&uword ICRNCH = $0304
|
||||
&uword IQPLOP = $0306
|
||||
&uword IGONE = $0308
|
||||
&uword IEVAL = $030a
|
||||
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||
; $0313 is unused.
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword IOPEN = $031a
|
||||
&uword ICLOSE = $031c
|
||||
&uword ICHKIN = $031e
|
||||
&uword ICKOUT = $0320
|
||||
&uword ICLRCH = $0322
|
||||
&uword IBASIN = $0324
|
||||
&uword IBSOUT = $0326
|
||||
&uword ISTOP = $0328
|
||||
&uword IGETIN = $032a
|
||||
&uword ICLALL = $032c
|
||||
&uword USERCMD = $032e
|
||||
&uword ILOAD = $0330
|
||||
&uword ISAVE = $0332
|
||||
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
@ -1,6 +1,7 @@
|
||||
; Cx16 specific disk drive I/O routines.
|
||||
|
||||
%import diskio
|
||||
%import string
|
||||
|
||||
cx16diskio {
|
||||
|
||||
@ -34,8 +35,8 @@ cx16diskio {
|
||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||
}
|
||||
|
||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",device,bank,address
|
||||
asmsub vload(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",drivenumber,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file has to have the usual 2 byte header (which will be skipped)
|
||||
%asm {{
|
||||
@ -76,8 +77,8 @@ internal_vload:
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vload_raw(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command BVLOAD "filename",device,bank,address
|
||||
asmsub vload_raw(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file is read fully including the first two bytes.
|
||||
%asm {{
|
||||
@ -95,15 +96,6 @@ internal_vload:
|
||||
return 0
|
||||
|
||||
diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
if diskio.have_first_byte {
|
||||
diskio.have_first_byte=false
|
||||
@(bufferpointer) = diskio.first_byte
|
||||
bufferpointer++
|
||||
diskio.list_blocks++
|
||||
num_bytes--
|
||||
}
|
||||
|
||||
void c64.CHKIN(11) ; use #11 as input channel again
|
||||
|
||||
; commander X16 supports fast block-read via macptr() kernal call
|
||||
uword size
|
||||
@ -133,29 +125,23 @@ byte_read_loop: ; fallback if macptr() isn't supported on the device
|
||||
lda bufferpointer+1
|
||||
sta m_in_buffer+2
|
||||
}}
|
||||
repeat num_bytes {
|
||||
while num_bytes {
|
||||
if c64.READST() {
|
||||
diskio.f_close()
|
||||
if c64.READST() & $40 ; eof?
|
||||
return diskio.list_blocks ; number of bytes read
|
||||
return 0 ; error.
|
||||
}
|
||||
%asm {{
|
||||
jsr c64.CHRIN
|
||||
sta cx16.r5
|
||||
m_in_buffer sta $ffff
|
||||
inc m_in_buffer+1
|
||||
bne +
|
||||
inc m_in_buffer+2
|
||||
+ inc diskio.list_blocks
|
||||
bne +
|
||||
inc diskio.list_blocks+1
|
||||
+
|
||||
}}
|
||||
|
||||
if cx16.r5==$0d { ; chance on I/o error status?
|
||||
diskio.first_byte = c64.READST()
|
||||
if diskio.first_byte & $40 {
|
||||
diskio.f_close() ; end of file, close it
|
||||
diskio.list_blocks-- ; don't count that last CHRIN read
|
||||
}
|
||||
if diskio.first_byte
|
||||
return diskio.list_blocks ; number of bytes read
|
||||
}
|
||||
diskio.list_blocks++
|
||||
num_bytes--
|
||||
}
|
||||
return diskio.list_blocks ; number of bytes read
|
||||
}
|
||||
@ -168,19 +154,78 @@ m_in_buffer sta $ffff
|
||||
return 0
|
||||
|
||||
uword total_read = 0
|
||||
if diskio.have_first_byte {
|
||||
diskio.have_first_byte=false
|
||||
@(bufferpointer) = diskio.first_byte
|
||||
bufferpointer++
|
||||
total_read = 1
|
||||
}
|
||||
|
||||
while not c64.READST() {
|
||||
uword size = cx16diskio.f_read(bufferpointer, 256)
|
||||
total_read += size
|
||||
bufferpointer += size
|
||||
cx16.r0 = cx16diskio.f_read(bufferpointer, 256)
|
||||
total_read += cx16.r0
|
||||
bufferpointer += cx16.r0
|
||||
}
|
||||
return total_read
|
||||
}
|
||||
|
||||
|
||||
sub chdir(ubyte drivenumber, str path) {
|
||||
; -- change current directory.
|
||||
diskio.list_filename[0] = 'c'
|
||||
diskio.list_filename[1] = 'd'
|
||||
diskio.list_filename[2] = ':'
|
||||
void string.copy(path, &diskio.list_filename+3)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub mkdir(ubyte drivenumber, str name) {
|
||||
; -- make a new subdirectory.
|
||||
diskio.list_filename[0] = 'm'
|
||||
diskio.list_filename[1] = 'd'
|
||||
diskio.list_filename[2] = ':'
|
||||
void string.copy(name, &diskio.list_filename+3)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub rmdir(ubyte drivenumber, str name) {
|
||||
; -- remove a subdirectory.
|
||||
void string.find(name, '*')
|
||||
if_cs
|
||||
return ; refuse to act on a wildcard *
|
||||
diskio.list_filename[0] = 'r'
|
||||
diskio.list_filename[1] = 'd'
|
||||
diskio.list_filename[2] = ':'
|
||||
void string.copy(name, &diskio.list_filename+3)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub relabel(ubyte drivenumber, str name) {
|
||||
; -- change the disk label.
|
||||
diskio.list_filename[0] = 'r'
|
||||
diskio.list_filename[1] = '-'
|
||||
diskio.list_filename[2] = 'h'
|
||||
diskio.list_filename[3] = ':'
|
||||
void string.copy(name, &diskio.list_filename+4)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||
ubyte[6] command = ['p',0,0,0,0,0]
|
||||
command[1] = 12 ; f_open uses channel 12
|
||||
command[2] = lsb(pos_loword)
|
||||
command[3] = msb(pos_loword)
|
||||
command[4] = lsb(pos_hiword)
|
||||
command[5] = msb(pos_hiword)
|
||||
send_command:
|
||||
c64.SETNAM(sizeof(command), &command)
|
||||
c64.SETLFS(15, diskio.last_drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLOSE(15)
|
||||
}
|
||||
|
||||
; TODO see if we can get this to work as well:
|
||||
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
|
||||
; cx16diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
|
||||
; cx16diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
; cx16diskio.f_seek.command[3] = msb(pos_loword)
|
||||
; cx16diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
; cx16diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
; goto cx16diskio.f_seek.send_command
|
||||
; }
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!]
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
|
||||
@ -68,18 +68,19 @@ romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
|
||||
; X16 additions
|
||||
romsub $fe81 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||
romsub $fe84 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||
romsub $fe87 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||
romsub $fe8a = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
||||
romsub $fe8d = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe90 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe93 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe96 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe99 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
|
||||
romsub $fe9c = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
||||
romsub $fe9f = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
|
||||
romsub $fea2 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
||||
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
|
||||
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
||||
romsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
|
||||
romsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
|
||||
|
||||
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||
|
@ -82,9 +82,10 @@ gfx2 {
|
||||
bpp = 2
|
||||
}
|
||||
else -> {
|
||||
; back to default text mode and colors
|
||||
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
|
||||
c64.CINT() ; back to text mode
|
||||
; back to default text mode
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
c64.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
width = 0
|
||||
height = 0
|
||||
bpp = 0
|
||||
@ -918,7 +919,7 @@ _done
|
||||
y++
|
||||
%asm {{
|
||||
phx
|
||||
ldx #1
|
||||
ldx color
|
||||
lda cx16.VERA_DATA1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #8
|
||||
|
@ -85,6 +85,18 @@ asmsub RDTIM16() -> uword @AY {
|
||||
cx16 {
|
||||
|
||||
; irq, system and hardware vectors:
|
||||
&uword IERROR = $0300
|
||||
&uword IMAIN = $0302
|
||||
&uword ICRNCH = $0304
|
||||
&uword IQPLOP = $0306
|
||||
&uword IGONE = $0308
|
||||
&uword IEVAL = $030a
|
||||
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||
; $0313 is unused.
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
@ -290,8 +302,9 @@ cx16 {
|
||||
&ubyte d2ier = via2+14
|
||||
&ubyte d2ora = via2+15
|
||||
|
||||
&ubyte ym2151adr = $9f40
|
||||
&ubyte ym2151dat = $9f41
|
||||
; YM-2151 sound chip
|
||||
&ubyte YM_ADDRESS = $9f40
|
||||
&ubyte YM_DATA = $9f41
|
||||
|
||||
const uword extdev = $9f60
|
||||
|
||||
@ -612,12 +625,22 @@ asmsub init_system() {
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
lda VERA_DC_VIDEO
|
||||
and #%00000111 ; retain chroma + output mode
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda #$80
|
||||
sta VERA_CTRL
|
||||
sta VERA_CTRL ; reset vera
|
||||
stz $01 ; select rom bank 0 (enable kernal)
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
lda VERA_DC_VIDEO
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta VERA_DC_VIDEO ; keep old output mode
|
||||
ldy #0
|
||||
clc
|
||||
jsr c64.PLOT ; force a call to PLOT to avoid autostart black square issue, also see textio.fix_autostart_square()
|
||||
lda #$90 ; black
|
||||
jsr c64.CHROUT
|
||||
lda #1 ; swap fg/bg
|
||||
|
@ -21,6 +21,26 @@ sub home() {
|
||||
txt.chrout(19)
|
||||
}
|
||||
|
||||
asmsub fix_autostart_square() {
|
||||
; Here's a possible work around for weird issue that prints a black character after first call to c64.PLOT()
|
||||
; if you're also using c64.CINT() yourself. The default prog8 program initialization (which calls CINT) already performs this workaround.
|
||||
; The problem occurs when a program is autostarded in the emulator with -run -prg test.prg,
|
||||
; or when the program is saved as AUTOBOOT.X16 and loaded on boot like that.
|
||||
%asm {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.PLOT
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
lda #' '
|
||||
jsr c64.CHROUT ; overwrite the black square
|
||||
clc
|
||||
jsr c64.PLOT ; cursor back to original position
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
@ -14,9 +14,10 @@ cx16logo {
|
||||
|
||||
sub logo() {
|
||||
uword strptr
|
||||
for strptr in logo_lines
|
||||
for strptr in logo_lines {
|
||||
txt.print(strptr)
|
||||
txt.nl()
|
||||
txt.nl()
|
||||
}
|
||||
}
|
||||
|
||||
str[] logo_lines = [
|
||||
|
@ -10,12 +10,12 @@ diskio {
|
||||
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(13, drivenumber, 0)
|
||||
c64.SETLFS(12, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void c64.OPEN() ; open 13,8,0,"$"
|
||||
void c64.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(13) ; use #13 as input channel
|
||||
void c64.CHKIN(12) ; use #12 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@ -53,7 +53,7 @@ diskio {
|
||||
|
||||
io_error:
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(13)
|
||||
c64.CLOSE(12)
|
||||
|
||||
if status and status & $40 == 0 { ; bit 6=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
@ -69,12 +69,12 @@ io_error:
|
||||
; -- Returns pointer to disk name string or 0 if failure.
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(13, drivenumber, 0)
|
||||
c64.SETLFS(12, drivenumber, 0)
|
||||
ubyte okay = false
|
||||
void c64.OPEN() ; open 13,8,0,"$"
|
||||
void c64.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(13) ; use #13 as input channel
|
||||
void c64.CHKIN(12) ; use #12 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@ -96,7 +96,7 @@ io_error:
|
||||
|
||||
io_error:
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(13)
|
||||
c64.CLOSE(12)
|
||||
if okay
|
||||
return &list_filename
|
||||
return 0
|
||||
@ -107,33 +107,37 @@ io_error:
|
||||
uword list_pattern
|
||||
uword list_blocks
|
||||
bool iteration_in_progress = false
|
||||
ubyte @zp first_byte
|
||||
bool have_first_byte
|
||||
ubyte last_drivenumber = 8 ; which drive was last used for a f_open operation?
|
||||
str list_filetype = "???" ; prg, seq, dir
|
||||
str list_filename = "?" * 50
|
||||
|
||||
; ----- get a list of files (uses iteration functions internally) -----
|
||||
|
||||
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
||||
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. Returns number of files.
|
||||
const uword filenames_buf_size = 800
|
||||
uword filenames_buffer = memory("filenames", filenames_buf_size, 0)
|
||||
sub list_filenames(ubyte drivenumber, uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
|
||||
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||
uword buffer_start = filenames_buffer
|
||||
ubyte files_found = 0
|
||||
if lf_start_list(drivenumber, pattern_ptr) {
|
||||
while lf_next_entry() {
|
||||
@(name_ptrs) = lsb(filenames_buffer)
|
||||
name_ptrs++
|
||||
@(name_ptrs) = msb(filenames_buffer)
|
||||
name_ptrs++
|
||||
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
||||
files_found++
|
||||
if filenames_buffer - buffer_start > filenames_buf_size-18
|
||||
break
|
||||
if files_found == max_names
|
||||
break
|
||||
if list_filetype!="dir" {
|
||||
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
||||
files_found++
|
||||
if filenames_buffer - buffer_start > filenames_buf_size-20 {
|
||||
@(filenames_buffer)=0
|
||||
lf_end_list()
|
||||
sys.set_carry()
|
||||
return files_found
|
||||
}
|
||||
}
|
||||
}
|
||||
lf_end_list()
|
||||
}
|
||||
@(filenames_buffer)=0
|
||||
sys.clear_carry()
|
||||
return files_found
|
||||
}
|
||||
|
||||
@ -170,7 +174,7 @@ io_error:
|
||||
|
||||
sub lf_next_entry() -> bool {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks and list_filename.
|
||||
; results will be found in list_blocks, list_filename, and list_filetype.
|
||||
; if it returns false though, there are no more entries (or an error occurred).
|
||||
|
||||
if not iteration_in_progress
|
||||
@ -207,6 +211,12 @@ io_error:
|
||||
|
||||
@(nameptr) = 0
|
||||
|
||||
do {
|
||||
cx16.r15L = c64.CHRIN()
|
||||
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
|
||||
list_filetype[0] = cx16.r15L
|
||||
list_filetype[1] = c64.CHRIN()
|
||||
list_filetype[2] = c64.CHRIN()
|
||||
while c64.CHRIN() {
|
||||
; read the rest of the entry until the end
|
||||
}
|
||||
@ -238,7 +248,7 @@ close_end:
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file loader functions (uses io channel 11) -----
|
||||
; ----- iterative file loader functions (uses io channel 12) -----
|
||||
|
||||
sub f_open(ubyte drivenumber, uword filenameptr) -> bool {
|
||||
; -- open a file for iterative reading with f_read
|
||||
@ -246,17 +256,19 @@ close_end:
|
||||
f_close()
|
||||
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(11, drivenumber, 0)
|
||||
void c64.OPEN() ; open 11,8,0,"filename"
|
||||
c64.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
|
||||
last_drivenumber = drivenumber
|
||||
void c64.OPEN() ; open 12,8,12,"filename"
|
||||
if_cc {
|
||||
if c64.READST()==0 {
|
||||
iteration_in_progress = true
|
||||
have_first_byte = false
|
||||
void c64.CHKIN(11) ; use #11 as input channel
|
||||
void c64.CHKIN(12) ; use #12 as input channel
|
||||
if_cc {
|
||||
first_byte = c64.CHRIN() ; read first byte to test for file not found
|
||||
void c64.CHRIN() ; read first byte to test for file not found
|
||||
if not c64.READST() {
|
||||
have_first_byte = true
|
||||
c64.CLOSE(12) ; close file because we already consumed first byte
|
||||
void c64.OPEN() ; re-open the file
|
||||
void c64.CHKIN(12)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -275,15 +287,6 @@ close_end:
|
||||
return 0
|
||||
|
||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
if have_first_byte {
|
||||
have_first_byte=false
|
||||
@(bufferpointer) = first_byte
|
||||
bufferpointer++
|
||||
list_blocks++
|
||||
num_bytes--
|
||||
}
|
||||
|
||||
void c64.CHKIN(11) ; use #11 as input channel again
|
||||
|
||||
%asm {{
|
||||
lda bufferpointer
|
||||
@ -291,29 +294,23 @@ close_end:
|
||||
lda bufferpointer+1
|
||||
sta m_in_buffer+2
|
||||
}}
|
||||
repeat num_bytes {
|
||||
while num_bytes {
|
||||
if c64.READST() {
|
||||
f_close()
|
||||
if c64.READST() & $40 ; eof?
|
||||
return list_blocks ; number of bytes read
|
||||
return 0 ; error.
|
||||
}
|
||||
%asm {{
|
||||
jsr c64.CHRIN
|
||||
sta cx16.r5
|
||||
m_in_buffer sta $ffff
|
||||
inc m_in_buffer+1
|
||||
bne +
|
||||
inc m_in_buffer+2
|
||||
+ inc list_blocks
|
||||
bne +
|
||||
inc list_blocks+1
|
||||
+
|
||||
}}
|
||||
|
||||
if cx16.r5==$0d { ; chance on I/o error status?
|
||||
first_byte = c64.READST()
|
||||
if first_byte & $40 {
|
||||
f_close() ; end of file, close it
|
||||
list_blocks-- ; don't count that last CHRIN read
|
||||
}
|
||||
if first_byte
|
||||
return list_blocks ; number of bytes read
|
||||
}
|
||||
list_blocks++
|
||||
num_bytes--
|
||||
}
|
||||
return list_blocks ; number of bytes read
|
||||
}
|
||||
@ -324,17 +321,10 @@ m_in_buffer sta $ffff
|
||||
return 0
|
||||
|
||||
uword total_read = 0
|
||||
if have_first_byte {
|
||||
have_first_byte=false
|
||||
@(bufferpointer) = first_byte
|
||||
bufferpointer++
|
||||
total_read = 1
|
||||
}
|
||||
|
||||
while not c64.READST() {
|
||||
uword size = f_read(bufferpointer, 256)
|
||||
total_read += size
|
||||
bufferpointer += size
|
||||
cx16.r0 = f_read(bufferpointer, 256)
|
||||
total_read += cx16.r0
|
||||
bufferpointer += cx16.r0
|
||||
}
|
||||
return total_read
|
||||
}
|
||||
@ -348,16 +338,9 @@ m_in_buffer sta $ffff
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx #11
|
||||
jsr c64.CHKIN ; use channel 11 again for input
|
||||
ldx #12
|
||||
jsr c64.CHKIN ; use channel 12 again for input
|
||||
ldy #0
|
||||
lda have_first_byte
|
||||
beq _loop
|
||||
lda #0
|
||||
sta have_first_byte
|
||||
lda first_byte
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
_loop jsr c64.CHRIN
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
beq _end
|
||||
@ -378,23 +361,23 @@ _end rts
|
||||
; -- end an iterative file loading session (close channels).
|
||||
if iteration_in_progress {
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(11)
|
||||
c64.CLOSE(12)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file saver functions (uses io channel 14) -----
|
||||
; ----- iterative file writing functions (uses io channel 13) -----
|
||||
|
||||
sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool {
|
||||
; -- open a file for iterative writing with f_write
|
||||
f_close_w()
|
||||
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(14, drivenumber, 1)
|
||||
void c64.OPEN() ; open 14,8,1,"filename"
|
||||
c64.SETLFS(13, drivenumber, 1)
|
||||
void c64.OPEN() ; open 13,8,1,"filename"
|
||||
if_cc {
|
||||
void c64.CHKOUT(14) ; use #14 as input channel
|
||||
c64.CHKOUT(13) ; use #13 as output channel
|
||||
return not c64.READST()
|
||||
}
|
||||
f_close_w()
|
||||
@ -402,9 +385,9 @@ _end rts
|
||||
}
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given umber of bytes to the currently open file
|
||||
; -- write the given number of bytes to the currently open file
|
||||
if num_bytes!=0 {
|
||||
void c64.CHKOUT(14) ; use #14 as input channel again
|
||||
c64.CHKOUT(13) ; use #13 as output channel again
|
||||
repeat num_bytes {
|
||||
c64.CHROUT(@(bufferpointer))
|
||||
bufferpointer++
|
||||
@ -417,7 +400,7 @@ _end rts
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(14)
|
||||
c64.CLOSE(13)
|
||||
}
|
||||
|
||||
|
||||
@ -436,10 +419,10 @@ _end rts
|
||||
goto io_error
|
||||
|
||||
while not c64.READST() {
|
||||
first_byte = c64.CHRIN()
|
||||
if first_byte=='\r' or first_byte=='\n'
|
||||
cx16.r5L = c64.CHRIN()
|
||||
if cx16.r5L=='\r' or cx16.r5L=='\n'
|
||||
break
|
||||
@(messageptr) = first_byte
|
||||
@(messageptr) = cx16.r5L
|
||||
messageptr++
|
||||
}
|
||||
@(messageptr) = 0
|
||||
@ -458,7 +441,7 @@ io_error:
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword @shared end_address = address + size
|
||||
first_byte = 0 ; result var reuse
|
||||
cx16.r0L = 0
|
||||
|
||||
%asm {{
|
||||
lda address
|
||||
@ -476,12 +459,12 @@ io_error:
|
||||
}}
|
||||
|
||||
if_cc
|
||||
first_byte = c64.READST()==0
|
||||
cx16.r0L = c64.READST()==0
|
||||
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
return first_byte
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
|
@ -1082,6 +1082,7 @@ containment_wordarray .proc
|
||||
iny
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
beq _found
|
||||
dey
|
||||
+ dey
|
||||
dey
|
||||
cpy #254
|
||||
|
@ -1 +1 @@
|
||||
8.7
|
||||
8.8
|
||||
|
@ -41,10 +41,9 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||
val keepIR by cli.option(ArgType.Boolean, fullName = "keepIR", description = "keep the IR code file (for targets that use it)")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)")
|
||||
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental)")
|
||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||
@ -127,7 +126,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
keepIR == true,
|
||||
compilationTarget,
|
||||
evalStackAddr,
|
||||
processedSymbols,
|
||||
@ -192,7 +190,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
keepIR == true,
|
||||
compilationTarget,
|
||||
evalStackAddr,
|
||||
processedSymbols,
|
||||
|
@ -38,7 +38,6 @@ class CompilerArguments(val filepath: Path,
|
||||
val quietAssembler: Boolean,
|
||||
val asmListfile: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val keepIR: Boolean,
|
||||
val compilationTarget: String,
|
||||
val evalStackBaseAddress: UInt?,
|
||||
val symbolDefs: Map<String, String>,
|
||||
@ -81,7 +80,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
asmQuiet = args.quietAssembler
|
||||
asmListfile = args.asmListfile
|
||||
experimentalCodegen = args.experimentalCodegen
|
||||
keepIR = args.keepIR
|
||||
evalStackBaseAddress = args.evalStackBaseAddress
|
||||
outputDir = args.outputDir.normalize()
|
||||
symbolDefs = args.symbolDefs
|
||||
@ -95,7 +93,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
|
||||
processAst(program, args.errors, compilationOptions)
|
||||
if (compilationOptions.optimize) {
|
||||
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
|
||||
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
|
||||
// printProgram(program)
|
||||
|
||||
optimizeAst(
|
||||
@ -108,7 +106,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
}
|
||||
postprocessAst(program, args.errors, compilationOptions)
|
||||
|
||||
// println("*********** AST BEFORE ASSEMBLYGEN *************")
|
||||
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
|
||||
// printProgram(program)
|
||||
|
||||
determineProgramLoadAddress(program, compilationOptions, args.errors)
|
||||
@ -382,7 +380,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
program.verifyFunctionArgTypes(errors)
|
||||
errors.report()
|
||||
program.moveMainAndStartToFirst()
|
||||
program.moveMainBlockAsFirst()
|
||||
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||
errors.report()
|
||||
}
|
||||
@ -391,7 +389,7 @@ private fun createAssemblyAndAssemble(program: Program,
|
||||
errors: IErrorReporter,
|
||||
compilerOptions: CompilationOptions
|
||||
): Boolean {
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
|
||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||
errors.report()
|
||||
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
|
||||
@ -402,7 +400,7 @@ private fun createAssemblyAndAssemble(program: Program,
|
||||
// to help clean up the code that still depends on them.
|
||||
// removeAllVardeclsFromAst(program)
|
||||
|
||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printProgram(program)
|
||||
|
||||
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
|
||||
|
@ -0,0 +1,49 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
class AsmInstructionNamesFinder(val target: ICompilationTarget): IAstVisitor {
|
||||
|
||||
val blocks = mutableSetOf<Block>()
|
||||
val variables = mutableSetOf<VarDecl>()
|
||||
val labels = mutableSetOf<Label>()
|
||||
val subroutines = mutableSetOf<Subroutine>()
|
||||
|
||||
private fun isPossibleInstructionName(name: String) = name.length==3 && name.all { it.isLetter() }
|
||||
|
||||
fun foundAny(): Boolean = blocks.isNotEmpty() || variables.isNotEmpty() || subroutines.isNotEmpty() || labels.isNotEmpty()
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(target.name!=VMTarget.NAME && !block.isInLibrary && isPossibleInstructionName(block.name)) {
|
||||
blocks += block
|
||||
}
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if(target.name!=VMTarget.NAME && !decl.definingModule.isLibrary && isPossibleInstructionName(decl.name)) {
|
||||
variables += decl
|
||||
}
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
override fun visit(label: Label) {
|
||||
if(target.name!=VMTarget.NAME && !label.definingModule.isLibrary && isPossibleInstructionName(label.name)) {
|
||||
labels += label
|
||||
}
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(target.name!=VMTarget.NAME && !subroutine.definingModule.isLibrary && isPossibleInstructionName(subroutine.name)) {
|
||||
subroutines += subroutine
|
||||
}
|
||||
super.visit(subroutine)
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
|
||||
class AsmInstructionNamesReplacer(
|
||||
val blocks: Set<Block>,
|
||||
val subroutines: Set<Subroutine>,
|
||||
val variables: Set<VarDecl>,
|
||||
val labels: Set<Label>): AstWalker() {
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
val newName = identifier.nameInSource.map { ident ->
|
||||
if(ident.length==3 && !identifier.definingModule.isLibrary) {
|
||||
val blockTarget = blocks.firstOrNull { it.name==ident }
|
||||
val subTarget = subroutines.firstOrNull {it.name==ident }
|
||||
val varTarget = variables.firstOrNull { it.name==ident }
|
||||
val labelTarget = labels.firstOrNull { it.name==ident}
|
||||
|
||||
if(blockTarget!=null || subTarget!=null || varTarget!=null || labelTarget!=null) {
|
||||
"p8p_$ident"
|
||||
} else
|
||||
ident
|
||||
} else
|
||||
ident
|
||||
}
|
||||
|
||||
return if(newName!=identifier.nameInSource)
|
||||
listOf(IAstModification.ReplaceNode(identifier, IdentifierReference(newName, identifier.position), parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(label: Label, parent: Node): Iterable<IAstModification> {
|
||||
return if(label in labels)
|
||||
listOf(IAstModification.ReplaceNode(label, Label("p8p_${label.name}", label.position), parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
return if(block in blocks)
|
||||
listOf(IAstModification.ReplaceNode(block, Block("p8p_${block.name}", block.address, block.statements, block.isInLibrary, block.position), parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
return if(decl in variables)
|
||||
listOf(IAstModification.ReplaceNode(decl, decl.renamed("p8p_${decl.name}"), parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
private val subsWithParamRefsToFix = mutableListOf<Subroutine>()
|
||||
|
||||
override fun applyModifications(): Int {
|
||||
var count = super.applyModifications()
|
||||
subsWithParamRefsToFix.forEach { subroutine ->
|
||||
subroutine.statements.withIndex().reversed().forEach { (index,stmt) ->
|
||||
if(stmt is VarDecl && stmt.origin==VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
val param = subroutine.parameters.single { it.name == stmt.name}
|
||||
val decl = VarDecl.fromParameter(param)
|
||||
subroutine.statements[index] = decl
|
||||
decl.linkParents(subroutine)
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val changedParams = mutableListOf<Pair<Int, SubroutineParameter>>()
|
||||
subroutine.parameters.withIndex().forEach { (index, param) ->
|
||||
if(param.name.length==3 && param.name.all { it.isLetter() } && !param.definingModule.isLibrary) {
|
||||
changedParams.add(index to SubroutineParameter("p8p_${param.name}", param.type, param.position))
|
||||
}
|
||||
}
|
||||
|
||||
changedParams.forEach { (index, newParam) -> subroutine.parameters[index] = newParam }
|
||||
val newName = if(subroutine in subroutines) "p8p_${subroutine.name}" else subroutine.name
|
||||
|
||||
return if(newName!=subroutine.name || changedParams.isNotEmpty()) {
|
||||
val newSub = Subroutine(newName, subroutine.parameters, subroutine.returntypes,
|
||||
subroutine.asmParameterRegisters, subroutine.asmReturnvaluesRegisters, subroutine.asmClobbers, subroutine.asmAddress, subroutine.isAsmSubroutine,
|
||||
subroutine.inline, subroutine.statements, subroutine.position)
|
||||
if(changedParams.isNotEmpty())
|
||||
subsWithParamRefsToFix += newSub
|
||||
listOf(IAstModification.ReplaceNode(subroutine, newSub, parent))
|
||||
} else {
|
||||
if(changedParams.isNotEmpty())
|
||||
subsWithParamRefsToFix += subroutine
|
||||
noModifications
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -251,7 +251,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
if(inlineAssembly.hasReturnOrRts(compilerOptions.compTarget))
|
||||
if(inlineAssembly.hasReturnOrRts())
|
||||
count++
|
||||
}
|
||||
}
|
||||
@ -509,7 +509,7 @@ internal class AstChecker(private val program: Program,
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCallExpression)
|
||||
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
|
||||
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
|
||||
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
|
||||
@ -843,8 +843,15 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown) {
|
||||
// check if maybe one of the operands is a label, this would need a '&'
|
||||
if (!leftIDt.isKnown && expr.left !is FunctionCallExpression)
|
||||
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.left.position)
|
||||
if (!rightIDt.isKnown && expr.right !is FunctionCallExpression)
|
||||
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.right.position)
|
||||
|
||||
return // hopefully this error will be detected elsewhere
|
||||
}
|
||||
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||
@ -1028,6 +1035,23 @@ internal class AstChecker(private val program: Program,
|
||||
if(targetStatement!=null) {
|
||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
|
||||
|
||||
if(functionCallStatement.void) {
|
||||
when(targetStatement) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
if(!builtinFunctionReturnType(targetStatement.name).isKnown)
|
||||
errors.warn("redundant void", functionCallStatement.position)
|
||||
}
|
||||
is Label -> {
|
||||
errors.warn("redundant void", functionCallStatement.position)
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(targetStatement.returntypes.isEmpty())
|
||||
errors.warn("redundant void", functionCallStatement.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val funcName = functionCallStatement.target.nameInSource
|
||||
|
@ -12,6 +12,7 @@ import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
@ -26,6 +27,21 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
|
||||
val boolRemover = BoolRemover(this)
|
||||
boolRemover.visit(this)
|
||||
boolRemover.applyModifications()
|
||||
|
||||
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
|
||||
val finder = AsmInstructionNamesFinder(compilerOptions.compTarget)
|
||||
finder.visit(this)
|
||||
if(finder.foundAny()) {
|
||||
val replacer = AsmInstructionNamesReplacer(
|
||||
finder.blocks,
|
||||
finder.subroutines,
|
||||
finder.variables,
|
||||
finder.labels)
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
}
|
||||
}
|
||||
|
||||
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
|
||||
fixer.visit(this)
|
||||
while (errors.noErrors() && fixer.applyModifications() > 0) {
|
||||
@ -129,35 +145,19 @@ internal fun Program.variousCleanups(errors: IErrorReporter, options: Compilatio
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.moveMainAndStartToFirst() {
|
||||
internal fun Program.moveMainBlockAsFirst() {
|
||||
// The module containing the program entrypoint is moved to the first in the sequence.
|
||||
// the "main" block containing the entrypoint is moved to the top in there,
|
||||
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
||||
// the "main" block containing the entrypoint is moved to the top in there.
|
||||
|
||||
// sortModules()
|
||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||
val start = this.entrypoint
|
||||
val mod = start.definingModule
|
||||
val block = start.definingBlock
|
||||
moveModuleToFront(mod)
|
||||
mod.remove(block)
|
||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||
val module = this.entrypoint.definingModule
|
||||
val block = this.entrypoint.definingBlock
|
||||
moveModuleToFront(module)
|
||||
module.remove(block)
|
||||
val afterDirective = module.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
mod.statements.add(block)
|
||||
module.statements.add(block)
|
||||
else
|
||||
mod.statements.add(afterDirective, block)
|
||||
block.remove(start)
|
||||
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
block.statements.add(start)
|
||||
else
|
||||
block.statements.add(afterDirective, start)
|
||||
|
||||
// overwrite the directives in the module containing the entrypoint
|
||||
for(directive in directives) {
|
||||
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||
modules[0].statements.add(0, directive)
|
||||
}
|
||||
module.statements.add(afterDirective, block)
|
||||
}
|
||||
|
||||
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
||||
@ -168,9 +168,9 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
|
||||
return false
|
||||
}
|
||||
|
||||
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
|
||||
internal fun Subroutine.hasRtsInAsm(): Boolean {
|
||||
return statements
|
||||
.asSequence()
|
||||
.filterIsInstance<InlineAssembly>()
|
||||
.any { it.hasReturnOrRts(compTarget) }
|
||||
.any { it.hasReturnOrRts() }
|
||||
}
|
@ -10,6 +10,7 @@ import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
|
||||
|
||||
@ -28,9 +29,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(block.name in compTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null) {
|
||||
if(block.isInLibrary)
|
||||
@ -50,9 +48,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
if(decl.name in BuiltinFunctions)
|
||||
errors.err("builtin function cannot be redefined", decl.position)
|
||||
|
||||
if(decl.name in compTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||
|
||||
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
|
||||
if(existingInSameScope!=null && existingInSameScope!==decl)
|
||||
nameError(decl.name, decl.position, existingInSameScope)
|
||||
@ -74,9 +69,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(subroutine.name in compTarget.machine.opcodeNames) {
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||
} else if(subroutine.name in BuiltinFunctions) {
|
||||
if(subroutine.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
errors.err("builtin function cannot be redefined", subroutine.position)
|
||||
} else {
|
||||
@ -86,7 +79,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
|
||||
val existing = subroutine.lookup(listOf(subroutine.name))
|
||||
if (existing != null && existing !== subroutine) {
|
||||
if(existing.parent!==existing.parent)
|
||||
if(existing.parent!==subroutine.parent && existing is Subroutine)
|
||||
nameShadowWarning(subroutine.name, existing.position, subroutine)
|
||||
else
|
||||
nameError(subroutine.name, existing.position, subroutine)
|
||||
@ -107,10 +100,12 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||
}
|
||||
|
||||
if(subroutine.name == subroutine.definingBlock.name) {
|
||||
// subroutines cannot have the same name as their enclosing block,
|
||||
// because this causes symbol scoping issues in the resulting assembly source
|
||||
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
|
||||
if(compTarget.name != VMTarget.NAME) {
|
||||
if (subroutine.name == subroutine.definingBlock.name) {
|
||||
// subroutines cannot have the same name as their enclosing block,
|
||||
// because this causes symbol scoping issues in the resulting assembly source
|
||||
errors.err("name conflict '${subroutine.name}', also defined at ${subroutine.definingBlock.position} (64tass scope nesting limitation)", subroutine.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,11 +113,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
override fun visit(label: Label) {
|
||||
if(label.name in compTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||
|
||||
if(label.name in BuiltinFunctions) {
|
||||
// the builtin functions can't be redefined
|
||||
errors.err("builtin function cannot be redefined", label.position)
|
||||
} else {
|
||||
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
|
||||
|
@ -37,11 +37,6 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
}
|
||||
|
||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
// move all subroutines to the bottom of the block
|
||||
val subs = block.statements.filterIsInstance<Subroutine>()
|
||||
block.statements.removeAll(subs)
|
||||
block.statements.addAll(subs)
|
||||
|
||||
// adjust global variables initialization
|
||||
if(options.dontReinitGlobals) {
|
||||
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
||||
@ -142,7 +137,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
} else {
|
||||
val last = subroutine.statements.last()
|
||||
if((last !is InlineAssembly || !last.hasReturnOrRts(options.compTarget)) && last !is Return) {
|
||||
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
|
||||
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
|
||||
if(lastStatement !is Return) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
@ -169,7 +164,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
}
|
||||
|
||||
if (!subroutine.inline || !options.optimize) {
|
||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) {
|
||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm()) {
|
||||
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
||||
// (non-asm routines get a Return statement as needed, above)
|
||||
mods += if(options.compTarget.name==VMTarget.NAME)
|
||||
|
@ -45,6 +45,21 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
||||
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator == "not") {
|
||||
|
||||
// first check if we're already part of a "boolean" expresion (i.e. comparing against 0)
|
||||
// if so, simplify THAT whole expression rather than making it more complicated
|
||||
if(parent is BinaryExpression && parent.right.constValue(program)?.number==0.0) {
|
||||
if(parent.operator=="==") {
|
||||
// (NOT X)==0 --> X!=0
|
||||
val replacement = BinaryExpression(expr.expression, "!=", NumericLiteral.optimalInteger(0, expr.position), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
|
||||
} else if(parent.operator=="!=") {
|
||||
// (NOT X)!=0 --> X==0
|
||||
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
|
||||
}
|
||||
}
|
||||
|
||||
// not(not(x)) -> x
|
||||
if((expr.expression as? PrefixExpression)?.operator=="not")
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||
|
@ -314,9 +314,7 @@ internal class StatementReorderer(val program: Program,
|
||||
AddressOf(sourceIdent, assign.position),
|
||||
AddressOf(identifier, assign.position),
|
||||
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
|
||||
),
|
||||
true,
|
||||
assign.position
|
||||
), false, assign.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||
}
|
||||
@ -328,7 +326,7 @@ internal class StatementReorderer(val program: Program,
|
||||
assign.value as? IdentifierReference ?: assign.value,
|
||||
identifier
|
||||
),
|
||||
true,
|
||||
false,
|
||||
assign.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
||||
|
@ -62,6 +62,8 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {
|
||||
var initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||
if(initialNumeric==0.0)
|
||||
initialNumeric=null // variable will go into BSS and this will be set to 0
|
||||
val initialStringLit = decl.value as? StringLiteral
|
||||
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||
val initialArrayLit = decl.value as? ArrayLiteral
|
||||
@ -79,10 +81,8 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
false
|
||||
else if(decl.isArray)
|
||||
initialArray.isNullOrEmpty()
|
||||
else {
|
||||
if(dontReinitGlobals) initialNumeric = initialNumeric ?: 0.0
|
||||
else
|
||||
initialNumeric == null
|
||||
}
|
||||
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
||||
}
|
||||
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
|
||||
|
@ -47,6 +47,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
val rightCv = expr.right.constValue(program)
|
||||
|
||||
if(leftDt.isKnown && rightDt.isKnown) {
|
||||
|
||||
if(expr.operator=="<<" && leftDt.isBytes) {
|
||||
// uword ww = 1 << shift --> make the '1' a word constant
|
||||
val leftConst = expr.left.constValue(program)
|
||||
if(leftConst!=null) {
|
||||
val leftConstAsWord =
|
||||
if(leftDt.istype(DataType.UBYTE))
|
||||
NumericLiteral(DataType.UWORD, leftConst.number, leftConst.position)
|
||||
else
|
||||
NumericLiteral(DataType.WORD, leftConst.number, leftConst.position)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if (parent is Assignment) {
|
||||
if (parent.target.inferType(program).isWords) {
|
||||
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
|
||||
if(rightDt.isBytes)
|
||||
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
|
||||
}
|
||||
} else if (parent is TypecastExpression && parent.type == DataType.UWORD && parent.parent is Assignment) {
|
||||
val assign = parent.parent as Assignment
|
||||
if (assign.target.inferType(program).isWords) {
|
||||
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
|
||||
if(rightDt.isBytes)
|
||||
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
|
||||
}
|
||||
}
|
||||
if(modifications.isNotEmpty())
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||
// see if any of the operands needs conversion to bool
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
@ -8,6 +8,8 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.ConditionalBranch
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
@ -219,5 +221,22 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> {
|
||||
if(branch.truepart.isEmpty() && branch.elsepart.isEmpty()) {
|
||||
errors.warn("removing empty conditional branch", branch.position)
|
||||
return listOf(IAstModification.Remove(branch, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty()) {
|
||||
errors.warn("removing empty if-else statement", ifElse.position)
|
||||
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,8 +52,8 @@ class TestAstChecks: FunSpec({
|
||||
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.warnings.size shouldBe 0
|
||||
errors.errors[0] shouldContain ":7:28: assignment value is invalid"
|
||||
errors.errors[1] shouldContain ":8:28: assignment value is invalid"
|
||||
errors.errors[0] shouldContain ":7:28: invalid assignment value, maybe forgot '&'"
|
||||
errors.errors[1] shouldContain ":8:28: invalid assignment value, maybe forgot '&'"
|
||||
}
|
||||
|
||||
test("can't do str or array expression without using address-of") {
|
||||
|
@ -33,7 +33,6 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
quietAssembler = true,
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
keepIR = false,
|
||||
compilationTarget = target.name,
|
||||
evalStackBaseAddress = null,
|
||||
symbolDefs = emptyMap(),
|
||||
@ -99,14 +98,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"circles",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"cube3d",
|
||||
"datetime",
|
||||
"diskspeed",
|
||||
"fileseek",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"keyboardhandler",
|
||||
"mandelbrot",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"rasterbars",
|
||||
"sincos",
|
||||
"snow",
|
||||
"tehtriz",
|
||||
"testgfx2",
|
||||
),
|
||||
@ -184,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
val (displayName, filepath) = prepareTestFiles(it, false, target)
|
||||
test(displayName) {
|
||||
val src = filepath.readText()
|
||||
compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null
|
||||
compileText(target, false, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -40,7 +40,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
strLits[0].value shouldBe "main.bar"
|
||||
strLits[1].value shouldBe "foo.bar"
|
||||
strLits[0].definingScope.name shouldBe "main"
|
||||
strLits[1].definingScope.name shouldBe "foo"
|
||||
strLits[1].definingScope.name shouldBe "foobar"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,6 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
quietAssembler = true,
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
keepIR = false,
|
||||
compilationTarget = Cx16Target.NAME,
|
||||
evalStackBaseAddress = null,
|
||||
symbolDefs = emptyMap(),
|
||||
|
61
compiler/test/TestGoldenRam.kt
Normal file
61
compiler/test/TestGoldenRam.kt
Normal file
@ -0,0 +1,61 @@
|
||||
package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.expectError
|
||||
import com.github.michaelbull.result.getOrThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
|
||||
class TestGoldenRam: FunSpec({
|
||||
|
||||
val options = CompilationOptions(
|
||||
OutputType.RAW,
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x00u..0xffu)),
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = VMTarget(),
|
||||
loadAddress = 999u
|
||||
)
|
||||
|
||||
test("empty golden ram allocations") {
|
||||
val errors = ErrorReporterForTests()
|
||||
val golden = GoldenRam(options, UIntRange.EMPTY)
|
||||
val result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||
result.expectError { "should not be able to allocate anything" }
|
||||
}
|
||||
|
||||
test("regular golden ram allocations") {
|
||||
val errors = ErrorReporterForTests()
|
||||
val golden = GoldenRam(options, 0x400u until 0x800u)
|
||||
|
||||
var result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||
var alloc = result.getOrThrow()
|
||||
alloc.size shouldBe 1
|
||||
alloc.address shouldBe 0x400u
|
||||
result = golden.allocate(listOf("test"), DataType.STR, 100, null, errors)
|
||||
alloc = result.getOrThrow()
|
||||
alloc.size shouldBe 100
|
||||
alloc.address shouldBe 0x401u
|
||||
|
||||
repeat(461) {
|
||||
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
|
||||
alloc = result.getOrThrow()
|
||||
alloc.size shouldBe 2
|
||||
}
|
||||
|
||||
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
|
||||
result.expectError { "just 1 more byte available" }
|
||||
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||
alloc = result.getOrThrow()
|
||||
alloc.size shouldBe 1
|
||||
alloc.address shouldBe golden.region.last
|
||||
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||
result.expectError { "nothing more available" }
|
||||
|
||||
}
|
||||
})
|
@ -11,13 +11,16 @@ class TestLaunchEmu: FunSpec({
|
||||
test("test launch virtualmachine via target") {
|
||||
val target = VMTarget()
|
||||
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
|
||||
tmpfile.writeText("""<PROGRAM NAME=test>
|
||||
tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
|
||||
<PROGRAM NAME="test">
|
||||
<OPTIONS>
|
||||
</OPTIONS>
|
||||
|
||||
<ASMSYMBOLS>
|
||||
</ASMSYMBOLS>
|
||||
|
||||
<BSS>
|
||||
</BSS>
|
||||
<VARIABLES>
|
||||
</VARIABLES>
|
||||
|
||||
@ -30,7 +33,7 @@ class TestLaunchEmu: FunSpec({
|
||||
<INITGLOBALS>
|
||||
</INITGLOBALS>
|
||||
|
||||
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]>
|
||||
<BLOCK NAME="main" ADDRESS="null" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
|
||||
</BLOCK>
|
||||
</PROGRAM>
|
||||
""")
|
||||
|
@ -124,7 +124,7 @@ class TestSubroutines: FunSpec({
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.statements.single() shouldBe instanceOf<InlineAssembly>()
|
||||
(asmfunc.statements.single() as InlineAssembly).assembly.trim() shouldBe "rts"
|
||||
asmfunc.hasRtsInAsm(C64Target()) shouldBe true
|
||||
asmfunc.hasRtsInAsm() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("str param should have been changed to uword") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
|
@ -14,6 +14,7 @@ import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
@ -934,4 +935,34 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("various floating point casts don't crash the compiler") {
|
||||
val text="""
|
||||
%import floats
|
||||
|
||||
main {
|
||||
sub score() -> ubyte {
|
||||
cx16.r15++
|
||||
return 5
|
||||
}
|
||||
|
||||
sub start() {
|
||||
float @shared total = 0
|
||||
ubyte bb = 5
|
||||
|
||||
cx16.r0 = 5
|
||||
total += cx16.r0 as float
|
||||
total += score() as float
|
||||
uword ww = 5
|
||||
total += ww as float
|
||||
total += bb as float
|
||||
float result = score() as float
|
||||
total += result
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), true, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
@ -188,7 +188,7 @@ class TestC64Zeropage: FunSpec({
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
|
||||
val loc = result.getOrElse { throw it } .first
|
||||
val loc = result.getOrElse { throw it } .address
|
||||
loc shouldBeGreaterThan 3u
|
||||
loc shouldNotBeIn zp.free
|
||||
val num = zp.availableBytes() / 2
|
||||
@ -216,18 +216,18 @@ class TestC64Zeropage: FunSpec({
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 18
|
||||
zp.allocate(emptyList(), DataType.WORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x04u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x06u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0au
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9bu
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9eu
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xa5u
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xb0u
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xbeu
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0eu
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x92u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x96u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0xf9u
|
||||
zp.allocate(emptyList(), DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
|
@ -888,9 +888,11 @@ class TestProg8Parser: FunSpec( {
|
||||
bool bb
|
||||
ubyte cc
|
||||
if cc in [' ', '@', 0] {
|
||||
cx16.r0L++
|
||||
}
|
||||
|
||||
if cc in "email" {
|
||||
cx16.r0L++
|
||||
}
|
||||
|
||||
bb = 99 in array
|
||||
|
@ -3,13 +3,19 @@ package prog8tests.ast
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.printProgram
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestVarious: FunSpec({
|
||||
@ -129,5 +135,31 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("bitshift left of const byte converted to word") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte shift = 10
|
||||
uword value = 1<<shift
|
||||
value++
|
||||
value = 1<<shift
|
||||
value++
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=false, src, writeAssembly=false)!!
|
||||
printProgram(result.program)
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 7
|
||||
val assign1expr = (stmts[3] as Assignment).value as BinaryExpression
|
||||
val assign2expr = (stmts[5] as Assignment).value as BinaryExpression
|
||||
assign1expr.operator shouldBe "<<"
|
||||
val leftval1 = assign1expr.left.constValue(result.program)!!
|
||||
leftval1.type shouldBe DataType.UWORD
|
||||
leftval1.number shouldBe 1.0
|
||||
val leftval2 = assign2expr.left.constValue(result.program)!!
|
||||
leftval2.type shouldBe DataType.UWORD
|
||||
leftval2.number shouldBe 1.0
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -11,6 +11,7 @@ import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.code.target.c64.C64Zeropage
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.compiler.astprocessing.SymbolTableMaker
|
||||
@ -173,4 +174,48 @@ main {
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)
|
||||
result shouldNotBe null
|
||||
}
|
||||
|
||||
"identifiers can have the names of cpu instructions" {
|
||||
val text="""
|
||||
%import textio
|
||||
|
||||
nop {
|
||||
sub lda(ubyte sec) -> ubyte {
|
||||
asl:
|
||||
ubyte brk = sec
|
||||
sec++
|
||||
brk += sec
|
||||
return brk
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
|
||||
sub ffalse(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 0
|
||||
}
|
||||
sub ftrue(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 128
|
||||
}
|
||||
|
||||
sub start() {
|
||||
ubyte col = 10
|
||||
ubyte row = 20
|
||||
txt.print_ub(nop.lda(42))
|
||||
txt.nl()
|
||||
txt.print_uw(nop.lda.asl)
|
||||
|
||||
void ffalse(99)
|
||||
void ftrue(99)
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)
|
||||
result shouldNotBe null
|
||||
val result2 = compileText(VMTarget(), false, text, writeAssembly = true)
|
||||
result2 shouldNotBe null
|
||||
|
||||
}
|
||||
})
|
||||
|
@ -63,5 +63,21 @@ class TestVariables: FunSpec({
|
||||
"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("negation of unsigned via casts") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
cx16.r0L = -(cx16.r0L as byte) as ubyte
|
||||
cx16.r0 = -(cx16.r0 as word) as uword
|
||||
ubyte ub
|
||||
uword uw
|
||||
ub = -(ub as byte) as ubyte
|
||||
uw = -(uw as word) as uword
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
})
|
||||
|
2
compiler/test/fixtures/foo_bar.asm
vendored
2
compiler/test/fixtures/foo_bar.asm
vendored
@ -1,2 +1,2 @@
|
||||
bar .text "foo.bar",0
|
||||
barbar .text "foo.bar",0
|
||||
|
||||
|
4
compiler/test/fixtures/foo_bar.p8
vendored
4
compiler/test/fixtures/foo_bar.p8
vendored
@ -1,3 +1,3 @@
|
||||
foo {
|
||||
str bar = "foo.bar"
|
||||
foobar {
|
||||
str barbar = "foo.bar"
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ main {
|
||||
str myBar = "main.bar"
|
||||
sub start() {
|
||||
txt.print(myBar)
|
||||
txt.print(foo.bar)
|
||||
txt.print(foobar.barbar)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ internal fun compileFile(
|
||||
outputDir: Path = prog8tests.helpers.outputDir,
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
keepIR: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult? {
|
||||
val filepath = fileDir.resolve(fileName)
|
||||
@ -32,7 +31,6 @@ internal fun compileFile(
|
||||
quietAssembler = true,
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
keepIR = keepIR,
|
||||
platform.name,
|
||||
evalStackBaseAddress = null,
|
||||
symbolDefs = emptyMap(),
|
||||
@ -53,12 +51,11 @@ internal fun compileText(
|
||||
sourceText: String,
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
keepIR: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult? {
|
||||
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
||||
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||
filePath.toFile().writeText(sourceText)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name,
|
||||
errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr, keepIR=keepIR)
|
||||
errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
|
||||
}
|
||||
|
@ -10,6 +10,9 @@ import prog8.ast.statements.Assignment
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.intermediate.IRFileReader
|
||||
import prog8.intermediate.IRSubroutine
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.vm.VmRunner
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.io.path.readText
|
||||
@ -50,7 +53,7 @@ main {
|
||||
}
|
||||
}"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, true, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
@ -162,7 +165,7 @@ skipLABEL:
|
||||
}
|
||||
}"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, true, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
@ -205,7 +208,7 @@ main {
|
||||
}
|
||||
}"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
|
||||
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||
@ -232,7 +235,7 @@ main {
|
||||
}
|
||||
}"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
|
||||
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||
@ -242,4 +245,86 @@ main {
|
||||
}
|
||||
exc.message shouldContain("does not support non-IR asmsubs")
|
||||
}
|
||||
|
||||
test("addresses from labels/subroutines not yet supported in VM") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
mylabel:
|
||||
ubyte variable
|
||||
uword @shared pointer1 = &main.start
|
||||
uword @shared pointer2 = &start
|
||||
uword @shared pointer3 = &main.start.mylabel
|
||||
uword @shared pointer4 = &mylabel
|
||||
uword[] @shared ptrs = [&variable, &start, &main.start, &mylabel, &main.start.mylabel]
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
|
||||
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
val exc = shouldThrow<Exception> {
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
}
|
||||
exc.message shouldContain("cannot yet load a label address as a value")
|
||||
}
|
||||
|
||||
test("nesting with overlapping names is ok (doesn't work for 64tass)") {
|
||||
val src="""
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
main()
|
||||
main.start.start()
|
||||
main.main()
|
||||
|
||||
sub main() {
|
||||
cx16.r0++
|
||||
}
|
||||
sub start() {
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
|
||||
sub main() {
|
||||
cx16.r0++
|
||||
}
|
||||
}"""
|
||||
|
||||
val target = VMTarget()
|
||||
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("compile virtual: short code for if-goto") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
if_cc
|
||||
goto ending
|
||||
if_cs
|
||||
goto ending
|
||||
if cx16.r0 goto ending
|
||||
if cx16.r0==0 goto ending
|
||||
if cx16.r0!=0 goto ending
|
||||
if cx16.r0s>0 goto ending
|
||||
if cx16.r0s<0 goto ending
|
||||
ending:
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), true, src, writeAssembly = true)!!
|
||||
result.program.entrypoint.statements.size shouldBe 9
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||
val irProgram = IRFileReader().read(virtfile)
|
||||
val start = irProgram.blocks[0].children[0] as IRSubroutine
|
||||
val instructions = start.chunks.flatMap { c->c.instructions }
|
||||
instructions.size shouldBe 18
|
||||
instructions.last().opcode shouldBe Opcode.RETURN
|
||||
}
|
||||
})
|
@ -7,7 +7,6 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
interface INamedStatement {
|
||||
@ -268,6 +267,13 @@ class VarDecl(val type: VarDeclType,
|
||||
return copy
|
||||
}
|
||||
|
||||
fun renamed(newName: String): VarDecl {
|
||||
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize, newName, value,
|
||||
isArray, sharedWithAsm, subroutineParameter, position)
|
||||
copy.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
return copy
|
||||
}
|
||||
|
||||
fun findInitializer(program: Program): Assignment? =
|
||||
(parent as IStatementContainer).statements
|
||||
.asSequence()
|
||||
@ -630,12 +636,12 @@ class InlineAssembly(val assembly: String, val isIR: Boolean, override val posit
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
fun hasReturnOrRts(target: ICompilationTarget): Boolean {
|
||||
return if(target.name!= VMTarget.NAME) {
|
||||
fun hasReturnOrRts(): Boolean {
|
||||
return if(isIR) {
|
||||
" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly || " jumpa" in assembly || "\tjumpa" in assembly
|
||||
} else {
|
||||
" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
|
||||
} else {
|
||||
" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly || " jumpa" in assembly || "\tjumpa" in assembly
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,7 @@ abstract class AstWalker {
|
||||
}
|
||||
}
|
||||
|
||||
fun applyModifications(): Int {
|
||||
open fun applyModifications(): Int {
|
||||
// check if there are double removes, keep only the last one
|
||||
val removals = modifications.filter { it.first is IAstModification.Remove }
|
||||
if(removals.isNotEmpty()) {
|
||||
|
@ -140,8 +140,8 @@ One or more .p8 module files
|
||||
Don't create code to reinitialize the global (block level) variables on every run of the program.
|
||||
Also means that all such variables are no longer placed in the zeropage.
|
||||
Sometimes the program will be a lot shorter when using this, but sometimes the opposite happens.
|
||||
When using this option, it is no longer be possible to run the program correctly more than once!
|
||||
*Experimental feature*: still has some problems!
|
||||
When using this option, it may no longer be possible to run the program correctly more than once!
|
||||
*Experimental feature*: this feature has not been tested much yet.
|
||||
|
||||
``-optfloatx``
|
||||
Also optimize float expressions if optimizations are enabled.
|
||||
@ -166,9 +166,6 @@ One or more .p8 module files
|
||||
``-asmlist``
|
||||
Generate an assembler listing file as well.
|
||||
|
||||
``-keepIR``
|
||||
Keep the IR code in a file (for targets that use it).
|
||||
|
||||
``-expericodegen``
|
||||
Use experimental code generation backend (*incomplete*).
|
||||
|
||||
|
@ -70,7 +70,7 @@ Language features
|
||||
- Variable data types include signed and unsigned bytes and words, arrays, strings.
|
||||
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
|
||||
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
|
||||
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
|
||||
- High-level code optimizations, such as const-folding (zero-allocation constants that are optimized away in expressions), expression and statement simplifications/rewriting.
|
||||
- Many built-in functions, such as ``sin``, ``cos``, ``abs``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``sort`` and ``reverse``
|
||||
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.
|
||||
|
@ -364,6 +364,7 @@ diskio module, to deal with loading of potentially large files in to banked ram
|
||||
Routines to directly load data into video ram are also present (vload and vload_raw).
|
||||
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
|
||||
to 16 bits, 64Kb)
|
||||
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir) and to relabel the disk.
|
||||
|
||||
|
||||
psg (cx16 only)
|
||||
|
@ -400,9 +400,9 @@ library function instead)
|
||||
Special types: const and memory-mapped
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When using ``const``, the value of the 'variable' can no longer be changed.
|
||||
When using ``const``, the value of the 'variable' cannot be changed; it has become a compile-time constant value instead.
|
||||
You'll have to specify the initial value expression. This value is then used
|
||||
by the compiler everywhere you refer to the constant (and no storage is allocated
|
||||
by the compiler everywhere you refer to the constant (and no memory is allocated
|
||||
for the constant itself). This is only valid for the simple numeric types (byte, word, float).
|
||||
|
||||
When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,
|
||||
|
@ -195,17 +195,22 @@ Directives
|
||||
.. data:: %breakpoint
|
||||
|
||||
Level: not at module scope.
|
||||
Defines a debugging breakpoint at this location. See :ref:`debugging`
|
||||
Defines a debugging breakpoint at this location. See :ref:`debugging`
|
||||
|
||||
.. data:: %asm {{ ... }}
|
||||
|
||||
Level: not at module scope.
|
||||
Declares that a piece of *assembly code* is inside the curly braces.
|
||||
This code will be copied as-is into the generated output assembly source file.
|
||||
The assembler syntax used should be for the 3rd party cross assembler tool that Prog8 uses (64tass).
|
||||
Note that the start and end markers are both *double curly braces* to minimize the chance
|
||||
that the assembly code itself contains either of those. If it does contain a ``}}``,
|
||||
it will confuse the parser.
|
||||
Declares that a piece of *assembly code* is inside the curly braces.
|
||||
This code will be copied as-is into the generated output assembly source file.
|
||||
The assembler syntax used should be for the 3rd party cross assembler tool that Prog8 uses (64tass).
|
||||
Note that the start and end markers are both *double curly braces* to minimize the chance
|
||||
that the assembly code itself contains either of those. If it does contain a ``}}``,
|
||||
it will confuse the parser.
|
||||
|
||||
If you use the correct scoping rules you can access symbols from the prog8 program from inside
|
||||
the assembly code. Sometimes you'll have to declare a variable in prog8 with `@shared` if it
|
||||
is only used in such assembly code. For symbols just consisting of 3 letters, prog8 will
|
||||
add a special prefix to them, read more about this in :ref:`three-letter-prefixing`.
|
||||
|
||||
|
||||
Identifiers
|
||||
@ -508,6 +513,7 @@ logical: ``not`` ``and`` ``or`` ``xor``
|
||||
Unlike most other programming languages, there is no short-circuit or McCarthy evaluation
|
||||
for the logical ``and`` and ``or`` operators. This means that prog8 currently always evaluates
|
||||
all operands from these logical expressions, even when one of them already determines the outcome!
|
||||
If you don't want this to happen, you have to split and nest the if-statements yourself.
|
||||
|
||||
range creation: ``to``
|
||||
Creates a range of values from the LHS value to the RHS value, inclusive.
|
||||
|
@ -13,6 +13,21 @@ Especially when you're dealing with interrupts or re-entrant routines: don't mod
|
||||
that you not own or else you will break stuff.
|
||||
|
||||
|
||||
.. _three-letter-prefixing:
|
||||
|
||||
Three-letter symbols prefixing in Assembly
|
||||
------------------------------------------
|
||||
|
||||
Symbols consisting of three letters such as "brk" or "tax" will confuse the assembler that
|
||||
thinks these are cpu instructions. It will likely fail to assemble the program correctly.
|
||||
Because of this, prog8 will prefix every 3-letter symbol with "``p8p_``" automatically during compilation.
|
||||
So "tax" will become "p8p_tax" in the resulting assembly code.
|
||||
|
||||
If you're referencing symbols from the prog8 program in hand-written assembly code, you have to take
|
||||
this into account. Either prefix the 3-letter symbols in the assembly with "``p8p_``" as well, or just
|
||||
choose a shorter or longer symbol name in the first place.
|
||||
|
||||
|
||||
Software stack for expression evaluation
|
||||
----------------------------------------
|
||||
|
||||
|
@ -3,8 +3,6 @@ TODO
|
||||
|
||||
For next release
|
||||
^^^^^^^^^^^^^^^^
|
||||
- ir: register allocation per data type a specific allocation, so we are certain when a reg is used it's just for one specific datatype
|
||||
- ir: write addresses as hex into p8ir file
|
||||
|
||||
...
|
||||
|
||||
@ -20,12 +18,16 @@ Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Compiler:
|
||||
|
||||
- create BSS section in output program and put StStaticVariables in there with bss=true. Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE! So requires self-modifying code
|
||||
- 6502 codegen: create BSS section in output assembly code and put StStaticVariables in there with bss=true.
|
||||
Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE if possible.
|
||||
Note that bss can still contain variables that have @zp tag and those are already dealt with differently
|
||||
- bss: subroutine parameters don't have to be set to 0.
|
||||
- ir: mechanism to determine for chunks which registers are getting input values from "outside"
|
||||
- ir: mechanism to determine for chunks which registers are passing values out? (i.e. are used again in another chunk)
|
||||
- ir: peephole opt: renumber registers in chunks to start with 1 again every time (but keep entry values in mind!)
|
||||
- ir peephole opt: reuse registers in chunks (but keep result registers in mind that pass values out!)
|
||||
- ir: add more optimizations in IRPeepholeOptimizer
|
||||
- vm: somehow be able to load a label address as value? (VmProgramLoader)
|
||||
- see if we can let for loops skip the loop if end<start, like other programming languages. Without adding a lot of code size/duplicating the loop condition.
|
||||
this is documented behavior to now loop around but it's too easy to forget about!
|
||||
Lot of work because of so many special cases in ForLoopsAsmgen.....
|
||||
@ -33,23 +35,25 @@ Compiler:
|
||||
- createAssemblyAndAssemble(): make it possible to actually get rid of the VarDecl nodes by fixing the rest of the code mentioned there.
|
||||
but probably better to rewrite the 6502 codegen on top of the new Ast.
|
||||
- generate WASM to eventually run prog8 on a browser canvas?
|
||||
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``p8v_``? Or not worth it (most 3 letter opcodes as variables are nonsensical anyway)
|
||||
then we can get rid of the instruction lists in the machinedefinitions as well?
|
||||
- [problematic due to using 64tass:] add a compiler option to not remove unused subroutines. this allows for building library programs. But this won't work with 64tass's .proc ...
|
||||
Perhaps replace all uses of .proc/.pend by .block/.bend will fix that with a compiler flag?
|
||||
But all library code written in asm uses .proc already..... (search/replace when writing the actual asm?)
|
||||
Once new codegen is written that is based on the IR, this point is moot anyway as that will have its own dead code removal.
|
||||
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that)
|
||||
- add special (u)word array type (or modifier?) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing
|
||||
- ast: don't rewrite by-reference parameter type to uword, but keep the original type (str, array)
|
||||
BUT that makes the handling of these types different between the scope they are defined in, and the
|
||||
scope they get passed in by reference... unless we make str and array types by-reference ALWAYS?
|
||||
BUT that makes simple code accessing them in the declared scope very slow because that then has to always go through
|
||||
the pointer rather than directly referencing the variable symbol in the generated asm....
|
||||
Or maybe make codegen smart to check if it's a subroutine parameter or local declared variable?
|
||||
|
||||
- Add a mechanism to allocate variables into golden ram (or segments really) (see GoldenRam class)
|
||||
- block "golden" treated specially: every var in here will be allocated in the Golden ram area
|
||||
- that block can only contain variables.
|
||||
- the variables can NOT have initialization values, they will all be set to zero on startup (simple memset)
|
||||
- just initialize them yourself in start() if you need a non-zero value
|
||||
- OR.... do all this automatically if 'golden' is enabled as a compiler option? So compiler allocates in ZP first, then Golden Ram, then regular ram
|
||||
- OR.... make all this more generic and use some %segment option to create real segments for 64tass?
|
||||
- (need separate step in codegen and IR to write the "golden" variables)
|
||||
|
||||
Libraries:
|
||||
|
||||
- duplicate diskio for cx16 (get rid of cx16diskio, just copy diskio and tweak everything) + documentation
|
||||
- get f_seek_w working like in the BASIC program - this needs the changes to diskio.f_open to use suffixes ,p,m
|
||||
- fix the problems in c128 target, and flesh out its libraries.
|
||||
- fix the problems in atari target, and flesh out its libraries.
|
||||
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
|
||||
|
@ -43,7 +43,7 @@ main {
|
||||
txt.print_uw(count)
|
||||
txt.print(" primes\n")
|
||||
|
||||
float time = c64.RDTIM16() / 60.0
|
||||
float time = c64.RDTIM16() as float / 60.0
|
||||
floats.print_f(time)
|
||||
txt.print(" sec total = ")
|
||||
floats.print_f(time/ITERS)
|
||||
|
112
examples/cx16/diskspeed.p8
Normal file
112
examples/cx16/diskspeed.p8
Normal file
@ -0,0 +1,112 @@
|
||||
%import diskio
|
||||
%import cx16diskio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
|
||||
ubyte[256] buffer = 0 to 255
|
||||
const ubyte REPEATS = 2
|
||||
|
||||
sub print_speed(uword jiffies) {
|
||||
float speed = 65536.0 * REPEATS / (jiffies as float / 60.0)
|
||||
txt.nl()
|
||||
txt.print_uw(jiffies)
|
||||
txt.print(" jiffies = ")
|
||||
floats.print_f(speed)
|
||||
txt.print(" bytes/sec\n")
|
||||
}
|
||||
|
||||
sub start() {
|
||||
txt.print("\n\ndisk benchmark. repeats = ")
|
||||
txt.print_ub(REPEATS)
|
||||
|
||||
uword batchtotaltime
|
||||
|
||||
txt.print("\n\nwriting 64kb using save")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
c64.SETTIM(0,0,0)
|
||||
void diskio.save(8, "@:benchmark.dat", $100, 32768)
|
||||
void diskio.save(8, "@:benchmark.dat", $100, 32768)
|
||||
batchtotaltime += c64.RDTIM16()
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.print("\nwriting 64kb sequentially")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
if diskio.f_open_w(8, "@:benchmark.dat") {
|
||||
c64.SETTIM(0,0,0)
|
||||
repeat 65536/256 {
|
||||
if not diskio.f_write(buffer, 256)
|
||||
sys.exit(1)
|
||||
}
|
||||
batchtotaltime += c64.RDTIM16()
|
||||
diskio.f_close_w()
|
||||
}
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.print("\nreading 64kb using load into hiram")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
c64.SETTIM(0,0,0)
|
||||
if not cx16diskio.load(8, "benchmark.dat", 4, $a000)
|
||||
sys.exit(1)
|
||||
batchtotaltime += c64.RDTIM16()
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.print("\nreading 64kb using vload into videoram")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
c64.SETTIM(0,0,0)
|
||||
if not cx16diskio.vload("benchmark.dat", 8, 0, $0000)
|
||||
sys.exit(1)
|
||||
batchtotaltime += c64.RDTIM16()
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.print("\nreading 64kb sequentially")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
if diskio.f_open(8, "benchmark.dat") {
|
||||
c64.SETTIM(0,0,0)
|
||||
repeat 65536/255 {
|
||||
if not diskio.f_read(buffer, 255)
|
||||
sys.exit(1)
|
||||
}
|
||||
batchtotaltime += c64.RDTIM16()
|
||||
diskio.f_close()
|
||||
}
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.print("\nreading 64kb sequentially (x16 optimized)")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
if diskio.f_open(8, "benchmark.dat") {
|
||||
c64.SETTIM(0,0,0)
|
||||
repeat 65536/255 {
|
||||
if not cx16diskio.f_read(buffer, 255)
|
||||
sys.exit(1)
|
||||
}
|
||||
batchtotaltime += c64.RDTIM16()
|
||||
diskio.f_close()
|
||||
}
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.nl()
|
||||
txt.print(diskio.status(8))
|
||||
txt.print("\ndone.\n")
|
||||
}
|
||||
}
|
97
examples/cx16/fileseek.p8
Normal file
97
examples/cx16/fileseek.p8
Normal file
@ -0,0 +1,97 @@
|
||||
; this program shows the use of the f_seek function to seek to a position in an opened file.
|
||||
; (this only works on Commander X16 DOS. on sdcard, not on host filesystem.)
|
||||
|
||||
%import diskio
|
||||
%import cx16diskio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
uword megabuffer = memory("megabuffer", 20000, 256)
|
||||
|
||||
sub start() {
|
||||
|
||||
txt.print("writing data file...\n")
|
||||
uword total=0
|
||||
if diskio.f_open_w(8, "@:seektestfile.bin") {
|
||||
repeat 100 {
|
||||
str text = "hello world.\n"
|
||||
void diskio.f_write(text, string.length(text))
|
||||
total += string.length(text)
|
||||
}
|
||||
diskio.f_close_w()
|
||||
txt.print("written size=")
|
||||
txt.print_uw(total)
|
||||
txt.nl()
|
||||
} else {
|
||||
txt.print("error: ")
|
||||
txt.print(diskio.status(8))
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
read_last_bytes()
|
||||
|
||||
; NOTE: f_seek_w() doesn't work right now. It requires substantial changes to the diskio library that are not compatible with the C64/C128.
|
||||
; txt.print("\nseeking to 1292 and writing a few bytes...\n")
|
||||
; if diskio.f_open_w(8, "seektestfile.bin,p,m") {
|
||||
; cx16diskio.f_seek_w(0, 1292)
|
||||
; void diskio.f_write("123", 3)
|
||||
; diskio.f_close_w()
|
||||
; } else {
|
||||
; txt.print("error: ")
|
||||
; txt.print(diskio.status(8))
|
||||
; sys.exit(1)
|
||||
; }
|
||||
;
|
||||
; read_last_bytes()
|
||||
}
|
||||
|
||||
sub read_last_bytes() {
|
||||
; read the last 10 bytes of the 1300 bytes file
|
||||
uword total = 0
|
||||
uword size
|
||||
txt.print("\nreading...\n")
|
||||
if diskio.f_open(8, "seektestfile.bin") {
|
||||
size = diskio.f_read_all(megabuffer)
|
||||
diskio.f_close()
|
||||
txt.print("size read:")
|
||||
txt.print_uw(size)
|
||||
txt.nl()
|
||||
} else {
|
||||
txt.print("error!\n")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
txt.print("\nseeking to 1290 and reading...\n")
|
||||
if diskio.f_open(8, "seektestfile.bin") {
|
||||
cx16diskio.f_seek(0, 1290)
|
||||
uword ptr = megabuffer
|
||||
do {
|
||||
size = diskio.f_read(ptr, 255)
|
||||
total += size
|
||||
ptr += size
|
||||
} until size==0
|
||||
diskio.f_close()
|
||||
txt.print("size read=")
|
||||
txt.print_uw(total)
|
||||
txt.nl()
|
||||
megabuffer[lsb(total)] = 0
|
||||
txt.print("buffer read=")
|
||||
ubyte idx
|
||||
for idx in 0 to lsb(total-1) {
|
||||
txt.print_ubhex(megabuffer[idx], false)
|
||||
txt.spc()
|
||||
}
|
||||
txt.spc()
|
||||
txt.chrout('{')
|
||||
txt.print(megabuffer)
|
||||
txt.chrout('}')
|
||||
txt.nl()
|
||||
} else {
|
||||
txt.print("error: ")
|
||||
txt.print(diskio.status(8))
|
||||
sys.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
BIN
examples/cx16/pcmaudio/adpcm-mono.wav
Normal file
BIN
examples/cx16/pcmaudio/adpcm-mono.wav
Normal file
Binary file not shown.
258
examples/cx16/pcmaudio/adpcm.p8
Normal file
258
examples/cx16/pcmaudio/adpcm.p8
Normal file
@ -0,0 +1,258 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%option no_sysinit
|
||||
%zeropage basicsafe
|
||||
|
||||
;
|
||||
; IMA ADPCM decoding and playback example.
|
||||
; https://wiki.multimedia.cx/index.php/IMA_ADPCM
|
||||
; https://wiki.multimedia.cx/index.php/Microsoft_IMA_ADPCM
|
||||
;
|
||||
; IMA ADPCM encodes two 16-bit PCM audio samples in 1 byte (1 word per nibble)
|
||||
; thus compressing the audio data by a factor of 4.
|
||||
; The encoding precision is about 13 bits per sample so it's a lossy compression scheme.
|
||||
;
|
||||
; NOTE: this program requires 16 bits MONO audio, and 256 byte encoded block size!
|
||||
; HOW TO CREATE SUCH IMA-ADPCM ENCODED AUDIO? Use sox or ffmpeg:
|
||||
; $ sox --guard source.mp3 -r 8000 -c 1 -e ima-adpcm out.wav trim 01:27.50 00:09
|
||||
; $ ffmpeg -i source.mp3 -ss 00:01:27.50 -to 00:01:36.50 -ar 8000 -ac 1 -c:a adpcm_ima_wav -block_size 256 -map_metadata -1 -bitexact out.wav
|
||||
; Or use a tool such as https://github.com/dbry/adpcm-xq .
|
||||
;
|
||||
|
||||
main {
|
||||
|
||||
ubyte adpcm_blocks_left
|
||||
uword @requirezp nibblesptr
|
||||
|
||||
sub start() {
|
||||
wavfile.parse()
|
||||
|
||||
txt.print_uw(wavfile.adpcm_size)
|
||||
txt.print(" adpcm data size = ")
|
||||
txt.print_ub(wavfile.num_adpcm_blocks)
|
||||
txt.print(" blocks\nsamplerate = ")
|
||||
txt.print_uw(wavfile.sample_rate)
|
||||
txt.print(" vera rate = ")
|
||||
txt.print_uw(wavfile.vera_rate_hz)
|
||||
txt.print("\n(b)enchmark or (p)layback? ")
|
||||
|
||||
when c64.CHRIN() {
|
||||
'b' -> benchmark()
|
||||
'p' -> playback()
|
||||
}
|
||||
}
|
||||
|
||||
sub benchmark() {
|
||||
nibblesptr = wavfile.adpcm_data_ptr
|
||||
|
||||
txt.print("\ndecoding all blocks...\n")
|
||||
c64.SETTIM(0,0,0)
|
||||
repeat wavfile.num_adpcm_blocks {
|
||||
adpcm.init(peekw(nibblesptr), @(nibblesptr+2))
|
||||
nibblesptr += 4
|
||||
repeat 252 {
|
||||
ubyte @zp nibble = @(nibblesptr)
|
||||
adpcm.decode_nibble(nibble & 15) ; first word
|
||||
adpcm.decode_nibble(nibble>>4) ; second word
|
||||
nibblesptr++
|
||||
}
|
||||
}
|
||||
float duration_secs = (c64.RDTIM16() as float) / 60.0
|
||||
float words_per_second = 505.0 * (wavfile.num_adpcm_blocks as float) / duration_secs
|
||||
txt.print_uw(words_per_second as uword)
|
||||
txt.print(" words/sec\n")
|
||||
}
|
||||
|
||||
sub playback() {
|
||||
nibblesptr = wavfile.adpcm_data_ptr
|
||||
adpcm_blocks_left = wavfile.num_adpcm_blocks
|
||||
|
||||
cx16.VERA_AUDIO_CTRL = %10101111 ; mono 16 bit
|
||||
cx16.VERA_AUDIO_RATE = 0 ; halt playback
|
||||
repeat 1024 {
|
||||
cx16.VERA_AUDIO_DATA = 0
|
||||
}
|
||||
|
||||
sys.set_irqd()
|
||||
cx16.CINV = &irq_handler
|
||||
cx16.VERA_IEN = %00001000 ; enable AFLOW
|
||||
sys.clear_irqd()
|
||||
|
||||
cx16.VERA_AUDIO_RATE = wavfile.vera_rate ; start playback
|
||||
|
||||
txt.print("\naudio via irq\n")
|
||||
|
||||
repeat {
|
||||
; audio will play via the IRQ.
|
||||
}
|
||||
|
||||
; not reached:
|
||||
; cx16.VERA_AUDIO_CTRL = %00100000
|
||||
; cx16.VERA_AUDIO_RATE = 0
|
||||
; txt.print("audio off.\n")
|
||||
}
|
||||
|
||||
sub irq_handler() {
|
||||
if cx16.VERA_ISR & %00001000 {
|
||||
; AFLOW irq.
|
||||
;; cx16.vpoke(1,$fa0c, $a0) ; paint a screen color
|
||||
|
||||
; refill the fifo buffer with two decoded adpcm blocks (252 nibbles -> 1008 bytes per block)
|
||||
repeat 2 {
|
||||
adpcm.init(peekw(nibblesptr), @(nibblesptr+2))
|
||||
nibblesptr += 4
|
||||
repeat 252 {
|
||||
ubyte @zp nibble = @(nibblesptr)
|
||||
adpcm.decode_nibble(nibble & 15) ; first word
|
||||
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict)
|
||||
cx16.VERA_AUDIO_DATA = msb(adpcm.predict)
|
||||
adpcm.decode_nibble(nibble>>4) ; second word
|
||||
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict)
|
||||
cx16.VERA_AUDIO_DATA = msb(adpcm.predict)
|
||||
nibblesptr++
|
||||
}
|
||||
|
||||
adpcm_blocks_left--
|
||||
if adpcm_blocks_left==0 {
|
||||
; restart adpcm data from the beginning
|
||||
nibblesptr = wavfile.adpcm_data_ptr
|
||||
adpcm_blocks_left = wavfile.num_adpcm_blocks
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
; TODO not AFLOW, handle other IRQ
|
||||
}
|
||||
|
||||
;; cx16.vpoke(1,$fa0c, 0) ; back to other screen color
|
||||
|
||||
%asm {{
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
adpcm {
|
||||
|
||||
; IMA ADPCM decoder.
|
||||
; https://wiki.multimedia.cx/index.php/IMA_ADPCM
|
||||
; https://wiki.multimedia.cx/index.php/Microsoft_IMA_ADPCM
|
||||
|
||||
ubyte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]
|
||||
uword[] t_step = [
|
||||
7, 8, 9, 10, 11, 12, 13, 14,
|
||||
16, 17, 19, 21, 23, 25, 28, 31,
|
||||
34, 37, 41, 45, 50, 55, 60, 66,
|
||||
73, 80, 88, 97, 107, 118, 130, 143,
|
||||
157, 173, 190, 209, 230, 253, 279, 307,
|
||||
337, 371, 408, 449, 494, 544, 598, 658,
|
||||
724, 796, 876, 963, 1060, 1166, 1282, 1411,
|
||||
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
|
||||
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
|
||||
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
|
||||
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
|
||||
32767]
|
||||
|
||||
uword @zp predict
|
||||
ubyte @zp index
|
||||
uword @zp pstep
|
||||
|
||||
sub init(uword startPredict, ubyte startIndex) {
|
||||
predict = startPredict
|
||||
index = startIndex
|
||||
pstep = t_step[index]
|
||||
}
|
||||
|
||||
sub decode_nibble(ubyte nibble) {
|
||||
; this is the hotspot of the decoder algorithm!
|
||||
cx16.r0s = 0 ; difference
|
||||
if nibble & %0100
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
if nibble & %0010
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
if nibble & %0001
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
cx16.r0s += pstep
|
||||
if nibble & %1000
|
||||
cx16.r0s = -cx16.r0s
|
||||
predict += cx16.r0s as uword
|
||||
index += t_index[nibble]
|
||||
if index & 128
|
||||
index = 0
|
||||
else if index > len(t_step)-1
|
||||
index = len(t_step)-1
|
||||
pstep = t_step[index]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wavfile {
|
||||
|
||||
const ubyte WAVE_FORMAT_PCM = $1
|
||||
const ubyte WAVE_FORMAT_ADPCM = $2
|
||||
const ubyte WAVE_FORMAT_IEEE_FLOAT = $3
|
||||
const ubyte WAVE_FORMAT_ALAW = $6
|
||||
const ubyte WAVE_FORMAT_MULAW = $7
|
||||
const ubyte WAVE_FORMAT_DVI_ADPCM = $11
|
||||
|
||||
uword sample_rate
|
||||
uword vera_rate_hz
|
||||
ubyte vera_rate
|
||||
uword adpcm_size
|
||||
uword adpcm_data_ptr
|
||||
ubyte num_adpcm_blocks
|
||||
|
||||
sub parse() {
|
||||
; "RIFF" , filesize (int32) , "WAVE", "fmt ", fmtsize (int32)
|
||||
; we assume file sizes are <= 64Kb so don't have to worry about the upper 16 bits
|
||||
uword @zp header = &wavdata.wav_data
|
||||
if header[0]!=iso:'R' or header[1]!=iso:'I' or header[2]!=iso:'F' or header[3]!=iso:'F'
|
||||
or header[8]!=iso:'W' or header[9]!=iso:'A' or header[10]!=iso:'V' or header[11]!=iso:'E'
|
||||
or header[12]!=iso:'f' or header[13]!=iso:'m' or header[14]!=iso:'t' or header[15]!=iso:' ' {
|
||||
txt.print("not a valid wav file\n")
|
||||
sys.exit(1)
|
||||
}
|
||||
; uword filesize = peekw(header+4)
|
||||
uword chunksize = peekw(header+16)
|
||||
uword wavefmt = peekw(header+20)
|
||||
uword nchannels = peekw(header+22)
|
||||
sample_rate = peekw(header+24) ; we assume sample rate <= 65535 so we can ignore the upper word
|
||||
uword block_align = peekw(header+32)
|
||||
|
||||
if block_align!=256 or nchannels!=1 or wavefmt!=WAVE_FORMAT_DVI_ADPCM {
|
||||
txt.print("invalid wav specs\n")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
; skip chunks until we reach the 'data' chunk
|
||||
header += chunksize + 20
|
||||
repeat {
|
||||
chunksize = peekw(header+4) ; assume chunk size never exceeds 64kb so ignore upper word
|
||||
if header[0]==iso:'d' and header[1]==iso:'a' and header[2]==iso:'t' and header[3]==iso:'a'
|
||||
break
|
||||
header += 8 + chunksize
|
||||
}
|
||||
adpcm_data_ptr = header + 8
|
||||
adpcm_size = chunksize
|
||||
num_adpcm_blocks = (chunksize / 256) as ubyte ; NOTE: THE ADPCM DATA NEEDS TO BE ENCODED IN 256-byte BLOCKS !
|
||||
|
||||
const float vera_freq_factor = 25e6 / 65536.0
|
||||
vera_rate = (sample_rate as float / vera_freq_factor) + 1.0 as ubyte
|
||||
vera_rate_hz = (vera_rate as float) * vera_freq_factor as uword
|
||||
}
|
||||
}
|
||||
|
||||
wavdata {
|
||||
%option align_page
|
||||
wav_data:
|
||||
%asmbinary "adpcm-mono.wav"
|
||||
wav_data_end:
|
||||
|
||||
}
|
BIN
examples/cx16/pcmaudio/pcm-mono.bin
Normal file
BIN
examples/cx16/pcmaudio/pcm-mono.bin
Normal file
Binary file not shown.
149
examples/cx16/pcmaudio/pcmplay1.asm
Normal file
149
examples/cx16/pcmaudio/pcmplay1.asm
Normal file
@ -0,0 +1,149 @@
|
||||
;
|
||||
; This program plays a short mono 16 bit PCM sample on repeat
|
||||
; It uses no IRQs but just checks the Fifo Full/Empty bits to see when it needs to copy more data.
|
||||
; The flashing green bar is when the routine is busy copying sample data into the fifo.
|
||||
;
|
||||
; source code is for 64TASS, assemble with: 64tass pcmplay1.asm -o pcmplay1.prg
|
||||
;
|
||||
|
||||
.cpu 'w65c02'
|
||||
.enc 'none'
|
||||
|
||||
* = $0801
|
||||
; basic launcher
|
||||
.word $080b, 2022
|
||||
.text $9e, "2061", $00
|
||||
.word 0
|
||||
entrypoint:
|
||||
|
||||
|
||||
CHROUT = $ffd2
|
||||
VERA_ADDR_L = $9f20
|
||||
VERA_ADDR_M = $9f21
|
||||
VERA_ADDR_H = $9f22
|
||||
VERA_DATA0 = $9f23
|
||||
VERA_CTRL = $9f25
|
||||
VERA_IEN = $9f26
|
||||
VERA_ISR = $9f27
|
||||
VERA_AUDIO_CTRL = $9f3b
|
||||
VERA_AUDIO_RATE = $9f3c
|
||||
VERA_AUDIO_DATA = $9f3d
|
||||
IRQ_VECTOR = $0314
|
||||
r0 = $02
|
||||
r0L = $02
|
||||
r0H = $03
|
||||
r1 = $04
|
||||
r1L = $04
|
||||
r1H = $05
|
||||
pcm_ptr = $06
|
||||
|
||||
|
||||
; stop playback and select mono 16 bit, max volume
|
||||
stz VERA_AUDIO_RATE
|
||||
lda #%10101111
|
||||
sta VERA_AUDIO_CTRL
|
||||
|
||||
; fill the fifo with some of silence
|
||||
ldy #1024/256
|
||||
_z1: ldx #0
|
||||
_z2: stz VERA_AUDIO_DATA
|
||||
dex
|
||||
bne _z2
|
||||
dey
|
||||
bne _z1
|
||||
|
||||
ldx #<audio_on_txt
|
||||
ldy #>audio_on_txt
|
||||
jsr print
|
||||
|
||||
ldx #<pcm_data
|
||||
ldy #>pcm_data
|
||||
stx pcm_ptr
|
||||
sty pcm_ptr+1
|
||||
|
||||
lda #21
|
||||
sta VERA_AUDIO_RATE ; start playing
|
||||
|
||||
play_loop:
|
||||
_wait_for_empty_fifo:
|
||||
bit VERA_AUDIO_CTRL ; fifo empty?
|
||||
bvc _wait_for_empty_fifo
|
||||
|
||||
; fifo is empty, we go ahead and fill it with more sound data.
|
||||
; paint a screen color
|
||||
stz VERA_CTRL
|
||||
lda #$0c
|
||||
sta VERA_ADDR_L
|
||||
lda #$fa
|
||||
sta VERA_ADDR_M
|
||||
lda #$01
|
||||
sta VERA_ADDR_H
|
||||
lda #$a0
|
||||
sta VERA_DATA0
|
||||
|
||||
|
||||
_copy_samples:
|
||||
; this assumes all samples are aligned to page size!
|
||||
; so we can at least copy a full page at once here.
|
||||
ldy #0
|
||||
_c1 lda (pcm_ptr),y
|
||||
sta VERA_AUDIO_DATA ; lsb
|
||||
iny
|
||||
lda (pcm_ptr),y
|
||||
sta VERA_AUDIO_DATA ; msb
|
||||
iny
|
||||
bne _c1
|
||||
inc pcm_ptr+1
|
||||
|
||||
; have we reached the end of the sample data? (pcm_ptr >= pcm_data_end)
|
||||
; due to page size alignment only the MSB has to be checked
|
||||
lda pcm_ptr+1
|
||||
cmp #>pcm_data_end
|
||||
bcc _continue
|
||||
bne _end_reached
|
||||
; lda pcm_ptr ; uncomment if lsb also needs to be checked
|
||||
; cmp #<pcm_data_end
|
||||
; bcs _end_reached
|
||||
_continue:
|
||||
bit VERA_AUDIO_CTRL ; is fifo full?
|
||||
bpl _copy_samples ; no, continue copying
|
||||
|
||||
lda #$00
|
||||
sta VERA_DATA0 ; back to other screen color
|
||||
jmp play_loop
|
||||
|
||||
_end_reached:
|
||||
; reset sound to beginning
|
||||
lda #<pcm_data
|
||||
sta pcm_ptr
|
||||
lda #>pcm_data
|
||||
sta pcm_ptr+1
|
||||
bra _continue
|
||||
|
||||
stop_playback:
|
||||
; stop playback
|
||||
stz VERA_AUDIO_RATE
|
||||
stz VERA_AUDIO_CTRL
|
||||
ldx #<audio_off_txt
|
||||
ldy #>audio_off_txt
|
||||
jsr print
|
||||
rts
|
||||
|
||||
audio_on_txt: .text "AUDIO ON", $0d, $00
|
||||
audio_off_txt: .text "AUDIO OFF", $0d, $00
|
||||
|
||||
print: ; -- print string pointed to by X/Y
|
||||
stx r0L
|
||||
sty r0H
|
||||
ldy #0
|
||||
_chr: lda (r0),y
|
||||
beq _done
|
||||
jsr $ffd2
|
||||
iny
|
||||
bne _chr
|
||||
_done: rts
|
||||
|
||||
.align $0100
|
||||
pcm_data:
|
||||
.binary "pcm-mono.bin"
|
||||
pcm_data_end:
|
167
examples/cx16/pcmaudio/pcmplay2.asm
Normal file
167
examples/cx16/pcmaudio/pcmplay2.asm
Normal file
@ -0,0 +1,167 @@
|
||||
;
|
||||
; This program plays a short mono 16 bit PCM sample on repeat
|
||||
; It uses the AFLOW IRQ that signals when the Fifo is about to drain empty.
|
||||
; The flashing green bar is when the routine is busy copying sample data into the fifo.
|
||||
;
|
||||
; source code is for 64TASS, assemble with: 64tass pcmplay2.asm -o pcmplay2.prg
|
||||
;
|
||||
|
||||
.cpu 'w65c02'
|
||||
.enc 'none'
|
||||
|
||||
* = $0801
|
||||
; basic launcher
|
||||
.word $080b, 2022
|
||||
.text $9e, "2061", $00
|
||||
.word 0
|
||||
entrypoint:
|
||||
|
||||
|
||||
CHROUT = $ffd2
|
||||
VERA_ADDR_L = $9f20
|
||||
VERA_ADDR_M = $9f21
|
||||
VERA_ADDR_H = $9f22
|
||||
VERA_DATA0 = $9f23
|
||||
VERA_CTRL = $9f25
|
||||
VERA_IEN = $9f26
|
||||
VERA_ISR = $9f27
|
||||
VERA_AUDIO_CTRL = $9f3b
|
||||
VERA_AUDIO_RATE = $9f3c
|
||||
VERA_AUDIO_DATA = $9f3d
|
||||
IRQ_VECTOR = $0314
|
||||
r0 = $02
|
||||
r0L = $02
|
||||
r0H = $03
|
||||
r1 = $04
|
||||
r1L = $04
|
||||
r1H = $05
|
||||
pcm_ptr = $06
|
||||
|
||||
|
||||
; stop playback and select mono 16 bit, max volume
|
||||
stz VERA_AUDIO_RATE
|
||||
lda #%10101111
|
||||
sta VERA_AUDIO_CTRL
|
||||
|
||||
; fill the fifo with some silence
|
||||
ldy #1024/256
|
||||
_z1: ldx #0
|
||||
_z2: stz VERA_AUDIO_DATA
|
||||
dex
|
||||
bne _z2
|
||||
dey
|
||||
bne _z1
|
||||
|
||||
ldx #<audio_on_txt
|
||||
ldy #>audio_on_txt
|
||||
jsr print
|
||||
|
||||
ldx #<pcm_data
|
||||
ldy #>pcm_data
|
||||
stx pcm_ptr
|
||||
sty pcm_ptr+1
|
||||
|
||||
; set interrupt handler
|
||||
sei
|
||||
ldx #<irq_handler
|
||||
ldy #>irq_handler
|
||||
stx IRQ_VECTOR
|
||||
sty IRQ_VECTOR+1
|
||||
lda #%00001000 ; enable the AFLOW irq
|
||||
sta VERA_IEN
|
||||
cli
|
||||
|
||||
lda #21
|
||||
sta VERA_AUDIO_RATE ; start playback
|
||||
|
||||
_wait:
|
||||
wai
|
||||
bra _wait
|
||||
|
||||
|
||||
irq_handler:
|
||||
lda VERA_ISR
|
||||
and #%00001000 ; is aflow?
|
||||
bne _aflow_irq
|
||||
; TODO other irq
|
||||
bra _exit_irq
|
||||
|
||||
_aflow_irq:
|
||||
; paint a screen color
|
||||
stz VERA_CTRL
|
||||
lda #$0c
|
||||
sta VERA_ADDR_L
|
||||
lda #$fa
|
||||
sta VERA_ADDR_M
|
||||
lda #$01
|
||||
sta VERA_ADDR_H
|
||||
lda #$a0
|
||||
sta VERA_DATA0
|
||||
|
||||
; refill fifo buffer (minimum 1Kb = 1/4)
|
||||
; this assumes all samples are aligned to page size!
|
||||
; so we can at least copy a full page at once here.
|
||||
ldx #8 ; number of pages to copy
|
||||
_copy_more_pages:
|
||||
ldy #0
|
||||
_c1: lda (pcm_ptr),y
|
||||
sta VERA_AUDIO_DATA ; lsb
|
||||
iny
|
||||
lda (pcm_ptr),y
|
||||
sta VERA_AUDIO_DATA ; msb
|
||||
iny
|
||||
; (in case of 16 bit stereo output, this can be unrolled by 2 bytes again).
|
||||
bne _c1
|
||||
inc pcm_ptr+1
|
||||
|
||||
; have we reached the end of the sample data? (pcm_ptr >= pcm_data_end)
|
||||
; due to page size alignment only the MSB has to be checked
|
||||
lda pcm_ptr+1
|
||||
cmp #>pcm_data_end
|
||||
bcc _not_at_end
|
||||
bne _end_reached
|
||||
; lda pcm_ptr ; uncomment if lsb also needs to be checked
|
||||
; cmp #<pcm_data_end
|
||||
; bcs _end_reached
|
||||
|
||||
_not_at_end:
|
||||
; note: filling the fifo until the Fifo Full bit is set doesn't seem to work reliably
|
||||
; so we just fill it with a fixed number of samples.
|
||||
; in this case 8 pages = 2Kb which fills the fifo back up to 50%-75%.
|
||||
dex
|
||||
bne _copy_more_pages
|
||||
|
||||
lda #$00
|
||||
sta VERA_DATA0 ; back to other screen color
|
||||
|
||||
_exit_irq:
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
_end_reached:
|
||||
lda #<pcm_data
|
||||
sta pcm_ptr
|
||||
lda #>pcm_data
|
||||
sta pcm_ptr+1
|
||||
bra _not_at_end
|
||||
|
||||
|
||||
audio_on_txt: .text "AUDIO ON (IRQ)", $0d, $00
|
||||
|
||||
print: ; -- print string pointed to by X/Y
|
||||
stx r0L
|
||||
sty r0H
|
||||
ldy #0
|
||||
_chr: lda (r0),y
|
||||
beq _done
|
||||
jsr $ffd2
|
||||
iny
|
||||
bne _chr
|
||||
_done: rts
|
||||
|
||||
.align $0100
|
||||
pcm_data:
|
||||
.binary "pcm-mono.bin"
|
||||
pcm_data_end:
|
82
examples/cx16/snow.p8
Normal file
82
examples/cx16/snow.p8
Normal file
@ -0,0 +1,82 @@
|
||||
%import math
|
||||
%import gfx2
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
gfx2.screen_mode(4)
|
||||
|
||||
uword[128] xx
|
||||
uword[128] yy
|
||||
|
||||
ubyte @zp idx
|
||||
for idx in 0 to 127 {
|
||||
xx[idx] = math.rndw() % 320
|
||||
yy[idx] = math.rndw() % 240
|
||||
}
|
||||
|
||||
gfx2.text(96, 90, 2, sc:"**************")
|
||||
gfx2.text(104, 100, 5, sc:"let it snow!")
|
||||
gfx2.text(96, 110, 2, sc:"**************")
|
||||
|
||||
const ubyte FALLING_SNOW_COLOR = 1
|
||||
const ubyte PILED_SNOW_COLOR = 15
|
||||
|
||||
repeat {
|
||||
sys.waitvsync()
|
||||
for idx in 0 to 127 {
|
||||
gfx2.plot(xx[idx], yy[idx], FALLING_SNOW_COLOR)
|
||||
}
|
||||
sys.waitvsync()
|
||||
for idx in 0 to 127 {
|
||||
if yy[idx]==239 {
|
||||
; reached the floor
|
||||
gfx2.plot(xx[idx], yy[idx], PILED_SNOW_COLOR)
|
||||
yy[idx] = 0
|
||||
xx[idx] = math.rndw() % 320
|
||||
} else if gfx2.pget(xx[idx], yy[idx]+1)==PILED_SNOW_COLOR {
|
||||
; pile up
|
||||
uword @zp snowx = xx[idx]
|
||||
if snowx!=0 and snowx!=319 { ; check to avoid x coordinate under/overflow
|
||||
uword pilex1
|
||||
uword pilex2
|
||||
if math.rnd() & 1 {
|
||||
pilex1 = snowx-1
|
||||
pilex2 = snowx+1
|
||||
} else {
|
||||
pilex1 = snowx+1
|
||||
pilex2 = snowx-1
|
||||
}
|
||||
if gfx2.pget(pilex1, yy[idx]+1)==0 {
|
||||
gfx2.plot(snowx, yy[idx], 0)
|
||||
gfx2.plot(pilex1, yy[idx]+1, PILED_SNOW_COLOR)
|
||||
} else if gfx2.pget(pilex2, yy[idx]+1)==0 {
|
||||
gfx2.plot(snowx, yy[idx], 0)
|
||||
gfx2.plot(pilex2, yy[idx]+1, PILED_SNOW_COLOR)
|
||||
} else {
|
||||
gfx2.plot(snowx, yy[idx], PILED_SNOW_COLOR)
|
||||
}
|
||||
}
|
||||
yy[idx] = 0
|
||||
xx[idx] = math.rndw() % 320
|
||||
} else {
|
||||
; fall
|
||||
gfx2.plot(xx[idx], yy[idx], 0)
|
||||
yy[idx]++
|
||||
when math.rnd() & 3 {
|
||||
1 -> {
|
||||
if xx[idx]
|
||||
xx[idx]--
|
||||
}
|
||||
2 -> {
|
||||
if xx[idx] < 319
|
||||
xx[idx]++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repeat {
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
%import gfx2
|
||||
%import textio
|
||||
%import test_stack
|
||||
%import math
|
||||
|
||||
%option no_sysinit
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
gfx2.screen_mode(5)
|
||||
|
||||
; demo1()
|
||||
; sys.wait(3*60)
|
||||
demo1()
|
||||
sys.wait(2*60)
|
||||
demo2()
|
||||
|
||||
gfx2.screen_mode(0)
|
||||
txt.print("done!\n")
|
||||
|
||||
test_stack.test()
|
||||
}
|
||||
|
||||
sub demo1() {
|
||||
@ -192,7 +192,7 @@ main {
|
||||
|
||||
ubyte tp
|
||||
for tp in 0 to 15 {
|
||||
gfx2.text(19+tp,20+tp*11, 5, sc:"ScreenCODE text! 1234![]<>#$%&*()")
|
||||
gfx2.text(19+tp,20+tp*11, 7, sc:"ScreenCODE text! 1234![]<>#$%&*()")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,27 +1,52 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
ubyte @requirezp zpvar = 10
|
||||
ubyte @zp zpvar2 = 20
|
||||
uword empty
|
||||
ubyte[10] bssarray
|
||||
uword[10] bsswordarray
|
||||
ubyte[10] nonbssarray = 99
|
||||
str name="irmen"
|
||||
|
||||
sub start() {
|
||||
float f1
|
||||
|
||||
floats.rndseedf(-1.2345)
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.print("= 10 ")
|
||||
txt.print_ub(zpvar)
|
||||
txt.nl()
|
||||
zpvar++
|
||||
|
||||
floats.rndseedf(1.2345)
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.spc()
|
||||
floats.print_f(floats.rndf())
|
||||
txt.print("= 20 ")
|
||||
txt.print_ub(zpvar2)
|
||||
txt.nl()
|
||||
zpvar2++
|
||||
|
||||
txt.print("= 0 ")
|
||||
txt.print_uw(empty)
|
||||
txt.nl()
|
||||
empty++
|
||||
|
||||
txt.print("+ 0 ")
|
||||
txt.print_ub(bssarray[1])
|
||||
txt.nl()
|
||||
bssarray[1]++
|
||||
|
||||
txt.print("+ 0 ")
|
||||
txt.print_uw(bsswordarray[1])
|
||||
txt.nl()
|
||||
bsswordarray[1]++
|
||||
|
||||
txt.print("+ 99 ")
|
||||
txt.print_ub(nonbssarray[1])
|
||||
txt.nl()
|
||||
nonbssarray[1]++
|
||||
|
||||
txt.print("+ r ")
|
||||
txt.chrout(name[1])
|
||||
txt.nl()
|
||||
name[1] = (name[1] as ubyte +1)
|
||||
|
||||
txt.print("try running again.\n")
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,4 @@ org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
javaVersion=11
|
||||
kotlinVersion=1.7.20
|
||||
kotlinVersion=1.7.21
|
||||
|
@ -42,8 +42,7 @@ class RequestParser : Take {
|
||||
symbolDefs = emptyMap(),
|
||||
quietAssembler = false,
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
keepIR = false
|
||||
experimentalCodegen = false
|
||||
)
|
||||
val compilationResult = compileProgram(args)
|
||||
return RsJson(Jsonding())
|
||||
|
@ -3,39 +3,57 @@ package prog8.intermediate
|
||||
import prog8.code.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.*
|
||||
import java.io.StringReader
|
||||
import java.nio.file.Path
|
||||
import javax.xml.stream.XMLEventReader
|
||||
import javax.xml.stream.XMLInputFactory
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.bufferedReader
|
||||
import kotlin.io.path.inputStream
|
||||
|
||||
|
||||
class IRFileReader {
|
||||
|
||||
fun read(irSourceCode: CharSequence): IRProgram {
|
||||
return parseProgram(irSourceCode.lineSequence().iterator())
|
||||
fun read(irSourceCode: String): IRProgram {
|
||||
StringReader(irSourceCode).use { stream ->
|
||||
val reader = XMLInputFactory.newInstance().createXMLEventReader(stream)
|
||||
try {
|
||||
return parseProgram(reader)
|
||||
} finally {
|
||||
reader.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun read(irSourceFile: Path): IRProgram {
|
||||
println("Reading intermediate representation from $irSourceFile")
|
||||
irSourceFile.bufferedReader().use { reader ->
|
||||
return parseProgram(reader.lineSequence().iterator())
|
||||
|
||||
irSourceFile.inputStream().use { stream ->
|
||||
val reader = XMLInputFactory.newInstance().createXMLEventReader(stream)
|
||||
try {
|
||||
return parseProgram(reader)
|
||||
} finally {
|
||||
reader.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseProgram(lines: Iterator<String>): IRProgram {
|
||||
val programPattern = Regex("<PROGRAM NAME=(.+)>")
|
||||
val line = lines.next()
|
||||
val match = programPattern.matchEntire(line) ?: throw IRParseException("invalid PROGRAM")
|
||||
val programName = match.groups[1]!!.value
|
||||
val options = parseOptions(lines)
|
||||
val asmsymbols = parseAsmSymbols(lines)
|
||||
val variables = parseVariables(lines, options.dontReinitGlobals)
|
||||
val memorymapped = parseMemMapped(lines)
|
||||
val slabs = parseSlabs(lines)
|
||||
val initGlobals = parseInitGlobals(lines)
|
||||
val blocks = parseBlocksUntilProgramEnd(lines)
|
||||
private fun parseProgram(reader: XMLEventReader): IRProgram {
|
||||
require(reader.nextEvent().isStartDocument)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="PROGRAM") { "missing PROGRAM" }
|
||||
val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value
|
||||
val options = parseOptions(reader)
|
||||
val asmsymbols = parseAsmSymbols(reader)
|
||||
val bss = parseBss(reader)
|
||||
val variables = parseVariables(reader, options.dontReinitGlobals)
|
||||
val memorymapped = parseMemMapped(reader)
|
||||
val slabs = parseSlabs(reader)
|
||||
val initGlobals = parseInitGlobals(reader)
|
||||
val blocks = parseBlocksUntilProgramEnd(reader)
|
||||
|
||||
val st = IRSymbolTable(null)
|
||||
asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)}
|
||||
bss.forEach { st.add(it) }
|
||||
variables.forEach { st.add(it) }
|
||||
memorymapped.forEach { st.add(it) }
|
||||
slabs.forEach { st.add(it) }
|
||||
@ -50,26 +68,13 @@ class IRFileReader {
|
||||
return program
|
||||
}
|
||||
|
||||
private fun parseAsmSymbols(lines: Iterator<String>): Map<String, String> {
|
||||
val symbols = mutableMapOf<String, String>()
|
||||
var line = lines.next()
|
||||
while(line.isBlank())
|
||||
line = lines.next()
|
||||
if(line!="<ASMSYMBOLS>")
|
||||
throw IRParseException("invalid ASMSYMBOLS")
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line=="</ASMSYMBOLS>")
|
||||
return symbols
|
||||
val (name, value) = line.split('=')
|
||||
symbols[name] = value
|
||||
}
|
||||
}
|
||||
private fun parseOptions(reader: XMLEventReader): CompilationOptions {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="OPTIONS") { "missing OPTIONS" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
private fun parseOptions(lines: Iterator<String>): CompilationOptions {
|
||||
var line = lines.next()
|
||||
while(line.isBlank())
|
||||
line = lines.next()
|
||||
var target: ICompilationTarget = VMTarget()
|
||||
var outputType = OutputType.PRG
|
||||
var launcher = CbmPrgLauncherType.NONE
|
||||
@ -80,37 +85,35 @@ class IRFileReader {
|
||||
var optimize = true
|
||||
var evalStackBaseAddress: UInt? = null
|
||||
var outputDir = Path("")
|
||||
if(line!="<OPTIONS>")
|
||||
throw IRParseException("invalid OPTIONS")
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line=="</OPTIONS>")
|
||||
break
|
||||
val (name, value) = line.split('=', limit=2)
|
||||
when(name) {
|
||||
"compTarget" -> {
|
||||
target = when(value) {
|
||||
VMTarget.NAME -> VMTarget()
|
||||
C64Target.NAME -> C64Target()
|
||||
C128Target.NAME -> C128Target()
|
||||
AtariTarget.NAME -> AtariTarget()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
else -> throw IRParseException("invalid target $value")
|
||||
|
||||
if(text.isNotBlank()) {
|
||||
text.lineSequence().forEach { line ->
|
||||
val (name, value) = line.split('=', limit=2)
|
||||
when(name) {
|
||||
"compTarget" -> {
|
||||
target = when(value) {
|
||||
VMTarget.NAME -> VMTarget()
|
||||
C64Target.NAME -> C64Target()
|
||||
C128Target.NAME -> C128Target()
|
||||
AtariTarget.NAME -> AtariTarget()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
else -> throw IRParseException("invalid target $value")
|
||||
}
|
||||
}
|
||||
"output" -> outputType = OutputType.valueOf(value)
|
||||
"launcher" -> launcher = CbmPrgLauncherType.valueOf(value)
|
||||
"zeropage" -> zeropage = ZeropageType.valueOf(value)
|
||||
"loadAddress" -> loadAddress = value.toUInt()
|
||||
"dontReinitGlobals" -> dontReinitGlobals = value.toBoolean()
|
||||
"evalStackBaseAddress" -> evalStackBaseAddress = if(value=="null") null else parseIRValue(value).toUInt()
|
||||
"zpReserved" -> {
|
||||
val (zpstart, zpend) = value.split(',')
|
||||
zpReserved.add(UIntRange(zpstart.toUInt(), zpend.toUInt()))
|
||||
}
|
||||
"outputDir" -> outputDir = Path(value)
|
||||
"optimize" -> optimize = value.toBoolean()
|
||||
else -> throw IRParseException("illegal OPTION $name")
|
||||
}
|
||||
"output" -> outputType = OutputType.valueOf(value)
|
||||
"launcher" -> launcher = CbmPrgLauncherType.valueOf(value)
|
||||
"zeropage" -> zeropage = ZeropageType.valueOf(value)
|
||||
"loadAddress" -> loadAddress = value.toUInt()
|
||||
"dontReinitGlobals" -> dontReinitGlobals = value.toBoolean()
|
||||
"evalStackBaseAddress" -> evalStackBaseAddress = if(value=="null") null else value.toUInt()
|
||||
"zpReserved" -> {
|
||||
val (start, end) = value.split(',')
|
||||
zpReserved.add(UIntRange(start.toUInt(), end.toUInt()))
|
||||
}
|
||||
"outputDir" -> outputDir = Path(value)
|
||||
"optimize" -> optimize = value.toBoolean()
|
||||
else -> throw IRParseException("illegal OPTION $name")
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,85 +133,354 @@ class IRFileReader {
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseVariables(lines: Iterator<String>, dontReinitGlobals: Boolean): List<StStaticVariable> {
|
||||
var line = lines.next()
|
||||
while(line.isBlank())
|
||||
line = lines.next()
|
||||
if(line!="<VARIABLES>")
|
||||
throw IRParseException("invalid VARIABLES")
|
||||
val variables = mutableListOf<StStaticVariable>()
|
||||
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+)=(.*?) (zp=(.+))?")
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line=="</VARIABLES>")
|
||||
break
|
||||
// examples:
|
||||
// uword main.start.qq2=0 zp=REQUIRE_ZP
|
||||
// ubyte[6] main.start.namestring=105,114,109,101,110,0
|
||||
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
|
||||
val (type, arrayspec, name, value, _, zpwish) = match.destructured
|
||||
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
||||
val dt: DataType = parseDatatype(type, arraysize!=null)
|
||||
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
|
||||
val bss: Boolean
|
||||
var initNumeric: Double? = null
|
||||
var initArray: StArray? = null
|
||||
when(dt) {
|
||||
in NumericDatatypes -> {
|
||||
if(value.isBlank()) {
|
||||
require(!dontReinitGlobals)
|
||||
bss = true
|
||||
} else {
|
||||
require(dontReinitGlobals)
|
||||
bss = false
|
||||
initNumeric = value.toDouble()
|
||||
}
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
if(value.isBlank()) {
|
||||
bss = true
|
||||
initArray = emptyList()
|
||||
} else {
|
||||
bss = false
|
||||
initArray = value.split(',').map {
|
||||
if (it.startsWith('&'))
|
||||
StArrayElement(null, it.drop(1).split('.'))
|
||||
else
|
||||
StArrayElement(it.toDouble(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.STR -> throw IRParseException("STR should have been converted to byte array")
|
||||
else -> throw IRParseException("weird dt")
|
||||
}
|
||||
private fun parseAsmSymbols(reader: XMLEventReader): Map<String, String> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="ASMSYMBOLS") { "missing ASMSYMBOLS" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
|
||||
}
|
||||
return variables
|
||||
return if(text.isBlank())
|
||||
emptyMap()
|
||||
else
|
||||
text.lineSequence().associate {
|
||||
val (name, value) = it.split('=')
|
||||
name to value
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMemMapped(lines: Iterator<String>): List<StMemVar> {
|
||||
var line = lines.next()
|
||||
while(line.isBlank())
|
||||
line = lines.next()
|
||||
if(line!="<MEMORYMAPPEDVARIABLES>")
|
||||
throw IRParseException("invalid MEMORYMAPPEDVARIABLES")
|
||||
val memvars = mutableListOf<StMemVar>()
|
||||
val mappedPattern = Regex("&(.+?)(\\[.+?\\])? (.+)=(.+)")
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line=="</MEMORYMAPPEDVARIABLES>")
|
||||
break
|
||||
// examples:
|
||||
// &uword main.start.mapped=49152
|
||||
// &ubyte[20] main.start.mappedarray=49408
|
||||
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
|
||||
val (type, arrayspec, name, address) = match.destructured
|
||||
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
||||
val dt: DataType = parseDatatype(type, arraysize!=null)
|
||||
memvars.add(StMemVar(name, dt, address.toUInt(), arraysize, Position.DUMMY))
|
||||
private fun parseBss(reader: XMLEventReader): List<StStaticVariable> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="BSS") { "missing BSS" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+) (zp=(.+))?")
|
||||
val bssVariables = mutableListOf<StStaticVariable>()
|
||||
text.lineSequence().forEach { line ->
|
||||
// example: uword main.start.qq2 zp=DONTCARE
|
||||
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid BSS $line")
|
||||
val (type, arrayspec, name, _, zpwish) = match.destructured
|
||||
if('.' !in name)
|
||||
throw IRParseException("unscoped varname: $name")
|
||||
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
||||
val dt: DataType = parseDatatype(type, arraysize!=null)
|
||||
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
|
||||
bssVariables.add(StStaticVariable(name, dt, true, null, null, null, arraysize, zp, Position.DUMMY))
|
||||
}
|
||||
return bssVariables
|
||||
}
|
||||
return memvars
|
||||
}
|
||||
|
||||
private fun parseVariables(reader: XMLEventReader, dontReinitGlobals: Boolean): List<StStaticVariable> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="VARIABLES") { "missing VARIABLES" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+)=(.*?) (zp=(.+))?")
|
||||
val variables = mutableListOf<StStaticVariable>()
|
||||
text.lineSequence().forEach { line ->
|
||||
// examples:
|
||||
// uword main.start.qq2=0 zp=REQUIRE_ZP
|
||||
// ubyte[6] main.start.namestring=105,114,109,101,110,0
|
||||
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
|
||||
val (type, arrayspec, name, value, _, zpwish) = match.destructured
|
||||
if('.' !in name)
|
||||
throw IRParseException("unscoped varname: $name")
|
||||
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
||||
val dt: DataType = parseDatatype(type, arraysize!=null)
|
||||
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
|
||||
val bss: Boolean
|
||||
var initNumeric: Double? = null
|
||||
var initArray: StArray? = null
|
||||
when(dt) {
|
||||
in NumericDatatypes -> {
|
||||
if(value.isBlank()) {
|
||||
require(!dontReinitGlobals)
|
||||
bss = true
|
||||
} else {
|
||||
require(dontReinitGlobals)
|
||||
bss = false
|
||||
initNumeric = parseIRValue(value).toDouble()
|
||||
}
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
if(value.isBlank()) {
|
||||
bss = true
|
||||
initArray = emptyList()
|
||||
} else {
|
||||
bss = false
|
||||
initArray = value.split(',').map {
|
||||
if (it.startsWith('@'))
|
||||
StArrayElement(null, it.drop(1).split('.'))
|
||||
else
|
||||
StArrayElement(parseIRValue(it).toDouble(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.STR -> throw IRParseException("STR should have been converted to byte array")
|
||||
else -> throw IRParseException("weird dt")
|
||||
}
|
||||
require(!bss) { "bss var should be in BSS section" }
|
||||
variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
|
||||
}
|
||||
return variables
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMemMapped(reader: XMLEventReader): List<StMemVar> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="MEMORYMAPPEDVARIABLES") { "missing MEMORYMAPPEDVARIABLES" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
val memvars = mutableListOf<StMemVar>()
|
||||
val mappedPattern = Regex("@(.+?)(\\[.+?\\])? (.+)=(.+)")
|
||||
text.lineSequence().forEach { line ->
|
||||
// examples:
|
||||
// @uword main.start.mapped=49152
|
||||
// @ubyte[20] main.start.mappedarray=49408
|
||||
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
|
||||
val (type, arrayspec, name, address) = match.destructured
|
||||
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
||||
val dt: DataType = parseDatatype(type, arraysize!=null)
|
||||
memvars.add(StMemVar(name, dt, parseIRValue(address).toUInt(), arraysize, Position.DUMMY))
|
||||
}
|
||||
memvars
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSlabs(reader: XMLEventReader): List<StMemorySlab> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="MEMORYSLABS") { "missing MEMORYSLABS" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
val slabs = mutableListOf<StMemorySlab>()
|
||||
val slabPattern = Regex("SLAB (.+) (.+) (.+)")
|
||||
text.lineSequence().forEach { line ->
|
||||
// example: "SLAB slabname 4096 0"
|
||||
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid SLAB $line")
|
||||
val (name, size, align) = match.destructured
|
||||
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), Position.DUMMY))
|
||||
}
|
||||
slabs
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseInitGlobals(reader: XMLEventReader): IRCodeChunk {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="INITGLOBALS") { "missing INITGLOBALS" }
|
||||
skipText(reader)
|
||||
val chunk: IRCodeChunk = if(reader.peek().isStartElement)
|
||||
parseCodeChunk(reader)
|
||||
else
|
||||
IRCodeChunk(null, null)
|
||||
skipText(reader)
|
||||
require(reader.nextEvent().isEndElement)
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun parseCodeChunk(reader: XMLEventReader): IRCodeChunk {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="CODE") { "missing CODE" }
|
||||
val label = start.attributes.asSequence().singleOrNull { it.name.localPart == "LABEL" }?.value?.ifBlank { null }
|
||||
val text = readText(reader).trim()
|
||||
val chunk = IRCodeChunk(label, null)
|
||||
if(text.isNotBlank()) {
|
||||
text.lineSequence().forEach { line ->
|
||||
if (line.isNotBlank() && !line.startsWith(';')) {
|
||||
val result = parseIRCodeLine(line, null, mutableMapOf())
|
||||
result.fold(
|
||||
ifLeft = {
|
||||
chunk += it
|
||||
},
|
||||
ifRight = {
|
||||
throw IRParseException("code chunk should not contain a separate label line anymore, this should be the proper label of a new separate chunk")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
require(reader.nextEvent().isEndElement)
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun parseBlocksUntilProgramEnd(reader: XMLEventReader): List<IRBlock> {
|
||||
val blocks = mutableListOf<IRBlock>()
|
||||
skipText(reader)
|
||||
while(reader.peek().isStartElement) {
|
||||
blocks.add(parseBlock(reader))
|
||||
skipText(reader)
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
private fun parseBlock(reader: XMLEventReader): IRBlock {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="BLOCK") { "missing BLOCK" }
|
||||
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
|
||||
val block = IRBlock(
|
||||
attrs.getValue("NAME"),
|
||||
if(attrs.getValue("ADDRESS")=="null") null else parseIRValue(attrs.getValue("ADDRESS")).toUInt(),
|
||||
IRBlock.BlockAlignment.valueOf(attrs.getValue("ALIGN")),
|
||||
parsePosition(attrs.getValue("POS")))
|
||||
skipText(reader)
|
||||
while(reader.peek().isStartElement) {
|
||||
when(reader.peek().asStartElement().name.localPart) {
|
||||
"SUB" -> block += parseSubroutine(reader)
|
||||
"ASMSUB" -> block += parseAsmSubroutine(reader)
|
||||
"INLINEASM" -> block += parseInlineAssembly(reader)
|
||||
"BYTES" -> block += parseBinaryBytes(reader)
|
||||
"CODE" -> {
|
||||
val chunk = parseCodeChunk(reader)
|
||||
if(chunk.isNotEmpty() || chunk.label==null)
|
||||
throw IRParseException("code chunk in block should only contain a label name")
|
||||
block += chunk
|
||||
}
|
||||
else -> throw IRParseException("invalid line in BLOCK: ${reader.peek()}")
|
||||
}
|
||||
skipText(reader)
|
||||
}
|
||||
require(reader.nextEvent().isEndElement)
|
||||
return block
|
||||
}
|
||||
|
||||
private fun parseSubroutine(reader: XMLEventReader): IRSubroutine {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="SUB") { "missing SUB" }
|
||||
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
|
||||
val returntype = attrs.getValue("RETURNTYPE")
|
||||
skipText(reader)
|
||||
val sub = IRSubroutine(attrs.getValue("NAME"),
|
||||
parseParameters(reader),
|
||||
if(returntype=="null") null else parseDatatype(returntype, false),
|
||||
parsePosition(attrs.getValue("POS")))
|
||||
|
||||
skipText(reader)
|
||||
while(reader.peek().isStartElement) {
|
||||
when(reader.peek().asStartElement().name.localPart) {
|
||||
"CODE" -> sub += parseCodeChunk(reader)
|
||||
"BYTES" -> sub += parseBinaryBytes(reader)
|
||||
"INLINEASM" -> sub += parseInlineAssembly(reader)
|
||||
else -> throw IRParseException("invalid line in SUB: ${reader.peek()}")
|
||||
}
|
||||
skipText(reader)
|
||||
}
|
||||
|
||||
require(reader.nextEvent().isEndElement)
|
||||
return sub
|
||||
}
|
||||
|
||||
private fun parseParameters(reader: XMLEventReader): List<IRSubroutine.IRParam> {
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="PARAMS") { "missing PARAMS" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
text.lines().map { line ->
|
||||
val (datatype, name) = line.split(' ')
|
||||
val dt = parseDatatype(datatype, datatype.contains('['))
|
||||
// val orig = variables.single { it.dt==dt && it.name==name}
|
||||
IRSubroutine.IRParam(name, dt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseBinaryBytes(reader: XMLEventReader): IRInlineBinaryChunk {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="BYTES") { "missing BYTES" }
|
||||
val label = start.attributes.asSequence().singleOrNull { it.name.localPart == "LABEL" }?.value?.ifBlank { null }
|
||||
val text = readText(reader).replace("\n", "")
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
val bytes = text.windowed(2, step = 2).map { it.toUByte(16) }
|
||||
return IRInlineBinaryChunk(label, bytes, null)
|
||||
}
|
||||
|
||||
private fun parseAsmSubroutine(reader: XMLEventReader): IRAsmSubroutine {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="ASMSUB") { "missing ASMSUB" }
|
||||
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
|
||||
val params = parseAsmParameters(reader)
|
||||
val assembly = parseInlineAssembly(reader)
|
||||
skipText(reader)
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
val clobbers = attrs.getValue("CLOBBERS")
|
||||
val clobberRegs = if(clobbers.isBlank()) emptyList() else clobbers.split(',').map { CpuRegister.valueOf(it) }
|
||||
val returnsSpec = attrs.getValue("RETURNS")
|
||||
val returns = if(returnsSpec.isNullOrBlank()) emptyList() else returnsSpec.split(',').map { rs ->
|
||||
val (regstr, dtstr) = rs.split(':')
|
||||
val dt = parseDatatype(dtstr, false)
|
||||
val regsf = parseRegisterOrStatusflag(regstr)
|
||||
IRAsmSubroutine.IRAsmParam(regsf, dt)
|
||||
}
|
||||
return IRAsmSubroutine(
|
||||
attrs.getValue("NAME"),
|
||||
if(attrs.getValue("ADDRESS")=="null") null else parseIRValue(attrs.getValue("ADDRESS")).toUInt(),
|
||||
clobberRegs.toSet(),
|
||||
params,
|
||||
returns,
|
||||
assembly,
|
||||
parsePosition(attrs.getValue("POS"))
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseAsmParameters(reader: XMLEventReader): List<IRAsmSubroutine.IRAsmParam> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="ASMPARAMS") { "missing ASMPARAMS" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
text.lines().map { line ->
|
||||
val (datatype, regOrSf) = line.split(' ')
|
||||
val dt = parseDatatype(datatype, datatype.contains('['))
|
||||
val regsf = parseRegisterOrStatusflag(regOrSf)
|
||||
IRAsmSubroutine.IRAsmParam(regsf, dt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseInlineAssembly(reader: XMLEventReader): IRInlineAsmChunk {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="INLINEASM") { "missing INLINEASM" }
|
||||
val label = start.attributes.asSequence().single { it.name.localPart == "LABEL" }.value.ifBlank { null }
|
||||
val isIr = start.attributes.asSequence().single { it.name.localPart == "IR" }.value.toBoolean()
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
return IRInlineAsmChunk(label, text, isIr, null)
|
||||
}
|
||||
|
||||
private fun parseDatatype(type: String, isArray: Boolean): DataType {
|
||||
@ -236,238 +508,6 @@ class IRFileReader {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSlabs(lines: Iterator<String>): List<StMemorySlab> {
|
||||
var line = lines.next()
|
||||
while(line.isBlank())
|
||||
line = lines.next()
|
||||
if(line!="<MEMORYSLABS>")
|
||||
throw IRParseException("invalid MEMORYSLABS")
|
||||
val slabs = mutableListOf<StMemorySlab>()
|
||||
val slabPattern = Regex("SLAB (.+) (.+) (.+)")
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line=="</MEMORYSLABS>")
|
||||
break
|
||||
// example: "SLAB slabname 4096 0"
|
||||
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid SLAB $line")
|
||||
val (name, size, align) = match.destructured
|
||||
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), Position.DUMMY))
|
||||
}
|
||||
return slabs
|
||||
}
|
||||
|
||||
private fun parseInitGlobals(lines: Iterator<String>): IRCodeChunk {
|
||||
var line = lines.next()
|
||||
while(line.isBlank())
|
||||
line = lines.next()
|
||||
if(line!="<INITGLOBALS>")
|
||||
throw IRParseException("invalid INITGLOBALS")
|
||||
line = lines.next()
|
||||
var chunk = IRCodeChunk(null, null)
|
||||
if(line=="<C>") {
|
||||
chunk = parseCodeChunk(line, lines)!!
|
||||
line = lines.next()
|
||||
}
|
||||
if(line!="</INITGLOBALS>")
|
||||
throw IRParseException("missing INITGLOBALS close tag")
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun parseBlocksUntilProgramEnd(lines: Iterator<String>): List<IRBlock> {
|
||||
val blocks = mutableListOf<IRBlock>()
|
||||
while(true) {
|
||||
var line = lines.next()
|
||||
while (line.isBlank())
|
||||
line = lines.next()
|
||||
if (line == "</PROGRAM>")
|
||||
break
|
||||
blocks.add(parseBlock(line, lines))
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
private val blockPattern = Regex("<BLOCK NAME=(.+) ADDRESS=(.+) ALIGN=(.+) POS=(.+)>")
|
||||
private val inlineAsmPattern = Regex("<INLINEASM LABEL=(.*) IR=(.+)>")
|
||||
private val bytesPattern = Regex("<BYTES LABEL=(.*)>")
|
||||
private val asmsubPattern = Regex("<ASMSUB NAME=(.+) ADDRESS=(.+) CLOBBERS=(.*) RETURNS=(.*) POS=(.+)>")
|
||||
private val subPattern = Regex("<SUB NAME=(.+) RETURNTYPE=(.+) POS=(.+)>")
|
||||
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
|
||||
|
||||
private fun parseBlock(startline: String, lines: Iterator<String>): IRBlock {
|
||||
var line = startline
|
||||
if(!line.startsWith("<BLOCK "))
|
||||
throw IRParseException("invalid BLOCK")
|
||||
val match = blockPattern.matchEntire(line) ?: throw IRParseException("invalid BLOCK")
|
||||
val (name, address, align, position) = match.destructured
|
||||
val addressNum = if(address=="null") null else address.toUInt()
|
||||
val block = IRBlock(name, addressNum, IRBlock.BlockAlignment.valueOf(align), parsePosition(position))
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line.isBlank())
|
||||
continue
|
||||
if(line=="</BLOCK>")
|
||||
return block
|
||||
if(line.startsWith("<SUB ")) {
|
||||
val sub = parseSubroutine(line, lines)
|
||||
block += sub
|
||||
} else if(line.startsWith("<ASMSUB ")) {
|
||||
val sub = parseAsmSubroutine(line, lines)
|
||||
block += sub
|
||||
} else if(line.startsWith("<INLINEASM ")) {
|
||||
val asm = parseInlineAssembly(line, lines)
|
||||
block += asm
|
||||
} else
|
||||
throw IRParseException("invalid line in BLOCK")
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseInlineAssembly(startline: String, lines: Iterator<String>): IRInlineAsmChunk {
|
||||
// <INLINEASM LABEL=optional-label IR=true>
|
||||
val match = inlineAsmPattern.matchEntire(startline) ?: throw IRParseException("invalid INLINEASM")
|
||||
val label = match.groupValues[1]
|
||||
val isIr = match.groupValues[2].toBoolean()
|
||||
val asmlines = mutableListOf<String>()
|
||||
var line = lines.next()
|
||||
while(line!="</INLINEASM>") {
|
||||
asmlines.add(line)
|
||||
line = lines.next()
|
||||
}
|
||||
return IRInlineAsmChunk(label, asmlines.joinToString("\n"), isIr, null)
|
||||
}
|
||||
|
||||
private fun parseAsmSubroutine(startline: String, lines: Iterator<String>): IRAsmSubroutine {
|
||||
// <ASMSUB NAME=main.testasmsub ADDRESS=null CLOBBERS=A,Y POS=[examples/test.p8: line 14 col 6-11]>
|
||||
val match = asmsubPattern.matchEntire(startline) ?: throw IRParseException("invalid ASMSUB")
|
||||
val (scopedname, address, clobbers, returnSpec, pos) = match.destructured
|
||||
// parse PARAMS
|
||||
var line = lines.next()
|
||||
if(line!="<PARAMS>")
|
||||
throw IRParseException("missing PARAMS")
|
||||
val params = mutableListOf<Pair<DataType, RegisterOrStatusflag>>()
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line=="</PARAMS>")
|
||||
break
|
||||
val (datatype, regOrSf) = line.split(' ')
|
||||
val dt = parseDatatype(datatype, datatype.contains('['))
|
||||
val regsf = parseRegisterOrStatusflag(regOrSf)
|
||||
params += Pair(dt, regsf)
|
||||
}
|
||||
line = lines.next()
|
||||
val asm = parseInlineAssembly(line, lines)
|
||||
while(line!="</ASMSUB>")
|
||||
line = lines.next()
|
||||
val clobberRegs = if(clobbers.isBlank()) emptyList() else clobbers.split(',').map { CpuRegister.valueOf(it) }
|
||||
val returns = mutableListOf<Pair<DataType, RegisterOrStatusflag>>()
|
||||
returnSpec.split(',').forEach{ rs ->
|
||||
val (regstr, dtstr) = rs.split(':')
|
||||
val dt = parseDatatype(dtstr, false)
|
||||
val regsf = parseRegisterOrStatusflag(regstr)
|
||||
returns.add(Pair(dt, regsf))
|
||||
}
|
||||
return IRAsmSubroutine(
|
||||
scopedname,
|
||||
if(address=="null") null else address.toUInt(),
|
||||
clobberRegs.toSet(),
|
||||
params,
|
||||
returns,
|
||||
asm,
|
||||
parsePosition(pos)
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseSubroutine(startline: String, lines: Iterator<String>): IRSubroutine {
|
||||
// <SUB NAME=main.start.nested.nested2 RETURNTYPE=null POS=[examples/test.p8: line 54 col 14-16]>
|
||||
val match = subPattern.matchEntire(startline) ?: throw IRParseException("invalid SUB")
|
||||
val (name, returntype, pos) = match.destructured
|
||||
val sub = IRSubroutine(name,
|
||||
parseParameters(lines),
|
||||
if(returntype=="null") null else parseDatatype(returntype, false),
|
||||
parsePosition(pos))
|
||||
while(true) {
|
||||
val line = lines.next()
|
||||
if(line=="</SUB>")
|
||||
return sub
|
||||
val chunk = if(line.startsWith("<C"))
|
||||
parseCodeChunk(line, lines)
|
||||
else if(line.startsWith("<BYTES "))
|
||||
parseBinaryBytes(line, lines)
|
||||
else if(line.startsWith("<INLINEASM "))
|
||||
parseInlineAssembly(line, lines)
|
||||
else
|
||||
throw IRParseException("invalid sub child node")
|
||||
|
||||
if (chunk == null)
|
||||
break
|
||||
else
|
||||
sub += chunk
|
||||
}
|
||||
val line = lines.next()
|
||||
if(line=="</SUB>")
|
||||
throw IRParseException("missing SUB close tag")
|
||||
return sub
|
||||
}
|
||||
|
||||
private fun parseBinaryBytes(startline: String, lines: Iterator<String>): IRInlineBinaryChunk {
|
||||
val match = bytesPattern.matchEntire(startline) ?: throw IRParseException("invalid BYTES")
|
||||
val label = match.groupValues[1]
|
||||
val bytes = mutableListOf<UByte>()
|
||||
var line = lines.next()
|
||||
while(line!="</BYTES>") {
|
||||
line.trimEnd().windowed(size=2, step=2) {
|
||||
bytes.add(it.toString().toUByte(16))
|
||||
}
|
||||
line = lines.next()
|
||||
}
|
||||
return IRInlineBinaryChunk(label, bytes, null)
|
||||
}
|
||||
|
||||
private fun parseParameters(lines: Iterator<String>): List<IRSubroutine.IRParam> {
|
||||
var line = lines.next()
|
||||
if(line!="<PARAMS>")
|
||||
throw IRParseException("missing PARAMS")
|
||||
val params = mutableListOf<IRSubroutine.IRParam>()
|
||||
while(true) {
|
||||
line = lines.next()
|
||||
if(line=="</PARAMS>")
|
||||
return params
|
||||
val (datatype, name) = line.split(' ')
|
||||
val dt = parseDatatype(datatype, datatype.contains('['))
|
||||
// val orig = variables.single { it.dt==dt && it.name==name}
|
||||
params.add(IRSubroutine.IRParam(name, dt))
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseCodeChunk(firstline: String, lines: Iterator<String>): IRCodeChunk? {
|
||||
if(!firstline.startsWith("<C")) {
|
||||
if(firstline=="</SUB>")
|
||||
return null
|
||||
else
|
||||
throw IRParseException("invalid or empty <C>ODE chunk")
|
||||
}
|
||||
val label = if(firstline.startsWith("<C LABEL="))
|
||||
firstline.split('=', limit = 2)[1].dropLast(1)
|
||||
else
|
||||
null
|
||||
val chunk = IRCodeChunk(label, null)
|
||||
while(true) {
|
||||
val line = lines.next()
|
||||
if (line == "</C>")
|
||||
return chunk
|
||||
if (line.isBlank() || line.startsWith(';'))
|
||||
continue
|
||||
val result = parseIRCodeLine(line, null, mutableMapOf())
|
||||
result.fold(
|
||||
ifLeft = {
|
||||
chunk += it
|
||||
},
|
||||
ifRight = {
|
||||
throw IRParseException("code chunk should not contain a separate label line anymore, this should be the proper label of a new separate chunk")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseRegisterOrStatusflag(regs: String): RegisterOrStatusflag {
|
||||
var reg: RegisterOrPair? = null
|
||||
var sf: Statusflag? = null
|
||||
@ -479,6 +519,8 @@ class IRFileReader {
|
||||
return RegisterOrStatusflag(reg, sf)
|
||||
}
|
||||
|
||||
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
|
||||
|
||||
private fun parsePosition(strpos: String): Position {
|
||||
// example: "[library:/prog8lib/virtual/textio.p8: line 5 col 2-4]"
|
||||
val match = posPattern.matchEntire(strpos) ?: throw IRParseException("invalid Position")
|
||||
@ -486,4 +528,16 @@ class IRFileReader {
|
||||
return Position(file, line.toInt(), startCol.toInt(), endCol.toInt())
|
||||
}
|
||||
|
||||
private fun readText(reader: XMLEventReader): String {
|
||||
val sb = StringBuilder()
|
||||
while(reader.peek().isCharacters) {
|
||||
sb.append(reader.nextEvent().asCharacters().data)
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun skipText(reader: XMLEventReader) {
|
||||
while(reader.peek().isCharacters)
|
||||
reader.nextEvent()
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package prog8.intermediate
|
||||
|
||||
import prog8.code.core.ArrayDatatypes
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.core.NumericDatatypes
|
||||
import prog8.code.core.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.bufferedWriter
|
||||
import kotlin.io.path.div
|
||||
@ -11,17 +8,18 @@ import kotlin.io.path.div
|
||||
|
||||
class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
private val outfile = outfileOverride ?: (irProgram.options.outputDir / ("${irProgram.name}.p8ir"))
|
||||
private val out = outfile.bufferedWriter()
|
||||
private val out = outfile.bufferedWriter(charset=Charsets.UTF_8)
|
||||
private var numChunks = 0
|
||||
private var numInstr = 0
|
||||
|
||||
|
||||
fun write(): Path {
|
||||
println("Writing intermediate representation to $outfile")
|
||||
out.write("<PROGRAM NAME=${irProgram.name}>\n")
|
||||
out.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
|
||||
out.write("<PROGRAM NAME=\"${irProgram.name}\">\n")
|
||||
writeOptions()
|
||||
writeAsmSymbols()
|
||||
writeVariableAllocations()
|
||||
writeVariables()
|
||||
|
||||
out.write("\n<INITGLOBALS>\n")
|
||||
if(!irProgram.options.dontReinitGlobals) {
|
||||
@ -47,42 +45,46 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
|
||||
private fun writeBlocks() {
|
||||
irProgram.blocks.forEach { block ->
|
||||
out.write("\n<BLOCK NAME=${block.name} ADDRESS=${block.address} ALIGN=${block.alignment} POS=${block.position}>\n")
|
||||
block.inlineAssembly.forEach {
|
||||
writeInlineAsm(it)
|
||||
}
|
||||
block.subroutines.forEach {
|
||||
out.write("<SUB NAME=${it.name} RETURNTYPE=${it.returnType.toString().lowercase()} POS=${it.position}>\n")
|
||||
out.write("<PARAMS>\n")
|
||||
it.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") }
|
||||
out.write("</PARAMS>\n")
|
||||
it.chunks.forEach { chunk ->
|
||||
numChunks++
|
||||
when (chunk) {
|
||||
is IRInlineAsmChunk -> writeInlineAsm(chunk)
|
||||
is IRInlineBinaryChunk -> writeInlineBytes(chunk)
|
||||
is IRCodeChunk -> writeCodeChunk(chunk)
|
||||
else -> throw InternalCompilerException("invalid chunk")
|
||||
out.write("\n<BLOCK NAME=\"${block.name}\" ADDRESS=\"${block.address?.toHex()}\" ALIGN=\"${block.alignment}\" POS=\"${block.position}\">\n")
|
||||
block.children.forEach { child ->
|
||||
when(child) {
|
||||
is IRAsmSubroutine -> {
|
||||
val clobbers = child.clobbers.joinToString(",")
|
||||
val returns = child.returns.map { ret ->
|
||||
if(ret.reg.registerOrPair!=null) "${ret.reg.registerOrPair}:${ret.dt.toString().lowercase()}"
|
||||
else "${ret.reg.statusflag}:${ret.dt.toString().lowercase()}"
|
||||
}.joinToString(",")
|
||||
out.write("<ASMSUB NAME=\"${child.label}\" ADDRESS=\"${child.address?.toHex()}\" CLOBBERS=\"$clobbers\" RETURNS=\"$returns\" POS=\"${child.position}\">\n")
|
||||
out.write("<ASMPARAMS>\n")
|
||||
child.parameters.forEach { ret ->
|
||||
val reg = if(ret.reg.registerOrPair!=null) ret.reg.registerOrPair.toString()
|
||||
else ret.reg.statusflag.toString()
|
||||
out.write("${ret.dt.toString().lowercase()} $reg\n")
|
||||
}
|
||||
out.write("</ASMPARAMS>\n")
|
||||
writeInlineAsm(child.asmChunk)
|
||||
out.write("</ASMSUB>\n")
|
||||
}
|
||||
is IRCodeChunk -> writeCodeChunk(child)
|
||||
is IRInlineAsmChunk -> writeInlineAsm(child)
|
||||
is IRInlineBinaryChunk -> writeInlineBytes(child)
|
||||
is IRSubroutine -> {
|
||||
out.write("<SUB NAME=\"${child.label}\" RETURNTYPE=\"${child.returnType.toString().lowercase()}\" POS=\"${child.position}\">\n")
|
||||
out.write("<PARAMS>\n")
|
||||
child.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") }
|
||||
out.write("</PARAMS>\n")
|
||||
child.chunks.forEach { chunk ->
|
||||
numChunks++
|
||||
when (chunk) {
|
||||
is IRInlineAsmChunk -> writeInlineAsm(chunk)
|
||||
is IRInlineBinaryChunk -> writeInlineBytes(chunk)
|
||||
is IRCodeChunk -> writeCodeChunk(chunk)
|
||||
else -> throw InternalCompilerException("invalid chunk")
|
||||
}
|
||||
}
|
||||
out.write("</SUB>\n")
|
||||
}
|
||||
}
|
||||
out.write("</SUB>\n")
|
||||
}
|
||||
block.asmSubroutines.forEach {
|
||||
val clobbers = it.clobbers.joinToString(",")
|
||||
val returns = it.returns.map { (dt, reg) ->
|
||||
if(reg.registerOrPair!=null) "${reg.registerOrPair}:${dt.toString().lowercase()}"
|
||||
else "${reg.statusflag}:${dt.toString().lowercase()}"
|
||||
}.joinToString(",")
|
||||
out.write("<ASMSUB NAME=${it.name} ADDRESS=${it.address} CLOBBERS=$clobbers RETURNS=$returns POS=${it.position}>\n")
|
||||
out.write("<PARAMS>\n")
|
||||
it.parameters.forEach { (dt, regOrSf) ->
|
||||
val reg = if(regOrSf.registerOrPair!=null) regOrSf.registerOrPair.toString()
|
||||
else regOrSf.statusflag.toString()
|
||||
out.write("${dt.toString().lowercase()} $reg\n")
|
||||
}
|
||||
out.write("</PARAMS>\n")
|
||||
writeInlineAsm(it.asmChunk)
|
||||
out.write("</ASMSUB>\n")
|
||||
}
|
||||
out.write("</BLOCK>\n")
|
||||
}
|
||||
@ -90,19 +92,19 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
|
||||
private fun writeCodeChunk(chunk: IRCodeChunk) {
|
||||
if(chunk.label!=null)
|
||||
out.write("<C LABEL=${chunk.label}>\n")
|
||||
out.write("<CODE LABEL=\"${chunk.label}\">\n")
|
||||
else
|
||||
out.write("<C>\n")
|
||||
out.write("<CODE>\n")
|
||||
chunk.instructions.forEach { instr ->
|
||||
numInstr++
|
||||
out.write(instr.toString())
|
||||
out.write("\n")
|
||||
}
|
||||
out.write("</C>\n")
|
||||
out.write("</CODE>\n")
|
||||
}
|
||||
|
||||
private fun writeInlineBytes(chunk: IRInlineBinaryChunk) {
|
||||
out.write("<BYTES LABEL=${chunk.label ?: ""}>\n")
|
||||
out.write("<BYTES LABEL=\"${chunk.label ?: ""}\">\n")
|
||||
chunk.data.withIndex().forEach {(index, byte) ->
|
||||
out.write(byte.toString(16).padStart(2,'0'))
|
||||
if(index and 63 == 63 && index < chunk.data.size-1)
|
||||
@ -112,7 +114,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
}
|
||||
|
||||
private fun writeInlineAsm(chunk: IRInlineAsmChunk) {
|
||||
out.write("<INLINEASM LABEL=${chunk.label ?: ""} IR=${chunk.isIR}>\n")
|
||||
out.write("<INLINEASM LABEL=\"${chunk.label ?: ""}\" IR=\"${chunk.isIR}\">\n")
|
||||
out.write(chunk.assembly)
|
||||
out.write("\n</INLINEASM>\n")
|
||||
}
|
||||
@ -126,23 +128,30 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
for(range in irProgram.options.zpReserved) {
|
||||
out.write("zpReserved=${range.first},${range.last}\n")
|
||||
}
|
||||
out.write("loadAddress=${irProgram.options.loadAddress}\n")
|
||||
out.write("loadAddress=${irProgram.options.loadAddress.toHex()}\n")
|
||||
out.write("optimize=${irProgram.options.optimize}\n")
|
||||
out.write("dontReinitGlobals=${irProgram.options.dontReinitGlobals}\n")
|
||||
out.write("evalStackBaseAddress=${irProgram.options.evalStackBaseAddress}\n")
|
||||
out.write("evalStackBaseAddress=${irProgram.options.evalStackBaseAddress?.toHex()}\n")
|
||||
out.write("outputDir=${irProgram.options.outputDir.toAbsolutePath()}\n")
|
||||
// other options not yet useful here?
|
||||
out.write("</OPTIONS>\n")
|
||||
}
|
||||
|
||||
private fun writeVariableAllocations() {
|
||||
private fun writeVariables() {
|
||||
|
||||
out.write("\n<VARIABLES>\n")
|
||||
for (variable in irProgram.st.allVariables()) {
|
||||
out.write("\n<BSS>\n")
|
||||
for (variable in irProgram.st.allVariables().filter { it.bss }) {
|
||||
val typeStr = getTypeString(variable)
|
||||
// bss variables have no initialization value
|
||||
out.write("$typeStr ${variable.name} zp=${variable.zpwish}\n")
|
||||
}
|
||||
|
||||
out.write("</BSS>\n<VARIABLES>\n")
|
||||
for (variable in irProgram.st.allVariables().filter { !it.bss }) {
|
||||
val typeStr = getTypeString(variable)
|
||||
val value: String = when(variable.dt) {
|
||||
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString()
|
||||
in NumericDatatypes -> (variable.onetimeInitializationNumericValue?.toInt() ?: "").toString()
|
||||
in NumericDatatypes -> (variable.onetimeInitializationNumericValue?.toInt()?.toHex() ?: "").toString()
|
||||
DataType.STR -> {
|
||||
val encoded = irProgram.encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
|
||||
encoded.joinToString(",") { it.toInt().toString() }
|
||||
@ -158,9 +167,9 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
if(variable.onetimeInitializationArrayValue!==null) {
|
||||
variable.onetimeInitializationArrayValue!!.joinToString(",") {
|
||||
if(it.number!=null)
|
||||
it.number!!.toInt().toString()
|
||||
it.number!!.toInt().toHex()
|
||||
else
|
||||
"&${it.addressOf!!.joinToString(".")}"
|
||||
"@${it.addressOf!!.joinToString(".")}"
|
||||
}
|
||||
} else {
|
||||
"" // array will be zero'd out at program start
|
||||
@ -175,7 +184,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
out.write("\n<MEMORYMAPPEDVARIABLES>\n")
|
||||
for (variable in irProgram.st.allMemMappedVariables()) {
|
||||
val typeStr = getTypeString(variable)
|
||||
out.write("&$typeStr ${variable.name}=${variable.address}\n")
|
||||
out.write("@$typeStr ${variable.name}=${variable.address.toHex()}\n")
|
||||
}
|
||||
out.write("</MEMORYMAPPEDVARIABLES>\n")
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package prog8.intermediate
|
||||
|
||||
import prog8.code.core.toHex
|
||||
|
||||
/*
|
||||
|
||||
Intermediate Representation instructions for the IR Virtual machine.
|
||||
@ -519,8 +521,8 @@ data class InstructionFormat(val datatype: IRDataType?,
|
||||
/*
|
||||
<X = X is not modified (input/readonly value)
|
||||
>X = X is overwritten with output value (output value)
|
||||
<>X = X is modified (input + output)
|
||||
TODO: also encode if memory is read/written/modified?
|
||||
<>X = X is modified (read as input + written as output)
|
||||
TODO: also encode if *memory* is read/written/modified?
|
||||
*/
|
||||
@Suppress("BooleanLiteralArgument")
|
||||
val instructionFormats = mutableMapOf(
|
||||
@ -807,7 +809,7 @@ data class IRInstruction(
|
||||
result.add(",")
|
||||
}
|
||||
value?.let {
|
||||
result.add(it.toString())
|
||||
result.add(it.toHex())
|
||||
result.add(",")
|
||||
}
|
||||
fpValue?.let {
|
||||
|
@ -70,29 +70,45 @@ class IRProgram(val name: String,
|
||||
|
||||
fun linkChunks() {
|
||||
fun getLabeledChunks(): Map<String?, IRCodeChunkBase> {
|
||||
return blocks.flatMap { it.subroutines }.flatMap { it.chunks }.associateBy { it.label } +
|
||||
blocks.flatMap { it.asmSubroutines }.map { it.asmChunk }.associateBy { it.label }
|
||||
val result = mutableMapOf<String?, IRCodeChunkBase>()
|
||||
blocks.forEach { block ->
|
||||
block.children.forEach { child ->
|
||||
when(child) {
|
||||
is IRAsmSubroutine -> result[child.asmChunk.label] = child.asmChunk
|
||||
is IRCodeChunk -> result[child.label] = child
|
||||
is IRInlineAsmChunk -> result[child.label] = child
|
||||
is IRInlineBinaryChunk -> result[child.label] = child
|
||||
is IRSubroutine -> result.putAll(child.chunks.associateBy { it.label })
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
val labeledChunks = getLabeledChunks()
|
||||
|
||||
if(globalInits.isNotEmpty()) {
|
||||
if(globalInits.next==null) {
|
||||
// link globalinits to subsequent chunk
|
||||
val firstBlock = blocks.firstOrNull()
|
||||
if(firstBlock!=null) {
|
||||
if(firstBlock.inlineAssembly.isNotEmpty()) {
|
||||
globalInits.next = firstBlock.inlineAssembly.first()
|
||||
} else if(firstBlock.subroutines.isNotEmpty()) {
|
||||
val firstSub = firstBlock.subroutines.first()
|
||||
if(firstSub.chunks.isNotEmpty())
|
||||
globalInits.next = firstSub.chunks.first()
|
||||
if(firstBlock!=null && firstBlock.isNotEmpty()) {
|
||||
firstBlock.children.forEach { child ->
|
||||
when(child) {
|
||||
is IRAsmSubroutine -> throw AssemblyError("cannot link next to asmsub $child")
|
||||
is IRCodeChunk -> globalInits.next = child
|
||||
is IRInlineAsmChunk -> globalInits.next = child
|
||||
is IRInlineBinaryChunk -> globalInits.next = child
|
||||
is IRSubroutine -> {
|
||||
if(child.chunks.isNotEmpty())
|
||||
globalInits.next = child.chunks.first()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
||||
|
||||
fun linkSubroutineChunks(sub: IRSubroutine) {
|
||||
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||
|
||||
fun nextChunk(): IRCodeChunkBase? = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null
|
||||
@ -109,8 +125,10 @@ class IRProgram(val name: String,
|
||||
chunk.next = next
|
||||
else if(next is IRInlineAsmChunk)
|
||||
chunk.next = next
|
||||
else if(next is IRInlineBinaryChunk)
|
||||
chunk.next =next
|
||||
else
|
||||
throw AssemblyError("code chunk flows into following non-executable chunk")
|
||||
throw AssemblyError("code chunk followed by invalid chunk type $next")
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,36 +152,46 @@ class IRProgram(val name: String,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks.forEach { block ->
|
||||
block.children.forEachIndexed { index, child ->
|
||||
val next = if(index<block.children.size-1) block.children[index+1] as? IRCodeChunkBase else null
|
||||
when (child) {
|
||||
is IRAsmSubroutine -> child.asmChunk.next = next
|
||||
is IRCodeChunk -> child.next = next
|
||||
is IRInlineAsmChunk -> child.next = next
|
||||
is IRInlineBinaryChunk -> child.next = next
|
||||
is IRSubroutine -> linkSubroutineChunks(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun validate() {
|
||||
blocks.forEach { block ->
|
||||
if(block.inlineAssembly.isNotEmpty()) {
|
||||
require(block.inlineAssembly.first().label == block.name) { "first block chunk should have block name as its label" }
|
||||
}
|
||||
block.inlineAssembly.forEach { chunk ->
|
||||
require(chunk.instructions.isEmpty())
|
||||
}
|
||||
block.subroutines.forEach { sub ->
|
||||
if(sub.chunks.isNotEmpty()) {
|
||||
require(sub.chunks.first().label == sub.name) { "first chunk in subroutine should have sub name as its label" }
|
||||
}
|
||||
sub.chunks.forEach { chunk ->
|
||||
if (chunk is IRCodeChunk) {
|
||||
require(chunk.instructions.isNotEmpty() || chunk.label != null)
|
||||
if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump)
|
||||
require(chunk.next == null) { "chunk ending with a jump shouldn't be linked to next" }
|
||||
else {
|
||||
// if chunk is NOT the last in the block, it needs to link to next.
|
||||
val isLast = sub.chunks.last() === chunk
|
||||
require(isLast || chunk.next != null) { "chunk needs to be linked to next" }
|
||||
}
|
||||
if(block.isNotEmpty()) {
|
||||
block.children.filterIsInstance<IRInlineAsmChunk>().forEach { chunk -> require(chunk.instructions.isEmpty()) }
|
||||
block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
|
||||
if(sub.chunks.isNotEmpty()) {
|
||||
require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" }
|
||||
}
|
||||
else
|
||||
require(chunk.instructions.isEmpty())
|
||||
chunk.instructions.forEach {
|
||||
if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch)
|
||||
require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" }
|
||||
sub.chunks.forEach { chunk ->
|
||||
if (chunk is IRCodeChunk) {
|
||||
require(chunk.instructions.isNotEmpty() || chunk.label != null)
|
||||
if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump)
|
||||
require(chunk.next == null) { "chunk ending with a jump shouldn't be linked to next" }
|
||||
else {
|
||||
// if chunk is NOT the last in the block, it needs to link to next.
|
||||
val isLast = sub.chunks.last() === chunk
|
||||
require(isLast || chunk.next != null) { "chunk needs to be linked to next" }
|
||||
}
|
||||
}
|
||||
else
|
||||
require(chunk.instructions.isEmpty())
|
||||
chunk.instructions.forEach {
|
||||
if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch)
|
||||
require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -184,10 +212,16 @@ class IRProgram(val name: String,
|
||||
}
|
||||
|
||||
globalInits.instructions.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) }
|
||||
blocks.forEach {
|
||||
it.inlineAssembly.forEach { chunk -> addUsed(chunk.usedRegisters()) }
|
||||
it.subroutines.flatMap { sub->sub.chunks }.forEach { chunk -> addUsed(chunk.usedRegisters()) }
|
||||
it.asmSubroutines.forEach { asmsub -> addUsed(asmsub.usedRegisters()) }
|
||||
blocks.forEach {block ->
|
||||
block.children.forEach { child ->
|
||||
when(child) {
|
||||
is IRAsmSubroutine -> addUsed(child.usedRegisters())
|
||||
is IRCodeChunk -> addUsed(child.usedRegisters())
|
||||
is IRInlineAsmChunk -> addUsed(child.usedRegisters())
|
||||
is IRInlineBinaryChunk -> addUsed(child.usedRegisters())
|
||||
is IRSubroutine -> child.chunks.forEach { chunk -> addUsed(chunk.usedRegisters()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs)
|
||||
@ -200,9 +234,8 @@ class IRBlock(
|
||||
val alignment: BlockAlignment,
|
||||
val position: Position
|
||||
) {
|
||||
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
|
||||
val subroutines = mutableListOf<IRSubroutine>()
|
||||
val asmSubroutines = mutableListOf<IRAsmSubroutine>()
|
||||
// TODO not separate lists but just a single list of chunks, like IRSubroutine? (but these are not all chunks...)
|
||||
val children = mutableListOf<IIRBlockElement>()
|
||||
|
||||
enum class BlockAlignment {
|
||||
NONE,
|
||||
@ -210,32 +243,41 @@ class IRBlock(
|
||||
PAGE
|
||||
}
|
||||
|
||||
operator fun plusAssign(sub: IRSubroutine) {
|
||||
subroutines += sub
|
||||
operator fun plusAssign(sub: IRSubroutine) { children += sub }
|
||||
operator fun plusAssign(sub: IRAsmSubroutine) { children += sub }
|
||||
operator fun plusAssign(asm: IRInlineAsmChunk) { children += asm }
|
||||
operator fun plusAssign(binary: IRInlineBinaryChunk) { children += binary }
|
||||
operator fun plusAssign(irCodeChunk: IRCodeChunk) {
|
||||
// this is for a separate label in the block scope. (random code statements are not allowed)
|
||||
require(irCodeChunk.isEmpty() && irCodeChunk.label!=null)
|
||||
children += irCodeChunk
|
||||
}
|
||||
operator fun plusAssign(sub: IRAsmSubroutine) { asmSubroutines += sub }
|
||||
operator fun plusAssign(asm: IRInlineAsmChunk) { inlineAssembly += asm }
|
||||
|
||||
fun isEmpty(): Boolean {
|
||||
val noAsm = inlineAssembly.isEmpty() || inlineAssembly.all { it.isEmpty() }
|
||||
val noSubs = subroutines.isEmpty() || subroutines.all { it.isEmpty() }
|
||||
val noAsmSubs = asmSubroutines.isEmpty() || asmSubroutines.all { it.isEmpty() }
|
||||
return noAsm && noSubs && noAsmSubs
|
||||
}
|
||||
fun isEmpty(): Boolean = children.isEmpty() || children.all { it.isEmpty() }
|
||||
fun isNotEmpty(): Boolean = !isEmpty()
|
||||
}
|
||||
|
||||
class IRSubroutine(val name: String,
|
||||
val parameters: List<IRParam>,
|
||||
val returnType: DataType?,
|
||||
val position: Position) {
|
||||
|
||||
sealed interface IIRBlockElement {
|
||||
val label: String?
|
||||
fun isEmpty(): Boolean
|
||||
fun isNotEmpty(): Boolean
|
||||
}
|
||||
|
||||
|
||||
class IRSubroutine(
|
||||
override val label: String,
|
||||
val parameters: List<IRParam>,
|
||||
val returnType: DataType?,
|
||||
val position: Position): IIRBlockElement {
|
||||
|
||||
class IRParam(val name: String, val dt: DataType)
|
||||
|
||||
val chunks = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
init {
|
||||
require('.' in name) {"subroutine name is not scoped: $name"}
|
||||
require(!name.startsWith("main.main.")) {"subroutine name invalid main prefix: $name"}
|
||||
require('.' in label) {"subroutine name is not scoped: $label"}
|
||||
require(!label.startsWith("main.main.")) {"subroutine name invalid main prefix: $label"}
|
||||
|
||||
// params and return value should not be str
|
||||
require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"}
|
||||
@ -249,34 +291,41 @@ class IRSubroutine(val name: String,
|
||||
chunks+= chunk
|
||||
}
|
||||
|
||||
fun isEmpty(): Boolean = chunks.isEmpty() || chunks.all { it.isEmpty() }
|
||||
override fun isEmpty(): Boolean = chunks.isEmpty() || chunks.all { it.isEmpty() }
|
||||
override fun isNotEmpty(): Boolean = !isEmpty()
|
||||
}
|
||||
|
||||
|
||||
class IRAsmSubroutine(
|
||||
val name: String,
|
||||
override val label: String,
|
||||
val address: UInt?,
|
||||
val clobbers: Set<CpuRegister>,
|
||||
val parameters: List<Pair<DataType, RegisterOrStatusflag>>,
|
||||
val returns: List<Pair<DataType, RegisterOrStatusflag>>,
|
||||
val parameters: List<IRAsmParam>,
|
||||
val returns: List<IRAsmParam>,
|
||||
val asmChunk: IRInlineAsmChunk,
|
||||
val position: Position
|
||||
) {
|
||||
): IIRBlockElement {
|
||||
|
||||
class IRAsmParam(val reg: RegisterOrStatusflag, val dt: DataType)
|
||||
|
||||
init {
|
||||
require('.' in name) { "subroutine name is not scoped: $name" }
|
||||
require(!name.startsWith("main.main.")) { "subroutine name invalid main prefix: $name" }
|
||||
require('.' in label) { "subroutine name is not scoped: $label" }
|
||||
require(!label.startsWith("main.main.")) { "subroutine name invalid main prefix: $label" }
|
||||
}
|
||||
|
||||
private val registersUsed by lazy { registersUsedInAssembly(asmChunk.isIR, asmChunk.assembly) }
|
||||
|
||||
fun usedRegisters() = registersUsed
|
||||
fun isEmpty(): Boolean = if(address==null) asmChunk.isEmpty() else false
|
||||
override fun isEmpty(): Boolean = if(address==null) asmChunk.isEmpty() else false
|
||||
override fun isNotEmpty(): Boolean = !isEmpty()
|
||||
}
|
||||
|
||||
sealed class IRCodeChunkBase(val label: String?, var next: IRCodeChunkBase?) {
|
||||
|
||||
sealed class IRCodeChunkBase(override val label: String?, var next: IRCodeChunkBase?): IIRBlockElement {
|
||||
val instructions = mutableListOf<IRInstruction>()
|
||||
|
||||
abstract fun isEmpty(): Boolean
|
||||
abstract fun isNotEmpty(): Boolean
|
||||
abstract override fun isEmpty(): Boolean
|
||||
abstract override fun isNotEmpty(): Boolean
|
||||
abstract fun usedRegisters(): RegistersUsed
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user