mirror of
https://github.com/irmen/prog8.git
synced 2025-06-22 04:24:05 +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).
|
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||||
*/
|
*/
|
||||||
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
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.
|
* The table as a flat mapping of scoped names to the StNode.
|
||||||
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
* 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) {
|
fun add(child: StNode) {
|
||||||
children[child.name] = child
|
children[child.name] = child
|
||||||
child.parent = this
|
child.parent = this
|
||||||
@ -201,18 +174,11 @@ class StStaticVariable(name: String,
|
|||||||
require(length == onetimeInitializationStringValue.first.length+1)
|
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) :
|
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
|
||||||
StNode(name, StNodeType.CONSTANT, 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
|
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||||
position: Position) :
|
position: Position) :
|
||||||
StNode(name, StNodeType.MEMVAR, position) {
|
StNode(name, StNodeType.MEMVAR, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$name dt=$dt address=${address.toHex()}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StMemorySlab(
|
class StMemorySlab(
|
||||||
@ -234,16 +197,10 @@ class StMemorySlab(
|
|||||||
position: Position
|
position: Position
|
||||||
):
|
):
|
||||||
StNode(name, StNodeType.MEMORYSLAB, 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) :
|
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
||||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
StNode(name, StNodeType.SUBROUTINE, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print(name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -253,9 +210,6 @@ class StRomSub(name: String,
|
|||||||
val returns: List<RegisterOrStatusflag>,
|
val returns: List<RegisterOrStatusflag>,
|
||||||
position: Position) :
|
position: Position) :
|
||||||
StNode(name, StNodeType.ROMSUB, 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 asmQuiet: Boolean = false,
|
||||||
var asmListfile: Boolean = false,
|
var asmListfile: Boolean = false,
|
||||||
var experimentalCodegen: Boolean = false,
|
var experimentalCodegen: Boolean = false,
|
||||||
var keepIR: Boolean = false,
|
|
||||||
var evalStackBaseAddress: UInt? = null,
|
var evalStackBaseAddress: UInt? = null,
|
||||||
var outputDir: Path = Path(""),
|
var outputDir: Path = Path(""),
|
||||||
var symbolDefs: Map<String, String> = emptyMap()
|
var symbolDefs: Map<String, String> = emptyMap()
|
||||||
|
@ -17,11 +17,11 @@ interface IMachineDefinition {
|
|||||||
var ESTACK_HI: UInt
|
var ESTACK_HI: UInt
|
||||||
val PROGRAM_LOAD_ADDRESS : UInt
|
val PROGRAM_LOAD_ADDRESS : UInt
|
||||||
|
|
||||||
val opcodeNames: Set<String>
|
|
||||||
var zeropage: Zeropage
|
|
||||||
val cpu: CpuType
|
val cpu: CpuType
|
||||||
|
var zeropage: Zeropage
|
||||||
|
var golden: GoldenRam
|
||||||
|
|
||||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||||
fun getFloatAsmBytes(num: Number): String
|
fun getFloatAsmBytes(num: Number): String
|
||||||
|
|
||||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||||
@ -31,5 +31,7 @@ interface IMachineDefinition {
|
|||||||
require(evalStackBaseAddress and 255u == 0u)
|
require(evalStackBaseAddress and 255u == 0u)
|
||||||
ESTACK_LO = evalStackBaseAddress
|
ESTACK_LO = evalStackBaseAddress
|
||||||
ESTACK_HI = evalStackBaseAddress + 256u
|
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
|
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_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_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_W1 : UInt // temp storage 1 for a word $fb+$fc
|
||||||
abstract val SCRATCH_W2 : UInt // temp storage 2 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.
|
// the variables allocated into Zeropage.
|
||||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
// 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.
|
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 }
|
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allocate(name: List<String>,
|
override fun allocate(name: List<String>,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
errors: IErrorReporter
|
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||||
): Result<Pair<UInt, Int>, ZeropageAllocationError> {
|
|
||||||
|
|
||||||
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
||||||
|
|
||||||
if(options.zeropage== ZeropageType.DONTUSE)
|
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 =
|
val size: Int =
|
||||||
when (datatype) {
|
when (datatype) {
|
||||||
@ -72,9 +81,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
else
|
else
|
||||||
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
|
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
|
||||||
memsize
|
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) {
|
synchronized(this) {
|
||||||
@ -82,18 +91,18 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
if(size==1) {
|
if(size==1) {
|
||||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||||
if(oneSeparateByteFree(candidate))
|
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) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||||
if (sequentialFree(candidate, size))
|
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)
|
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())
|
free.removeAll(address until address+size.toUInt())
|
||||||
if(name.isNotEmpty()) {
|
if(name.isNotEmpty()) {
|
||||||
allocatedVariables[name] = when(datatype) {
|
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
|
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 -> ZpAllocation(address, datatype, size)
|
DataType.STR -> VarAllocation(address, datatype, size)
|
||||||
in ArrayDatatypes -> ZpAllocation(address, datatype, size)
|
in ArrayDatatypes -> VarAllocation(address, datatype, size)
|
||||||
else -> throw AssemblyError("invalid dt")
|
else -> throw AssemblyError("invalid dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,3 +129,37 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
abstract fun allocateCx16VirtualRegisters()
|
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
|
package prog8.code.target.atari
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.c64.normal6502instructions
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
@ -19,6 +18,7 @@ class AtariMachineDefinition: IMachineDefinition {
|
|||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
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 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)
|
zeropage = AtariZeropage(compilerOptions)
|
||||||
|
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val opcodeNames = normal6502instructions
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package prog8.code.target.c128
|
package prog8.code.target.c128
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.c64.normal6502instructions
|
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.nio.file.Path
|
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)
|
// 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_LO = 0x1a00u // $1a00-$1aff inclusive
|
||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
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 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)
|
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)
|
// 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_LO = 0xce00u // $ce00-$ceff inclusive
|
||||||
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
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 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)
|
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)
|
// 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).
|
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||||
for(reg in 0..15) {
|
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}")] = VarAllocation((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}s")] = VarAllocation((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}L")] = VarAllocation((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}H")] = VarAllocation((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}sL")] = VarAllocation((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}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
free.remove((4+reg*2).toUInt())
|
free.remove((4+reg*2).toUInt())
|
||||||
free.remove((5+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)
|
// 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_LO = 0x0400u // $0400-$04ff inclusive
|
||||||
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
@ -50,28 +50,16 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
println("\nStarting Commander X16 emulator $emulator...")
|
println("\nStarting Commander X16 emulator $emulator...")
|
||||||
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
|
||||||
val process: Process = processb.start()
|
val process: Process = processb.start()
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
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)
|
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.
|
// 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)
|
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||||
for(reg in 0..15) {
|
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}")] = VarAllocation((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}s")] = VarAllocation((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}L")] = VarAllocation((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}H")] = VarAllocation((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}sL")] = VarAllocation((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}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
package prog8.code.target.virtual
|
package prog8.code.target.virtual
|
||||||
|
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.*
|
||||||
import prog8.code.core.CpuType
|
|
||||||
import prog8.code.core.IMachineDefinition
|
|
||||||
import prog8.code.core.Zeropage
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.isReadable
|
import kotlin.io.path.isReadable
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
@ -20,8 +17,8 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override var ESTACK_LO = 0u // not actually used
|
override var ESTACK_LO = 0u // not actually used
|
||||||
override var ESTACK_HI = 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")
|
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 isIOAddress(address: UInt): Boolean = false
|
||||||
|
|
||||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {}
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
|
||||||
|
|
||||||
override val opcodeNames = emptySet<String>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IVirtualMachineRunner {
|
interface IVirtualMachineRunner {
|
||||||
fun runProgram(irSource: CharSequence)
|
fun runProgram(irSource: String)
|
||||||
}
|
}
|
||||||
|
@ -375,7 +375,7 @@ class AsmGen(internal val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
|
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||||
out(
|
out(
|
||||||
"""
|
"""
|
||||||
lda $indexName
|
lda $indexName
|
||||||
@ -406,7 +406,7 @@ class AsmGen(internal val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
|
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||||
out(
|
out(
|
||||||
"""
|
"""
|
||||||
lda $indexName
|
lda $indexName
|
||||||
@ -605,7 +605,7 @@ class AsmGen(internal val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
|
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")
|
val repeatLabel = program.makeLabel("repeat")
|
||||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||||
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
|
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
|
||||||
@ -667,7 +667,7 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
|
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")
|
val repeatLabel = program.makeLabel("repeat")
|
||||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||||
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
|
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
|
||||||
@ -794,7 +794,7 @@ $repeatLabel lda $counterVar
|
|||||||
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
|
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
|
||||||
throw AssemblyError("only else part contains code, shoud have been switched already")
|
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) {
|
if(jump!=null) {
|
||||||
// branch with only a jump (goto)
|
// branch with only a jump (goto)
|
||||||
val instruction = branchInstruction(stmt.condition, false)
|
val instruction = branchInstruction(stmt.condition, false)
|
||||||
@ -812,11 +812,13 @@ $repeatLabel lda $counterVar
|
|||||||
translate(stmt.elsepart)
|
translate(stmt.elsepart)
|
||||||
} else {
|
} else {
|
||||||
if(stmt.elsepart.isEmpty()) {
|
if(stmt.elsepart.isEmpty()) {
|
||||||
val instruction = branchInstruction(stmt.condition, true)
|
if(stmt.truepart.isNotEmpty()) {
|
||||||
val elseLabel = program.makeLabel("branch_else")
|
val instruction = branchInstruction(stmt.condition, true)
|
||||||
out(" $instruction $elseLabel")
|
val elseLabel = program.makeLabel("branch_else")
|
||||||
translate(stmt.truepart)
|
out(" $instruction $elseLabel")
|
||||||
out(elseLabel)
|
translate(stmt.truepart)
|
||||||
|
out(elseLabel)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val instruction = branchInstruction(stmt.condition, true)
|
val instruction = branchInstruction(stmt.condition, true)
|
||||||
val elseLabel = program.makeLabel("branch_else")
|
val elseLabel = program.makeLabel("branch_else")
|
||||||
@ -1068,11 +1070,20 @@ $repeatLabel lda $counterVar
|
|||||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
|
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||||
if(saveA)
|
if(saveA)
|
||||||
out(" pha")
|
out(" pha")
|
||||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(ptrAndIndex.second.isSimple) {
|
||||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
if(saveA)
|
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||||
out(" pla")
|
if(saveA)
|
||||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
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 {
|
} else {
|
||||||
if(pointervar!=null && isZpVar(pointervar)) {
|
if(pointervar!=null && isZpVar(pointervar)) {
|
||||||
@ -1080,9 +1091,16 @@ $repeatLabel lda $counterVar
|
|||||||
out(" lda (${asmSymbolName(pointervar)}),y")
|
out(" lda (${asmSymbolName(pointervar)}),y")
|
||||||
} else {
|
} else {
|
||||||
// copy the pointer var to zp first
|
// copy the pointer var to zp first
|
||||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(ptrAndIndex.second.isSimple) {
|
||||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
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
|
return true
|
||||||
@ -1576,8 +1594,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1611,8 +1635,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1648,8 +1678,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
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")
|
return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1687,8 +1724,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
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")
|
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1727,8 +1771,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1764,8 +1814,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1807,8 +1863,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
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")
|
return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1851,8 +1914,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleLeftOperand(left, right, ::code))
|
if(wordJumpForSimpleLeftOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(right.isSimple) {
|
||||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
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")
|
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1892,8 +1962,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1929,8 +2005,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1974,8 +2056,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
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")
|
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2022,8 +2111,15 @@ $repeatLabel lda $counterVar
|
|||||||
return code(asmVariableName(left))
|
return code(asmVariableName(left))
|
||||||
}
|
}
|
||||||
|
|
||||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(right.isSimple) {
|
||||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
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")
|
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2059,8 +2155,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2093,8 +2195,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2129,8 +2237,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(right.isSimple) {
|
||||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
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")
|
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2168,8 +2283,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
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")
|
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2204,8 +2326,17 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
out(" cmp P8ZP_SCRATCH_B1 | bne $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2241,8 +2372,17 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
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")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2308,8 +2448,18 @@ $repeatLabel lda $counterVar
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
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("""
|
out("""
|
||||||
cmp P8ZP_SCRATCH_W2
|
cmp P8ZP_SCRATCH_W2
|
||||||
bne $jumpIfFalseLabel
|
bne $jumpIfFalseLabel
|
||||||
@ -2384,8 +2534,18 @@ $repeatLabel lda $counterVar
|
|||||||
+""")
|
+""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
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("""
|
out("""
|
||||||
cmp P8ZP_SCRATCH_W2
|
cmp P8ZP_SCRATCH_W2
|
||||||
bne +
|
bne +
|
||||||
@ -2780,7 +2940,7 @@ $repeatLabel lda $counterVar
|
|||||||
val parameter = target.subroutineParameter
|
val parameter = target.subroutineParameter
|
||||||
if(parameter!=null) {
|
if(parameter!=null) {
|
||||||
val sub = parameter.definingSubroutine!!
|
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 shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
|
||||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||||
if(reg.statusflag!=null) {
|
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) {
|
internal fun pushCpuStack(dt: DataType, value: Expression) {
|
||||||
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
|
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
|
||||||
if(dt in ByteDatatypes) {
|
if(dt in ByteDatatypes) {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.A, signed)
|
assignExpressionToRegister(value, RegisterOrPair.A, signed)
|
||||||
out(" pha")
|
out(" pha")
|
||||||
} else {
|
} else if(dt in WordDatatypes) {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
|
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
|
||||||
if (isTargetCpu(CpuType.CPU65c02))
|
if (isTargetCpu(CpuType.CPU65c02))
|
||||||
out(" pha | phy")
|
out(" pha | phy")
|
||||||
else
|
else
|
||||||
out(" pha | tya | pha")
|
out(" pha | tya | pha")
|
||||||
|
} else {
|
||||||
|
throw AssemblyError("can't push $dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
|||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods= optimizeJsrRts(linesByFour)
|
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
@ -486,8 +486,11 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
|
|||||||
return mods
|
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
|
// jsr Sub + rts -> jmp Sub
|
||||||
|
// rts + jmp -> remove jmp
|
||||||
|
// rts + bxx -> remove bxx
|
||||||
|
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (lines in linesByFour) {
|
for (lines in linesByFour) {
|
||||||
val first = lines[0].value
|
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[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||||
mods += Modification(lines[1].index, true, null)
|
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
|
return mods
|
||||||
}
|
}
|
||||||
|
@ -254,15 +254,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
||||||
} else {
|
} else {
|
||||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
if(arg1.isSimple) {
|
||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
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 -> {
|
else -> {
|
||||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
if(arg1.isSimple) {
|
||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
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
|
} else
|
||||||
@ -288,13 +300,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
+""")
|
+""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
if(arg1.isSimple) {
|
||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||||
asmgen.out("""
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||||
cpy P8ZP_SCRATCH_W1+1
|
asmgen.out("""
|
||||||
bne +
|
cpy P8ZP_SCRATCH_W1+1
|
||||||
cmp P8ZP_SCRATCH_W1
|
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
|
} else
|
||||||
@ -306,7 +330,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
if(discardResult || fcall !is BuiltinFunctionCall)
|
if(discardResult || fcall !is BuiltinFunctionCall)
|
||||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||||
val name = (fcall.args[0] as StringLiteral).value
|
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)
|
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
|
||||||
slabname.linkParents(fcall)
|
slabname.linkParents(fcall)
|
||||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
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
|
// 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.
|
// 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())
|
if(callee.parameters.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -202,16 +202,10 @@ internal class ProgramAndVarsGen(
|
|||||||
asmsubs2asm(block.statements)
|
asmsubs2asm(block.statements)
|
||||||
|
|
||||||
asmgen.out("")
|
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 initializers = blockVariableInitializers.getValue(block)
|
||||||
val statements = block.statements.filterNot { it in initializers }
|
val notInitializers = block.statements.filterNot { it in initializers }
|
||||||
val (subroutine, stmts) = statements.partition { it is Subroutine }
|
notInitializers.forEach { asmgen.translate(it) }
|
||||||
stmts.forEach { asmgen.translate(it) }
|
|
||||||
subroutine.forEach { asmgen.translate(it) }
|
|
||||||
|
|
||||||
if(!options.dontReinitGlobals) {
|
if(!options.dontReinitGlobals) {
|
||||||
// generate subroutine to initialize block-level (global) variables
|
// generate subroutine to initialize block-level (global) variables
|
||||||
@ -435,13 +429,13 @@ internal class ProgramAndVarsGen(
|
|||||||
|
|
||||||
private class ZpStringWithInitial(
|
private class ZpStringWithInitial(
|
||||||
val name: List<String>,
|
val name: List<String>,
|
||||||
val alloc: Zeropage.ZpAllocation,
|
val alloc: MemoryAllocator.VarAllocation,
|
||||||
val value: Pair<String, Encoding>
|
val value: Pair<String, Encoding>
|
||||||
)
|
)
|
||||||
|
|
||||||
private class ZpArrayWithInitial(
|
private class ZpArrayWithInitial(
|
||||||
val name: List<String>,
|
val name: List<String>,
|
||||||
val alloc: Zeropage.ZpAllocation,
|
val alloc: MemoryAllocator.VarAllocation,
|
||||||
val value: StArray
|
val value: StArray
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
|
|
||||||
private val zeropage = options.compTarget.machine.zeropage
|
private val zeropage = options.compTarget.machine.zeropage
|
||||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
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 {
|
init {
|
||||||
allocateZeropageVariables()
|
allocateZeropageVariables()
|
||||||
|
@ -207,7 +207,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
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)) {
|
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||||
"source dt size must be less or equal to target dt size at $position"
|
"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)
|
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) {
|
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||||
if(assign.isAugmentable) {
|
if(assign.isAugmentable) {
|
||||||
augmentableAsmGen.translate(assign)
|
augmentableAsmGen.translate(assign)
|
||||||
@ -299,7 +292,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
)
|
)
|
||||||
when (value.operator) {
|
when (value.operator) {
|
||||||
"+" -> {}
|
"+" -> {}
|
||||||
"-" -> augmentableAsmGen.inplaceNegate(assign)
|
"-" -> augmentableAsmGen.inplaceNegate(assign, true)
|
||||||
"~" -> augmentableAsmGen.inplaceInvert(assign)
|
"~" -> augmentableAsmGen.inplaceInvert(assign)
|
||||||
else -> throw AssemblyError("invalid prefix operator")
|
else -> throw AssemblyError("invalid prefix operator")
|
||||||
}
|
}
|
||||||
@ -385,48 +378,60 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
if(!expr.inferType(program).isInteger)
|
if(!expr.inferType(program).isInteger)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
|
fun simpleLogicalBytesExpr() {
|
||||||
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
|
// both left and right expression operands are simple.
|
||||||
expr.left.isSimple && expr.right.isSimple) {
|
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||||
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
|
||||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
|
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||||
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
|
||||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
|
else {
|
||||||
else {
|
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
||||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
when (expr.operator) {
|
||||||
when (expr.operator) {
|
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||||
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||||
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
else -> throw AssemblyError("invalid operator")
|
||||||
else -> throw AssemblyError("invalid operator")
|
|
||||||
}
|
|
||||||
assignRegisterByte(assign.target, CpuRegister.A)
|
|
||||||
}
|
}
|
||||||
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)
|
fun simpleLogicalWordsExpr() {
|
||||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
|
// both left and right expression operands are simple.
|
||||||
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
|
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
|
||||||
else {
|
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
|
||||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
else {
|
||||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
||||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
when (expr.operator) {
|
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||||
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
||||||
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
|
when (expr.operator) {
|
||||||
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
|
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
|
||||||
else -> throw AssemblyError("invalid operator")
|
"|", "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")
|
||||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
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
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,7 +814,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
||||||
when(variable.datatype) {
|
when(variable.datatype) {
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
require(elementDt.isBytes)
|
require(elementDt.isBytes) { "must be byte string ${variable.position}" }
|
||||||
val stringVal = variable.value as StringLiteral
|
val stringVal = variable.value as StringLiteral
|
||||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
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")
|
throw AssemblyError("containment check of floats not supported")
|
||||||
}
|
}
|
||||||
in ArrayDatatypes -> {
|
in ArrayDatatypes -> {
|
||||||
require(elementDt.isInteger)
|
require(elementDt.isInteger) { "must be integer array ${variable.position}" }
|
||||||
val arrayVal = variable.value as ArrayLiteral
|
val arrayVal = variable.value as ArrayLiteral
|
||||||
val dt = elementDt.getOr(DataType.UNDEFINED)
|
val dt = elementDt.getOr(DataType.UNDEFINED)
|
||||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||||
@ -888,11 +945,9 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
is NumericLiteral -> {
|
is NumericLiteral -> {
|
||||||
val address = (value.addressExpression as NumericLiteral).number.toUInt()
|
val address = (value.addressExpression as NumericLiteral).number.toUInt()
|
||||||
assignMemoryByteIntoWord(target, address, null)
|
assignMemoryByteIntoWord(target, address, null)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
|
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
is BinaryExpression -> {
|
is BinaryExpression -> {
|
||||||
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
|
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
|
||||||
@ -906,6 +961,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
assignViaExprEval(value.addressExpression)
|
assignViaExprEval(value.addressExpression)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
|
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
|
// special case optimizations
|
||||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||||
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
|
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
|
||||||
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
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)
|
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
|
||||||
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
|
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
|
||||||
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
|
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
if(!(valueDt isAssignableTo targetDt)) {
|
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
|
// word to byte, just take the lsb
|
||||||
return assignCastViaLsbFunc(value, target)
|
assignCastViaLsbFunc(value, target)
|
||||||
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
|
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
|
||||||
// word to word, just assign
|
// word to word, just assign
|
||||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
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) {
|
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
|
||||||
// byte to word, just assign
|
// byte to word, just assign
|
||||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
|
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")
|
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)) {
|
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||||
when(valueDt) {
|
when(valueDt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.FREADUY")
|
asmgen.out(" jsr floats.FREADUY")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.A, true)
|
assignExpressionToRegister(value, RegisterOrPair.A, true)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.FREADSA")
|
asmgen.out(" jsr floats.FREADSA")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.GIVUAYFAY")
|
asmgen.out(" jsr floats.GIVUAYFAY")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.GIVAYFAY")
|
asmgen.out(" jsr floats.GIVAYFAY")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid dt")
|
else -> throw AssemblyError("invalid dt")
|
||||||
}
|
}
|
||||||
if(target.register==RegisterOrPair.FAC2) {
|
if(target.register==RegisterOrPair.FAC2) {
|
||||||
asmgen.out(" jsr floats.MOVEF")
|
asmgen.out(" jsr floats.MOVEF")
|
||||||
}
|
}
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
// 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..... :-/
|
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
||||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1199,14 +1298,10 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(
|
asmgen.out(
|
||||||
" st${
|
" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||||
regs.toString().lowercase()
|
|
||||||
} $targetAsmVarName | stz $targetAsmVarName+1")
|
|
||||||
else
|
else
|
||||||
asmgen.out(
|
asmgen.out(
|
||||||
" st${
|
" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||||
regs.toString().lowercase()
|
|
||||||
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
when(regs) {
|
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
|
// 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
|
// these will be correctly typecasted from a byte to a word value here
|
||||||
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
|
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) {
|
when(target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -2093,7 +2188,9 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
|
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)
|
if(target.datatype==DataType.FLOAT)
|
||||||
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
|
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) {
|
fun translate(assign: AsmAssignment) {
|
||||||
require(assign.isAugmentable)
|
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!!) {
|
when (val value = assign.source.expression!!) {
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
// A = -A , A = +A, A = ~A, A = not A
|
// A = -A , A = +A, A = ~A, A = not A
|
||||||
when (value.operator) {
|
when (value.operator) {
|
||||||
"+" -> {}
|
"+" -> {}
|
||||||
"-" -> inplaceNegate(assign)
|
"-" -> inplaceNegate(assign, false)
|
||||||
"~" -> inplaceInvert(assign)
|
"~" -> inplaceInvert(assign)
|
||||||
else -> throw AssemblyError("invalid prefix operator")
|
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
|
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 -> {
|
DataType.BYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
@ -258,7 +258,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
else if(memory!=null) {
|
else if(memory!=null) {
|
||||||
require(vmDt== IRDataType.BYTE)
|
require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
|
||||||
if(zero) {
|
if(zero) {
|
||||||
if(memory.address is PtNumber) {
|
if(memory.address is PtNumber) {
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
|
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.StStaticVariable
|
||||||
import prog8.code.StSub
|
import prog8.code.StSub
|
||||||
import prog8.code.ast.*
|
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.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
|
||||||
internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||||
fun translateExpression(expr: PtExpression, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
|
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) {
|
return when (expr) {
|
||||||
is PtMachineRegister -> {
|
is PtMachineRegister -> {
|
||||||
@ -586,7 +591,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: IRDataType, resultRegister: Int): IRCodeChunks {
|
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 result = mutableListOf<IRCodeChunkBase>()
|
||||||
val rightResultReg = codeGen.registers.nextFree()
|
val rightResultReg = codeGen.registers.nextFree()
|
||||||
if(binExpr.right is PtNumber) {
|
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.
|
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
|
||||||
|
|
||||||
irProg.blocks.forEach { block ->
|
irProg.blocks.forEach { block ->
|
||||||
if(block.inlineAssembly.isNotEmpty()) {
|
block.children.firstOrNull { it is IRInlineAsmChunk }?.let { first->
|
||||||
val first = block.inlineAssembly.first()
|
first as IRInlineAsmChunk
|
||||||
if(first.label==null) {
|
if(first.label==null) {
|
||||||
val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next)
|
val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next)
|
||||||
block.inlineAssembly.removeAt(0)
|
block.children.removeAt(0)
|
||||||
block.inlineAssembly.add(0, replacement)
|
block.children.add(0, replacement)
|
||||||
} else if(first.label != block.name) {
|
} else if(first.label != block.name) {
|
||||||
throw AssemblyError("first chunk in block has label that differs from 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()) {
|
if(sub.chunks.isNotEmpty()) {
|
||||||
val first = sub.chunks.first()
|
val first = sub.chunks.first()
|
||||||
if(first.label==null) {
|
if(first.label==null) {
|
||||||
val replacement = when(first) {
|
val replacement = when(first) {
|
||||||
is IRCodeChunk -> {
|
is IRCodeChunk -> {
|
||||||
val replacement = IRCodeChunk(sub.name, first.next)
|
val replacement = IRCodeChunk(sub.label, first.next)
|
||||||
replacement.instructions += first.instructions
|
replacement.instructions += first.instructions
|
||||||
replacement
|
replacement
|
||||||
}
|
}
|
||||||
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.name, first.assembly, first.isIR, first.next)
|
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next)
|
||||||
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.name, first.data, first.next)
|
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.label, first.data, first.next)
|
||||||
else -> throw AssemblyError("invalid chunk")
|
else -> throw AssemblyError("invalid chunk")
|
||||||
}
|
}
|
||||||
sub.chunks.removeAt(0)
|
sub.chunks.removeAt(0)
|
||||||
sub.chunks.add(0, replacement)
|
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
|
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
|
// 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.
|
// for instance when a piece of inlined assembly references them.
|
||||||
val replacements = mutableListOf<Triple<IRCodeChunkBase, Int, UInt>>()
|
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 {
|
chunk.instructions.withIndex().forEach {
|
||||||
(idx, instr) ->
|
(idx, instr) ->
|
||||||
val symbolExpr = instr.labelSymbol
|
val symbolExpr = instr.labelSymbol
|
||||||
@ -180,14 +180,12 @@ class IRCodeGen(
|
|||||||
private fun flattenNestedSubroutines() {
|
private fun flattenNestedSubroutines() {
|
||||||
// this moves all nested subroutines up to the block scope.
|
// 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 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 flattenedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
||||||
val flattenedAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
val flattenedAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
||||||
val removalsSubs = mutableListOf<Pair<PtSub, PtSub>>()
|
val removalsSubs = mutableListOf<Pair<PtSub, PtSub>>()
|
||||||
val removalsAsmSubs = mutableListOf<Pair<PtSub, PtAsmSub>>()
|
val removalsAsmSubs = mutableListOf<Pair<PtSub, PtAsmSub>>()
|
||||||
val renameSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
val renameSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
||||||
val renameAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
val renameAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
||||||
val entrypoint = program.entrypoint()
|
|
||||||
|
|
||||||
fun flattenNestedAsmSub(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
|
fun flattenNestedAsmSub(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
|
||||||
val flattened = PtAsmSub(asmsub.scopedName.joinToString("."),
|
val flattened = PtAsmSub(asmsub.scopedName.joinToString("."),
|
||||||
@ -236,17 +234,8 @@ class IRCodeGen(
|
|||||||
renameSubs.forEach { (parent, sub) ->
|
renameSubs.forEach { (parent, sub) ->
|
||||||
val renamedSub = PtSub(sub.scopedName.joinToString("."), sub.parameters, sub.returntype, sub.inline, sub.position)
|
val renamedSub = PtSub(sub.scopedName.joinToString("."), sub.parameters, sub.returntype, sub.inline, sub.position)
|
||||||
sub.children.forEach { renamedSub.add(it) }
|
sub.children.forEach { renamedSub.add(it) }
|
||||||
parent.children.remove(sub)
|
val subindex = parent.children.indexOf(sub)
|
||||||
if (sub === entrypoint) {
|
parent.children[subindex] = renamedSub // keep the order of nodes the same
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
renameAsmSubs.forEach { (parent, sub) ->
|
renameAsmSubs.forEach { (parent, sub) ->
|
||||||
val renamedSub = PtAsmSub(sub.scopedName.joinToString("."),
|
val renamedSub = PtAsmSub(sub.scopedName.joinToString("."),
|
||||||
@ -260,8 +249,8 @@ class IRCodeGen(
|
|||||||
|
|
||||||
if(sub.children.isNotEmpty())
|
if(sub.children.isNotEmpty())
|
||||||
renamedSub.add(sub.children.single())
|
renamedSub.add(sub.children.single())
|
||||||
parent.children.remove(sub)
|
val subindex = parent.children.indexOf(sub)
|
||||||
parent.add(renamedSub)
|
parent.children[subindex] = renamedSub // keep the order of nodes the same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,12 +280,7 @@ class IRCodeGen(
|
|||||||
}
|
}
|
||||||
is PtConditionalBranch -> translate(node)
|
is PtConditionalBranch -> translate(node)
|
||||||
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
|
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
|
||||||
is PtIncludeBinary -> {
|
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
|
||||||
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 PtAddressOf,
|
is PtAddressOf,
|
||||||
is PtContainmentCheck,
|
is PtContainmentCheck,
|
||||||
is PtMemoryByte,
|
is PtMemoryByte,
|
||||||
@ -327,8 +311,50 @@ class IRCodeGen(
|
|||||||
return chunks
|
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 {
|
private fun translate(branch: PtConditionalBranch): IRCodeChunks {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
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()
|
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
|
// 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) {
|
val branchIns = when(branch.condition) {
|
||||||
@ -338,8 +364,8 @@ class IRCodeGen(
|
|||||||
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
|
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
|
||||||
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
|
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
|
||||||
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
|
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
|
||||||
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
|
BranchCondition.VC -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
|
||||||
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
|
BranchCondition.VS -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
|
||||||
}
|
}
|
||||||
addInstr(result, branchIns, null)
|
addInstr(result, branchIns, null)
|
||||||
result += translateNode(branch.trueScope)
|
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 signed = ifElse.condition.left.type in arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||||
val irDt = irType(ifElse.condition.left.type)
|
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 {
|
fun translateNonZeroComparison(): IRCodeChunks {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val elseBranch = when(ifElse.condition.operator) {
|
val elseBranch = when(ifElse.condition.operator) {
|
||||||
@ -1095,7 +1147,9 @@ class IRCodeGen(
|
|||||||
is PtAsmSub -> {
|
is PtAsmSub -> {
|
||||||
if(child.address!=null) {
|
if(child.address!=null) {
|
||||||
// romsub. No codegen needed: calls to this are jumping straight to the address.
|
// 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 {
|
} else {
|
||||||
// regular asmsub
|
// regular asmsub
|
||||||
val assemblyChild = child.children.single() as PtInlineAssembly
|
val assemblyChild = child.children.single() as PtInlineAssembly
|
||||||
@ -1106,8 +1160,8 @@ class IRCodeGen(
|
|||||||
child.name,
|
child.name,
|
||||||
child.address,
|
child.address,
|
||||||
child.clobbers,
|
child.clobbers,
|
||||||
child.parameters.map { Pair(it.first.type, it.second) }, // note: the name of the asmsub param is not used anymore.
|
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),
|
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) },
|
||||||
asmChunk,
|
asmChunk,
|
||||||
child.position
|
child.position
|
||||||
)
|
)
|
||||||
@ -1116,6 +1170,12 @@ class IRCodeGen(
|
|||||||
is PtInlineAssembly -> {
|
is PtInlineAssembly -> {
|
||||||
irBlock += IRInlineAsmChunk(null, child.assembly, child.isIR, null)
|
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")
|
else -> TODO("weird child node $child")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import prog8.intermediate.*
|
|||||||
|
|
||||||
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||||
fun optimize() {
|
fun optimize() {
|
||||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
removeEmptyChunks(sub)
|
removeEmptyChunks(sub)
|
||||||
joinChunks(sub)
|
joinChunks(sub)
|
||||||
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||||
|
@ -7,15 +7,23 @@ import prog8.intermediate.*
|
|||||||
|
|
||||||
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
||||||
fun optimize(): Int {
|
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
|
// remove empty subs
|
||||||
irprog.blocks.forEach { block ->
|
irprog.blocks.forEach { block ->
|
||||||
block.subroutines.reversed().forEach { sub ->
|
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
|
||||||
if(sub.isEmpty()) {
|
if(sub.isEmpty()) {
|
||||||
if(!sub.position.file.startsWith(libraryFilePrefix))
|
if(!sub.position.file.startsWith(libraryFilePrefix))
|
||||||
errors.warn("unused subroutine ${sub.name}", sub.position)
|
errors.warn("unused subroutine ${sub.label}", sub.position)
|
||||||
block.subroutines.remove(sub)
|
block.children.remove(sub)
|
||||||
numRemoved++
|
numRemoved++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,14 +40,20 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
|||||||
return numRemoved
|
return numRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeUnreachable(): Int {
|
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||||
val reachable = mutableSetOf(irprog.blocks.single { it.name=="main" }.subroutines.single { it.name=="main.start" }.chunks.first())
|
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() {
|
fun grow() {
|
||||||
val new = mutableSetOf<IRCodeChunkBase>()
|
val new = mutableSetOf<IRCodeChunkBase>()
|
||||||
reachable.forEach {
|
reachable.forEach {
|
||||||
it.next?.let { next -> new += next }
|
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
|
reachable += new
|
||||||
}
|
}
|
||||||
@ -55,13 +69,19 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
|||||||
return removeUnlinkedChunks(reachable)
|
return removeUnlinkedChunks(reachable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeSimpleUnlinked(): Int {
|
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
|
||||||
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
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 ->
|
sub.chunks.forEach { chunk ->
|
||||||
chunk.next?.let { next -> linkedChunks += next }
|
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")
|
if (chunk.label == "main.start")
|
||||||
linkedChunks += chunk
|
linkedChunks += chunk
|
||||||
}
|
}
|
||||||
@ -74,7 +94,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
|||||||
linkedChunks: MutableSet<IRCodeChunkBase>
|
linkedChunks: MutableSet<IRCodeChunkBase>
|
||||||
): Int {
|
): Int {
|
||||||
var numRemoved = 0
|
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) ->
|
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||||
if (chunk !in linkedChunks) {
|
if (chunk !in linkedChunks) {
|
||||||
if (chunk === sub.chunks[0]) {
|
if (chunk === sub.chunks[0]) {
|
||||||
|
@ -20,7 +20,6 @@ class VmCodeGen(private val program: PtProgram,
|
|||||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||||
val irProgram = irCodeGen.generate()
|
val irProgram = irCodeGen.generate()
|
||||||
|
|
||||||
// no need to check options.keepIR, as the VM file format *is* the IR file.
|
|
||||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
return makeIRProgram(listOf(chunk))
|
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") {
|
test("remove nops") {
|
||||||
val irProg = makeIRProgram(listOf(
|
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))
|
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
|
// unsigned >= 0 --> true
|
||||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
|
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) {
|
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))
|
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
|
// unsigned < 0 --> false
|
||||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
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
|
// boolvar & 1 --> boolvar
|
||||||
@ -592,7 +600,7 @@ class ExpressionSimplifier(private val program: Program,
|
|||||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD -> {
|
||||||
if (amount >= 16) {
|
if (amount >= 16) {
|
||||||
errors.warn("shift always results in 0", expr.position)
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral(targetDt, 0.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)
|
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 -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,9 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
|
|
||||||
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
|
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
|
||||||
if(sub.inline && sub.parameters.isEmpty()) {
|
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) {
|
return if(sub.isAsmSubroutine) {
|
||||||
// simply insert the asm for the argument-less routine
|
// simply insert the asm for the argument-less routine
|
||||||
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
|
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> {
|
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||||
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
|
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
|
||||||
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
|
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) {
|
return if(sub.isAsmSubroutine) {
|
||||||
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
|
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
|
||||||
noModifications
|
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) {
|
if(assignment.target.inferType(program).isWords) {
|
||||||
var fcall = assignment.value as? FunctionCallExpression
|
var fcall = assignment.value as? FunctionCallExpression
|
||||||
if (fcall == null)
|
if (fcall == null)
|
||||||
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
|
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) {
|
if (fcall.args.single() isSameAs assignment.target) {
|
||||||
return if (fcall.target.nameInSource == listOf("lsb")) {
|
// optimize word=lsb(word) ==> word &= $00ff
|
||||||
// optimize word=lsb(word) ==> word &= $00ff
|
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
|
||||||
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)
|
||||||
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
|
return listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ dependencies {
|
|||||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
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'
|
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="codeGenExperimental" />
|
||||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||||
<orderEntry type="module" module-name="virtualmachine" />
|
<orderEntry type="module" module-name="virtualmachine" />
|
||||||
|
<orderEntry type="module" module-name="intermediate" scope="TEST" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
@ -12,9 +12,34 @@ c64 {
|
|||||||
|
|
||||||
&ubyte COLOR = $00f1 ; cursor color
|
&ubyte COLOR = $00f1 ; cursor color
|
||||||
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
|
;;&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 CINV = $0314 ; IRQ vector (in ram)
|
||||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||||
&uword NMINV = $0318 ; NMI 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 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 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
|
&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 COLOR = $0286 ; cursor color
|
||||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
&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 CINV = $0314 ; IRQ vector (in ram)
|
||||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||||
&uword NMINV = $0318 ; NMI 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 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 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
|
&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.
|
; Cx16 specific disk drive I/O routines.
|
||||||
|
|
||||||
%import diskio
|
%import diskio
|
||||||
|
%import string
|
||||||
|
|
||||||
cx16diskio {
|
cx16diskio {
|
||||||
|
|
||||||
@ -34,8 +35,8 @@ cx16diskio {
|
|||||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
asmsub vload(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||||
; -- like the basic command VLOAD "filename",device,bank,address
|
; -- 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
|
; 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)
|
; the file has to have the usual 2 byte header (which will be skipped)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -76,8 +77,8 @@ internal_vload:
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub vload_raw(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
asmsub vload_raw(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||||
; -- like the basic command BVLOAD "filename",device,bank,address
|
; -- 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
|
; 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.
|
; the file is read fully including the first two bytes.
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -95,15 +96,6 @@ internal_vload:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
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
|
; commander X16 supports fast block-read via macptr() kernal call
|
||||||
uword size
|
uword size
|
||||||
@ -133,29 +125,23 @@ byte_read_loop: ; fallback if macptr() isn't supported on the device
|
|||||||
lda bufferpointer+1
|
lda bufferpointer+1
|
||||||
sta m_in_buffer+2
|
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 {{
|
%asm {{
|
||||||
jsr c64.CHRIN
|
jsr c64.CHRIN
|
||||||
sta cx16.r5
|
|
||||||
m_in_buffer sta $ffff
|
m_in_buffer sta $ffff
|
||||||
inc m_in_buffer+1
|
inc m_in_buffer+1
|
||||||
bne +
|
bne +
|
||||||
inc m_in_buffer+2
|
inc m_in_buffer+2
|
||||||
+ inc diskio.list_blocks
|
|
||||||
bne +
|
|
||||||
inc diskio.list_blocks+1
|
|
||||||
+
|
+
|
||||||
}}
|
}}
|
||||||
|
diskio.list_blocks++
|
||||||
if cx16.r5==$0d { ; chance on I/o error status?
|
num_bytes--
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return diskio.list_blocks ; number of bytes read
|
return diskio.list_blocks ; number of bytes read
|
||||||
}
|
}
|
||||||
@ -168,19 +154,78 @@ m_in_buffer sta $ffff
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
uword total_read = 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() {
|
while not c64.READST() {
|
||||||
uword size = cx16diskio.f_read(bufferpointer, 256)
|
cx16.r0 = cx16diskio.f_read(bufferpointer, 256)
|
||||||
total_read += size
|
total_read += cx16.r0
|
||||||
bufferpointer += size
|
bufferpointer += cx16.r0
|
||||||
}
|
}
|
||||||
return total_read
|
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 $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!!!]
|
; 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)
|
; (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 $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
|
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)
|
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||||
|
|
||||||
; X16 additions
|
; X16 additions
|
||||||
romsub $fe81 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||||
romsub $fe84 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||||
romsub $fe87 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||||
romsub $fe8a = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
||||||
romsub $fe8d = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||||
romsub $fe90 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||||
romsub $fe93 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
romsub $fe81 = 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 $fe84 = 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 $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
|
||||||
romsub $fe9c = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
romsub $fe8a = 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 $fe8d = 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 $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||||
|
@ -82,9 +82,10 @@ gfx2 {
|
|||||||
bpp = 2
|
bpp = 2
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
; back to default text mode and colors
|
; back to default text mode
|
||||||
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
|
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||||
c64.CINT() ; back to text mode
|
c64.CINT()
|
||||||
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||||
width = 0
|
width = 0
|
||||||
height = 0
|
height = 0
|
||||||
bpp = 0
|
bpp = 0
|
||||||
@ -918,7 +919,7 @@ _done
|
|||||||
y++
|
y++
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
phx
|
||||||
ldx #1
|
ldx color
|
||||||
lda cx16.VERA_DATA1
|
lda cx16.VERA_DATA1
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
ldy #8
|
ldy #8
|
||||||
|
@ -85,6 +85,18 @@ asmsub RDTIM16() -> uword @AY {
|
|||||||
cx16 {
|
cx16 {
|
||||||
|
|
||||||
; irq, system and hardware vectors:
|
; 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 CINV = $0314 ; IRQ vector (in ram)
|
||||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||||
@ -290,8 +302,9 @@ cx16 {
|
|||||||
&ubyte d2ier = via2+14
|
&ubyte d2ier = via2+14
|
||||||
&ubyte d2ora = via2+15
|
&ubyte d2ora = via2+15
|
||||||
|
|
||||||
&ubyte ym2151adr = $9f40
|
; YM-2151 sound chip
|
||||||
&ubyte ym2151dat = $9f41
|
&ubyte YM_ADDRESS = $9f40
|
||||||
|
&ubyte YM_DATA = $9f41
|
||||||
|
|
||||||
const uword extdev = $9f60
|
const uword extdev = $9f60
|
||||||
|
|
||||||
@ -612,12 +625,22 @@ asmsub init_system() {
|
|||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
cld
|
cld
|
||||||
|
lda VERA_DC_VIDEO
|
||||||
|
and #%00000111 ; retain chroma + output mode
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
lda #$80
|
lda #$80
|
||||||
sta VERA_CTRL
|
sta VERA_CTRL ; reset vera
|
||||||
stz $01 ; select rom bank 0 (enable kernal)
|
stz $01 ; select rom bank 0 (enable kernal)
|
||||||
jsr c64.IOINIT
|
jsr c64.IOINIT
|
||||||
jsr c64.RESTOR
|
jsr c64.RESTOR
|
||||||
jsr c64.CINT
|
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
|
lda #$90 ; black
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
lda #1 ; swap fg/bg
|
lda #1 ; swap fg/bg
|
||||||
|
@ -21,6 +21,26 @@ sub home() {
|
|||||||
txt.chrout(19)
|
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() {
|
sub nl() {
|
||||||
txt.chrout('\n')
|
txt.chrout('\n')
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ cx16logo {
|
|||||||
|
|
||||||
sub logo() {
|
sub logo() {
|
||||||
uword strptr
|
uword strptr
|
||||||
for strptr in logo_lines
|
for strptr in logo_lines {
|
||||||
txt.print(strptr)
|
txt.print(strptr)
|
||||||
txt.nl()
|
txt.nl()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str[] logo_lines = [
|
str[] logo_lines = [
|
||||||
|
@ -10,12 +10,12 @@ diskio {
|
|||||||
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||||
|
|
||||||
c64.SETNAM(1, "$")
|
c64.SETNAM(1, "$")
|
||||||
c64.SETLFS(13, drivenumber, 0)
|
c64.SETLFS(12, drivenumber, 0)
|
||||||
ubyte status = 1
|
ubyte status = 1
|
||||||
void c64.OPEN() ; open 13,8,0,"$"
|
void c64.OPEN() ; open 12,8,0,"$"
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
void c64.CHKIN(13) ; use #13 as input channel
|
void c64.CHKIN(12) ; use #12 as input channel
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ diskio {
|
|||||||
|
|
||||||
io_error:
|
io_error:
|
||||||
c64.CLRCHN() ; restore default i/o devices
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
c64.CLOSE(13)
|
c64.CLOSE(12)
|
||||||
|
|
||||||
if status and status & $40 == 0 { ; bit 6=end of file
|
if status and status & $40 == 0 { ; bit 6=end of file
|
||||||
txt.print("\ni/o error, status: ")
|
txt.print("\ni/o error, status: ")
|
||||||
@ -69,12 +69,12 @@ io_error:
|
|||||||
; -- Returns pointer to disk name string or 0 if failure.
|
; -- Returns pointer to disk name string or 0 if failure.
|
||||||
|
|
||||||
c64.SETNAM(1, "$")
|
c64.SETNAM(1, "$")
|
||||||
c64.SETLFS(13, drivenumber, 0)
|
c64.SETLFS(12, drivenumber, 0)
|
||||||
ubyte okay = false
|
ubyte okay = false
|
||||||
void c64.OPEN() ; open 13,8,0,"$"
|
void c64.OPEN() ; open 12,8,0,"$"
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
void c64.CHKIN(13) ; use #13 as input channel
|
void c64.CHKIN(12) ; use #12 as input channel
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ io_error:
|
|||||||
|
|
||||||
io_error:
|
io_error:
|
||||||
c64.CLRCHN() ; restore default i/o devices
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
c64.CLOSE(13)
|
c64.CLOSE(12)
|
||||||
if okay
|
if okay
|
||||||
return &list_filename
|
return &list_filename
|
||||||
return 0
|
return 0
|
||||||
@ -107,33 +107,37 @@ io_error:
|
|||||||
uword list_pattern
|
uword list_pattern
|
||||||
uword list_blocks
|
uword list_blocks
|
||||||
bool iteration_in_progress = false
|
bool iteration_in_progress = false
|
||||||
ubyte @zp first_byte
|
ubyte last_drivenumber = 8 ; which drive was last used for a f_open operation?
|
||||||
bool have_first_byte
|
str list_filetype = "???" ; prg, seq, dir
|
||||||
str list_filename = "?" * 50
|
str list_filename = "?" * 50
|
||||||
|
|
||||||
; ----- get a list of files (uses iteration functions internally) -----
|
; ----- get a list of files (uses iteration functions internally) -----
|
||||||
|
|
||||||
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
sub list_filenames(ubyte drivenumber, uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||||
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. Returns number of files.
|
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||||
const uword filenames_buf_size = 800
|
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
|
||||||
uword filenames_buffer = memory("filenames", filenames_buf_size, 0)
|
; 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
|
uword buffer_start = filenames_buffer
|
||||||
ubyte files_found = 0
|
ubyte files_found = 0
|
||||||
if lf_start_list(drivenumber, pattern_ptr) {
|
if lf_start_list(drivenumber, pattern_ptr) {
|
||||||
while lf_next_entry() {
|
while lf_next_entry() {
|
||||||
@(name_ptrs) = lsb(filenames_buffer)
|
if list_filetype!="dir" {
|
||||||
name_ptrs++
|
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
||||||
@(name_ptrs) = msb(filenames_buffer)
|
files_found++
|
||||||
name_ptrs++
|
if filenames_buffer - buffer_start > filenames_buf_size-20 {
|
||||||
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
@(filenames_buffer)=0
|
||||||
files_found++
|
lf_end_list()
|
||||||
if filenames_buffer - buffer_start > filenames_buf_size-18
|
sys.set_carry()
|
||||||
break
|
return files_found
|
||||||
if files_found == max_names
|
}
|
||||||
break
|
}
|
||||||
}
|
}
|
||||||
lf_end_list()
|
lf_end_list()
|
||||||
}
|
}
|
||||||
|
@(filenames_buffer)=0
|
||||||
|
sys.clear_carry()
|
||||||
return files_found
|
return files_found
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +174,7 @@ io_error:
|
|||||||
|
|
||||||
sub lf_next_entry() -> bool {
|
sub lf_next_entry() -> bool {
|
||||||
; -- retrieve the next entry from an iterative file listing session.
|
; -- 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 it returns false though, there are no more entries (or an error occurred).
|
||||||
|
|
||||||
if not iteration_in_progress
|
if not iteration_in_progress
|
||||||
@ -207,6 +211,12 @@ io_error:
|
|||||||
|
|
||||||
@(nameptr) = 0
|
@(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() {
|
while c64.CHRIN() {
|
||||||
; read the rest of the entry until the end
|
; 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 {
|
sub f_open(ubyte drivenumber, uword filenameptr) -> bool {
|
||||||
; -- open a file for iterative reading with f_read
|
; -- open a file for iterative reading with f_read
|
||||||
@ -246,17 +256,19 @@ close_end:
|
|||||||
f_close()
|
f_close()
|
||||||
|
|
||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
c64.SETLFS(11, drivenumber, 0)
|
c64.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
|
||||||
void c64.OPEN() ; open 11,8,0,"filename"
|
last_drivenumber = drivenumber
|
||||||
|
void c64.OPEN() ; open 12,8,12,"filename"
|
||||||
if_cc {
|
if_cc {
|
||||||
if c64.READST()==0 {
|
if c64.READST()==0 {
|
||||||
iteration_in_progress = true
|
iteration_in_progress = true
|
||||||
have_first_byte = false
|
void c64.CHKIN(12) ; use #12 as input channel
|
||||||
void c64.CHKIN(11) ; use #11 as input channel
|
|
||||||
if_cc {
|
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() {
|
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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,15 +287,6 @@ close_end:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
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 {{
|
%asm {{
|
||||||
lda bufferpointer
|
lda bufferpointer
|
||||||
@ -291,29 +294,23 @@ close_end:
|
|||||||
lda bufferpointer+1
|
lda bufferpointer+1
|
||||||
sta m_in_buffer+2
|
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 {{
|
%asm {{
|
||||||
jsr c64.CHRIN
|
jsr c64.CHRIN
|
||||||
sta cx16.r5
|
|
||||||
m_in_buffer sta $ffff
|
m_in_buffer sta $ffff
|
||||||
inc m_in_buffer+1
|
inc m_in_buffer+1
|
||||||
bne +
|
bne +
|
||||||
inc m_in_buffer+2
|
inc m_in_buffer+2
|
||||||
+ inc list_blocks
|
|
||||||
bne +
|
|
||||||
inc list_blocks+1
|
|
||||||
+
|
+
|
||||||
}}
|
}}
|
||||||
|
list_blocks++
|
||||||
if cx16.r5==$0d { ; chance on I/o error status?
|
num_bytes--
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return list_blocks ; number of bytes read
|
return list_blocks ; number of bytes read
|
||||||
}
|
}
|
||||||
@ -324,17 +321,10 @@ m_in_buffer sta $ffff
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
uword total_read = 0
|
uword total_read = 0
|
||||||
if have_first_byte {
|
|
||||||
have_first_byte=false
|
|
||||||
@(bufferpointer) = first_byte
|
|
||||||
bufferpointer++
|
|
||||||
total_read = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
while not c64.READST() {
|
while not c64.READST() {
|
||||||
uword size = f_read(bufferpointer, 256)
|
cx16.r0 = f_read(bufferpointer, 256)
|
||||||
total_read += size
|
total_read += cx16.r0
|
||||||
bufferpointer += size
|
bufferpointer += cx16.r0
|
||||||
}
|
}
|
||||||
return total_read
|
return total_read
|
||||||
}
|
}
|
||||||
@ -348,16 +338,9 @@ m_in_buffer sta $ffff
|
|||||||
%asm {{
|
%asm {{
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
sty P8ZP_SCRATCH_W1+1
|
sty P8ZP_SCRATCH_W1+1
|
||||||
ldx #11
|
ldx #12
|
||||||
jsr c64.CHKIN ; use channel 11 again for input
|
jsr c64.CHKIN ; use channel 12 again for input
|
||||||
ldy #0
|
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
|
_loop jsr c64.CHRIN
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
beq _end
|
beq _end
|
||||||
@ -378,23 +361,23 @@ _end rts
|
|||||||
; -- end an iterative file loading session (close channels).
|
; -- end an iterative file loading session (close channels).
|
||||||
if iteration_in_progress {
|
if iteration_in_progress {
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
c64.CLOSE(11)
|
c64.CLOSE(12)
|
||||||
iteration_in_progress = false
|
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 {
|
sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool {
|
||||||
; -- open a file for iterative writing with f_write
|
; -- open a file for iterative writing with f_write
|
||||||
f_close_w()
|
f_close_w()
|
||||||
|
|
||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
c64.SETLFS(14, drivenumber, 1)
|
c64.SETLFS(13, drivenumber, 1)
|
||||||
void c64.OPEN() ; open 14,8,1,"filename"
|
void c64.OPEN() ; open 13,8,1,"filename"
|
||||||
if_cc {
|
if_cc {
|
||||||
void c64.CHKOUT(14) ; use #14 as input channel
|
c64.CHKOUT(13) ; use #13 as output channel
|
||||||
return not c64.READST()
|
return not c64.READST()
|
||||||
}
|
}
|
||||||
f_close_w()
|
f_close_w()
|
||||||
@ -402,9 +385,9 @@ _end rts
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
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 {
|
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 {
|
repeat num_bytes {
|
||||||
c64.CHROUT(@(bufferpointer))
|
c64.CHROUT(@(bufferpointer))
|
||||||
bufferpointer++
|
bufferpointer++
|
||||||
@ -417,7 +400,7 @@ _end rts
|
|||||||
sub f_close_w() {
|
sub f_close_w() {
|
||||||
; -- end an iterative file writing session (close channels).
|
; -- end an iterative file writing session (close channels).
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
c64.CLOSE(14)
|
c64.CLOSE(13)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -436,10 +419,10 @@ _end rts
|
|||||||
goto io_error
|
goto io_error
|
||||||
|
|
||||||
while not c64.READST() {
|
while not c64.READST() {
|
||||||
first_byte = c64.CHRIN()
|
cx16.r5L = c64.CHRIN()
|
||||||
if first_byte=='\r' or first_byte=='\n'
|
if cx16.r5L=='\r' or cx16.r5L=='\n'
|
||||||
break
|
break
|
||||||
@(messageptr) = first_byte
|
@(messageptr) = cx16.r5L
|
||||||
messageptr++
|
messageptr++
|
||||||
}
|
}
|
||||||
@(messageptr) = 0
|
@(messageptr) = 0
|
||||||
@ -458,7 +441,7 @@ io_error:
|
|||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
c64.SETLFS(1, drivenumber, 0)
|
c64.SETLFS(1, drivenumber, 0)
|
||||||
uword @shared end_address = address + size
|
uword @shared end_address = address + size
|
||||||
first_byte = 0 ; result var reuse
|
cx16.r0L = 0
|
||||||
|
|
||||||
%asm {{
|
%asm {{
|
||||||
lda address
|
lda address
|
||||||
@ -476,12 +459,12 @@ io_error:
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
if_cc
|
if_cc
|
||||||
first_byte = c64.READST()==0
|
cx16.r0L = c64.READST()==0
|
||||||
|
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
c64.CLOSE(1)
|
c64.CLOSE(1)
|
||||||
|
|
||||||
return first_byte
|
return cx16.r0L
|
||||||
}
|
}
|
||||||
|
|
||||||
; Use kernal LOAD routine to load the given program file in memory.
|
; Use kernal LOAD routine to load the given program file in memory.
|
||||||
|
@ -1082,6 +1082,7 @@ containment_wordarray .proc
|
|||||||
iny
|
iny
|
||||||
cmp (P8ZP_SCRATCH_W2),y
|
cmp (P8ZP_SCRATCH_W2),y
|
||||||
beq _found
|
beq _found
|
||||||
|
dey
|
||||||
+ dey
|
+ dey
|
||||||
dey
|
dey
|
||||||
cpy #254
|
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 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 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 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 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 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 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 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")
|
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,
|
quietAssembler == true,
|
||||||
asmListfile == true,
|
asmListfile == true,
|
||||||
experimentalCodegen == true,
|
experimentalCodegen == true,
|
||||||
keepIR == true,
|
|
||||||
compilationTarget,
|
compilationTarget,
|
||||||
evalStackAddr,
|
evalStackAddr,
|
||||||
processedSymbols,
|
processedSymbols,
|
||||||
@ -192,7 +190,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
quietAssembler == true,
|
quietAssembler == true,
|
||||||
asmListfile == true,
|
asmListfile == true,
|
||||||
experimentalCodegen == true,
|
experimentalCodegen == true,
|
||||||
keepIR == true,
|
|
||||||
compilationTarget,
|
compilationTarget,
|
||||||
evalStackAddr,
|
evalStackAddr,
|
||||||
processedSymbols,
|
processedSymbols,
|
||||||
|
@ -38,7 +38,6 @@ class CompilerArguments(val filepath: Path,
|
|||||||
val quietAssembler: Boolean,
|
val quietAssembler: Boolean,
|
||||||
val asmListfile: Boolean,
|
val asmListfile: Boolean,
|
||||||
val experimentalCodegen: Boolean,
|
val experimentalCodegen: Boolean,
|
||||||
val keepIR: Boolean,
|
|
||||||
val compilationTarget: String,
|
val compilationTarget: String,
|
||||||
val evalStackBaseAddress: UInt?,
|
val evalStackBaseAddress: UInt?,
|
||||||
val symbolDefs: Map<String, String>,
|
val symbolDefs: Map<String, String>,
|
||||||
@ -81,7 +80,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
asmQuiet = args.quietAssembler
|
asmQuiet = args.quietAssembler
|
||||||
asmListfile = args.asmListfile
|
asmListfile = args.asmListfile
|
||||||
experimentalCodegen = args.experimentalCodegen
|
experimentalCodegen = args.experimentalCodegen
|
||||||
keepIR = args.keepIR
|
|
||||||
evalStackBaseAddress = args.evalStackBaseAddress
|
evalStackBaseAddress = args.evalStackBaseAddress
|
||||||
outputDir = args.outputDir.normalize()
|
outputDir = args.outputDir.normalize()
|
||||||
symbolDefs = args.symbolDefs
|
symbolDefs = args.symbolDefs
|
||||||
@ -95,7 +93,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
|
|
||||||
processAst(program, args.errors, compilationOptions)
|
processAst(program, args.errors, compilationOptions)
|
||||||
if (compilationOptions.optimize) {
|
if (compilationOptions.optimize) {
|
||||||
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
|
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
|
||||||
// printProgram(program)
|
// printProgram(program)
|
||||||
|
|
||||||
optimizeAst(
|
optimizeAst(
|
||||||
@ -108,7 +106,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
}
|
}
|
||||||
postprocessAst(program, args.errors, compilationOptions)
|
postprocessAst(program, args.errors, compilationOptions)
|
||||||
|
|
||||||
// println("*********** AST BEFORE ASSEMBLYGEN *************")
|
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
|
||||||
// printProgram(program)
|
// printProgram(program)
|
||||||
|
|
||||||
determineProgramLoadAddress(program, compilationOptions, args.errors)
|
determineProgramLoadAddress(program, compilationOptions, args.errors)
|
||||||
@ -382,7 +380,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
|||||||
callGraph.checkRecursiveCalls(errors)
|
callGraph.checkRecursiveCalls(errors)
|
||||||
program.verifyFunctionArgTypes(errors)
|
program.verifyFunctionArgTypes(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
program.moveMainAndStartToFirst()
|
program.moveMainBlockAsFirst()
|
||||||
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||||
errors.report()
|
errors.report()
|
||||||
}
|
}
|
||||||
@ -391,7 +389,7 @@ private fun createAssemblyAndAssemble(program: Program,
|
|||||||
errors: IErrorReporter,
|
errors: IErrorReporter,
|
||||||
compilerOptions: CompilationOptions
|
compilerOptions: CompilationOptions
|
||||||
): Boolean {
|
): Boolean {
|
||||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
|
||||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
|
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.
|
// to help clean up the code that still depends on them.
|
||||||
// removeAllVardeclsFromAst(program)
|
// removeAllVardeclsFromAst(program)
|
||||||
|
|
||||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
|
||||||
// printProgram(program)
|
// printProgram(program)
|
||||||
|
|
||||||
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
|
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) {
|
override fun visit(inlineAssembly: InlineAssembly) {
|
||||||
if(inlineAssembly.hasReturnOrRts(compilerOptions.compTarget))
|
if(inlineAssembly.hasReturnOrRts())
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,7 +509,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
val sourceDatatype = assignment.value.inferType(program)
|
val sourceDatatype = assignment.value.inferType(program)
|
||||||
if (sourceDatatype.isUnknown) {
|
if (sourceDatatype.isUnknown) {
|
||||||
if (assignment.value !is FunctionCallExpression)
|
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 {
|
} else {
|
||||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
|
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
|
||||||
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
|
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
|
||||||
@ -843,8 +843,15 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
val leftIDt = expr.left.inferType(program)
|
val leftIDt = expr.left.inferType(program)
|
||||||
val rightIDt = expr.right.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
|
return // hopefully this error will be detected elsewhere
|
||||||
|
}
|
||||||
|
|
||||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||||
@ -1028,6 +1035,23 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(targetStatement!=null) {
|
if(targetStatement!=null) {
|
||||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||||
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
|
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
|
val funcName = functionCallStatement.target.nameInSource
|
||||||
|
@ -12,6 +12,7 @@ import prog8.ast.statements.VarDeclOrigin
|
|||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
@ -26,6 +27,21 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
|
|||||||
val boolRemover = BoolRemover(this)
|
val boolRemover = BoolRemover(this)
|
||||||
boolRemover.visit(this)
|
boolRemover.visit(this)
|
||||||
boolRemover.applyModifications()
|
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)
|
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
|
||||||
fixer.visit(this)
|
fixer.visit(this)
|
||||||
while (errors.noErrors() && fixer.applyModifications() > 0) {
|
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 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,
|
// 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.
|
|
||||||
|
|
||||||
// sortModules()
|
val module = this.entrypoint.definingModule
|
||||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
val block = this.entrypoint.definingBlock
|
||||||
val start = this.entrypoint
|
moveModuleToFront(module)
|
||||||
val mod = start.definingModule
|
module.remove(block)
|
||||||
val block = start.definingBlock
|
val afterDirective = module.statements.indexOfFirst { it !is Directive }
|
||||||
moveModuleToFront(mod)
|
|
||||||
mod.remove(block)
|
|
||||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
|
||||||
if(afterDirective<0)
|
if(afterDirective<0)
|
||||||
mod.statements.add(block)
|
module.statements.add(block)
|
||||||
else
|
else
|
||||||
mod.statements.add(afterDirective, block)
|
module.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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
||||||
@ -168,9 +168,9 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
|
internal fun Subroutine.hasRtsInAsm(): Boolean {
|
||||||
return statements
|
return statements
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filterIsInstance<InlineAssembly>()
|
.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.ICompilationTarget
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
import prog8.compiler.BuiltinFunctions
|
import prog8.compiler.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
@ -28,9 +29,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block) {
|
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]
|
val existing = blocks[block.name]
|
||||||
if(existing!=null) {
|
if(existing!=null) {
|
||||||
if(block.isInLibrary)
|
if(block.isInLibrary)
|
||||||
@ -50,9 +48,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
if(decl.name in BuiltinFunctions)
|
if(decl.name in BuiltinFunctions)
|
||||||
errors.err("builtin function cannot be redefined", decl.position)
|
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))
|
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
|
||||||
if(existingInSameScope!=null && existingInSameScope!==decl)
|
if(existingInSameScope!=null && existingInSameScope!==decl)
|
||||||
nameError(decl.name, decl.position, existingInSameScope)
|
nameError(decl.name, decl.position, existingInSameScope)
|
||||||
@ -74,9 +69,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
if(subroutine.name in compTarget.machine.opcodeNames) {
|
if(subroutine.name in BuiltinFunctions) {
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
|
||||||
} else if(subroutine.name in BuiltinFunctions) {
|
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
errors.err("builtin function cannot be redefined", subroutine.position)
|
errors.err("builtin function cannot be redefined", subroutine.position)
|
||||||
} else {
|
} else {
|
||||||
@ -86,7 +79,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
|
|
||||||
val existing = subroutine.lookup(listOf(subroutine.name))
|
val existing = subroutine.lookup(listOf(subroutine.name))
|
||||||
if (existing != null && existing !== subroutine) {
|
if (existing != null && existing !== subroutine) {
|
||||||
if(existing.parent!==existing.parent)
|
if(existing.parent!==subroutine.parent && existing is Subroutine)
|
||||||
nameShadowWarning(subroutine.name, existing.position, subroutine)
|
nameShadowWarning(subroutine.name, existing.position, subroutine)
|
||||||
else
|
else
|
||||||
nameError(subroutine.name, existing.position, subroutine)
|
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)
|
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subroutine.name == subroutine.definingBlock.name) {
|
if(compTarget.name != VMTarget.NAME) {
|
||||||
// subroutines cannot have the same name as their enclosing block,
|
if (subroutine.name == subroutine.definingBlock.name) {
|
||||||
// because this causes symbol scoping issues in the resulting assembly source
|
// subroutines cannot have the same name as their enclosing block,
|
||||||
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
|
// 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) {
|
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) {
|
if(label.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
|
||||||
errors.err("builtin function cannot be redefined", label.position)
|
errors.err("builtin function cannot be redefined", label.position)
|
||||||
} else {
|
} else {
|
||||||
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
|
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> {
|
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
|
// adjust global variables initialization
|
||||||
if(options.dontReinitGlobals) {
|
if(options.dontReinitGlobals) {
|
||||||
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
||||||
@ -142,7 +137,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
} else {
|
} else {
|
||||||
val last = subroutine.statements.last()
|
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 }
|
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
|
||||||
if(lastStatement !is Return) {
|
if(lastStatement !is Return) {
|
||||||
val returnStmt = Return(null, subroutine.position)
|
val returnStmt = Return(null, subroutine.position)
|
||||||
@ -169,7 +164,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!subroutine.inline || !options.optimize) {
|
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
|
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
||||||
// (non-asm routines get a Return statement as needed, above)
|
// (non-asm routines get a Return statement as needed, above)
|
||||||
mods += if(options.compTarget.name==VMTarget.NAME)
|
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> {
|
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||||
if(expr.operator == "not") {
|
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
|
// not(not(x)) -> x
|
||||||
if((expr.expression as? PrefixExpression)?.operator=="not")
|
if((expr.expression as? PrefixExpression)?.operator=="not")
|
||||||
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||||
|
@ -314,9 +314,7 @@ internal class StatementReorderer(val program: Program,
|
|||||||
AddressOf(sourceIdent, assign.position),
|
AddressOf(sourceIdent, assign.position),
|
||||||
AddressOf(identifier, assign.position),
|
AddressOf(identifier, assign.position),
|
||||||
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
|
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
|
||||||
),
|
), false, assign.position
|
||||||
true,
|
|
||||||
assign.position
|
|
||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||||
}
|
}
|
||||||
@ -328,7 +326,7 @@ internal class StatementReorderer(val program: Program,
|
|||||||
assign.value as? IdentifierReference ?: assign.value,
|
assign.value as? IdentifierReference ?: assign.value,
|
||||||
identifier
|
identifier
|
||||||
),
|
),
|
||||||
true,
|
false,
|
||||||
assign.position
|
assign.position
|
||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
||||||
|
@ -62,6 +62,8 @@ internal class SymbolTableMaker: IAstVisitor {
|
|||||||
when(decl.type) {
|
when(decl.type) {
|
||||||
VarDeclType.VAR -> {
|
VarDeclType.VAR -> {
|
||||||
var initialNumeric = (decl.value as? NumericLiteral)?.number
|
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 initialStringLit = decl.value as? StringLiteral
|
||||||
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||||
val initialArrayLit = decl.value as? ArrayLiteral
|
val initialArrayLit = decl.value as? ArrayLiteral
|
||||||
@ -79,10 +81,8 @@ internal class SymbolTableMaker: IAstVisitor {
|
|||||||
false
|
false
|
||||||
else if(decl.isArray)
|
else if(decl.isArray)
|
||||||
initialArray.isNullOrEmpty()
|
initialArray.isNullOrEmpty()
|
||||||
else {
|
else
|
||||||
if(dontReinitGlobals) initialNumeric = initialNumeric ?: 0.0
|
|
||||||
initialNumeric == null
|
initialNumeric == null
|
||||||
}
|
|
||||||
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
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)
|
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)
|
val rightCv = expr.right.constValue(program)
|
||||||
|
|
||||||
if(leftDt.isKnown && rightDt.isKnown) {
|
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) {
|
if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||||
// see if any of the operands needs conversion to bool
|
// see if any of the operands needs conversion to bool
|
||||||
val modifications = mutableListOf<IAstModification>()
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
@ -8,6 +8,8 @@ import prog8.ast.base.FatalAstException
|
|||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.AnonymousScope
|
import prog8.ast.statements.AnonymousScope
|
||||||
import prog8.ast.statements.Assignment
|
import prog8.ast.statements.Assignment
|
||||||
|
import prog8.ast.statements.ConditionalBranch
|
||||||
|
import prog8.ast.statements.IfElse
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
@ -219,5 +221,22 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
return noModifications
|
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
|
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldBe null
|
||||||
errors.errors.size shouldBe 2
|
errors.errors.size shouldBe 2
|
||||||
errors.warnings.size shouldBe 0
|
errors.warnings.size shouldBe 0
|
||||||
errors.errors[0] shouldContain ":7:28: assignment value is invalid"
|
errors.errors[0] shouldContain ":7:28: invalid assignment value, maybe forgot '&'"
|
||||||
errors.errors[1] shouldContain ":8:28: assignment value is invalid"
|
errors.errors[1] shouldContain ":8:28: invalid assignment value, maybe forgot '&'"
|
||||||
}
|
}
|
||||||
|
|
||||||
test("can't do str or array expression without using address-of") {
|
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,
|
quietAssembler = true,
|
||||||
asmListfile = false,
|
asmListfile = false,
|
||||||
experimentalCodegen = false,
|
experimentalCodegen = false,
|
||||||
keepIR = false,
|
|
||||||
compilationTarget = target.name,
|
compilationTarget = target.name,
|
||||||
evalStackBaseAddress = null,
|
evalStackBaseAddress = null,
|
||||||
symbolDefs = emptyMap(),
|
symbolDefs = emptyMap(),
|
||||||
@ -99,14 +98,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
|||||||
"circles",
|
"circles",
|
||||||
"cobramk3-gfx",
|
"cobramk3-gfx",
|
||||||
"colorbars",
|
"colorbars",
|
||||||
|
"cube3d",
|
||||||
"datetime",
|
"datetime",
|
||||||
|
"diskspeed",
|
||||||
|
"fileseek",
|
||||||
"highresbitmap",
|
"highresbitmap",
|
||||||
"kefrenbars",
|
"kefrenbars",
|
||||||
|
"keyboardhandler",
|
||||||
"mandelbrot",
|
"mandelbrot",
|
||||||
"mandelbrot-gfx-colors",
|
"mandelbrot-gfx-colors",
|
||||||
"multipalette",
|
"multipalette",
|
||||||
"rasterbars",
|
"rasterbars",
|
||||||
"sincos",
|
"sincos",
|
||||||
|
"snow",
|
||||||
"tehtriz",
|
"tehtriz",
|
||||||
"testgfx2",
|
"testgfx2",
|
||||||
),
|
),
|
||||||
@ -184,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
|||||||
val (displayName, filepath) = prepareTestFiles(it, false, target)
|
val (displayName, filepath) = prepareTestFiles(it, false, target)
|
||||||
test(displayName) {
|
test(displayName) {
|
||||||
val src = filepath.readText()
|
val src = filepath.readText()
|
||||||
compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null
|
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||||
compileText(target, false, src, writeAssembly = true, keepIR=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[0].value shouldBe "main.bar"
|
||||||
strLits[1].value shouldBe "foo.bar"
|
strLits[1].value shouldBe "foo.bar"
|
||||||
strLits[0].definingScope.name shouldBe "main"
|
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,
|
quietAssembler = true,
|
||||||
asmListfile = false,
|
asmListfile = false,
|
||||||
experimentalCodegen = false,
|
experimentalCodegen = false,
|
||||||
keepIR = false,
|
|
||||||
compilationTarget = Cx16Target.NAME,
|
compilationTarget = Cx16Target.NAME,
|
||||||
evalStackBaseAddress = null,
|
evalStackBaseAddress = null,
|
||||||
symbolDefs = emptyMap(),
|
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") {
|
test("test launch virtualmachine via target") {
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
|
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>
|
||||||
</OPTIONS>
|
</OPTIONS>
|
||||||
|
|
||||||
<ASMSYMBOLS>
|
<ASMSYMBOLS>
|
||||||
</ASMSYMBOLS>
|
</ASMSYMBOLS>
|
||||||
|
|
||||||
|
<BSS>
|
||||||
|
</BSS>
|
||||||
<VARIABLES>
|
<VARIABLES>
|
||||||
</VARIABLES>
|
</VARIABLES>
|
||||||
|
|
||||||
@ -30,7 +33,7 @@ class TestLaunchEmu: FunSpec({
|
|||||||
<INITGLOBALS>
|
<INITGLOBALS>
|
||||||
</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>
|
</BLOCK>
|
||||||
</PROGRAM>
|
</PROGRAM>
|
||||||
""")
|
""")
|
||||||
|
@ -124,7 +124,7 @@ class TestSubroutines: FunSpec({
|
|||||||
asmfunc.isAsmSubroutine shouldBe true
|
asmfunc.isAsmSubroutine shouldBe true
|
||||||
asmfunc.statements.single() shouldBe instanceOf<InlineAssembly>()
|
asmfunc.statements.single() shouldBe instanceOf<InlineAssembly>()
|
||||||
(asmfunc.statements.single() as InlineAssembly).assembly.trim() shouldBe "rts"
|
(asmfunc.statements.single() as InlineAssembly).assembly.trim() shouldBe "rts"
|
||||||
asmfunc.hasRtsInAsm(C64Target()) shouldBe true
|
asmfunc.hasRtsInAsm() shouldBe true
|
||||||
func.isAsmSubroutine shouldBe false
|
func.isAsmSubroutine shouldBe false
|
||||||
withClue("str param should have been changed to uword") {
|
withClue("str param should have been changed to uword") {
|
||||||
asmfunc.parameters.single().type shouldBe DataType.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.DataType
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
import prog8.code.target.C64Target
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
import prog8tests.helpers.ErrorReporterForTests
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
|
|
||||||
@ -934,4 +935,34 @@ main {
|
|||||||
}"""
|
}"""
|
||||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
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.hasByteAvailable() shouldBe true
|
||||||
zp.hasWordAvailable() shouldBe true
|
zp.hasWordAvailable() shouldBe true
|
||||||
var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
|
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 shouldBeGreaterThan 3u
|
||||||
loc shouldNotBeIn zp.free
|
loc shouldNotBeIn zp.free
|
||||||
val num = zp.availableBytes() / 2
|
val num = zp.availableBytes() / 2
|
||||||
@ -216,18 +216,18 @@ class TestC64Zeropage: FunSpec({
|
|||||||
test("testEfficientAllocation") {
|
test("testEfficientAllocation") {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||||
zp.availableBytes() shouldBe 18
|
zp.availableBytes() shouldBe 18
|
||||||
zp.allocate(emptyList(), DataType.WORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x04u
|
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}.first shouldBe 0x06u
|
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}.first shouldBe 0x0au
|
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}.first shouldBe 0x9bu
|
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}.first shouldBe 0x9eu
|
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}.first shouldBe 0xa5u
|
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}.first shouldBe 0xb0u
|
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}.first shouldBe 0xbeu
|
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}.first shouldBe 0x0eu
|
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}.first shouldBe 0x92u
|
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}.first shouldBe 0x96u
|
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}.first shouldBe 0xf9u
|
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
|
||||||
zp.availableBytes() shouldBe 0
|
zp.availableBytes() shouldBe 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,9 +888,11 @@ class TestProg8Parser: FunSpec( {
|
|||||||
bool bb
|
bool bb
|
||||||
ubyte cc
|
ubyte cc
|
||||||
if cc in [' ', '@', 0] {
|
if cc in [' ', '@', 0] {
|
||||||
|
cx16.r0L++
|
||||||
}
|
}
|
||||||
|
|
||||||
if cc in "email" {
|
if cc in "email" {
|
||||||
|
cx16.r0L++
|
||||||
}
|
}
|
||||||
|
|
||||||
bb = 99 in array
|
bb = 99 in array
|
||||||
|
@ -3,13 +3,19 @@ package prog8tests.ast
|
|||||||
import io.kotest.core.spec.style.FunSpec
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.kotest.matchers.shouldNotBe
|
import io.kotest.matchers.shouldNotBe
|
||||||
|
import io.kotest.matchers.types.instanceOf
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.expressions.BinaryExpression
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.expressions.NumericLiteral
|
||||||
import prog8.ast.expressions.StringLiteral
|
import prog8.ast.expressions.StringLiteral
|
||||||
|
import prog8.ast.statements.Assignment
|
||||||
import prog8.ast.statements.InlineAssembly
|
import prog8.ast.statements.InlineAssembly
|
||||||
import prog8.ast.statements.VarDecl
|
import prog8.ast.statements.VarDecl
|
||||||
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
import prog8.code.target.C64Target
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.compiler.printProgram
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
|
|
||||||
class TestVarious: FunSpec({
|
class TestVarious: FunSpec({
|
||||||
@ -129,5 +135,31 @@ main {
|
|||||||
}"""
|
}"""
|
||||||
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
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.ast.statements.*
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.C64Target
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
import prog8.code.target.c64.C64Zeropage
|
import prog8.code.target.c64.C64Zeropage
|
||||||
import prog8.codegen.cpu6502.AsmGen
|
import prog8.codegen.cpu6502.AsmGen
|
||||||
import prog8.compiler.astprocessing.SymbolTableMaker
|
import prog8.compiler.astprocessing.SymbolTableMaker
|
||||||
@ -173,4 +174,48 @@ main {
|
|||||||
val result = compileText(C64Target(), false, text, writeAssembly = true)
|
val result = compileText(C64Target(), false, text, writeAssembly = true)
|
||||||
result shouldNotBe null
|
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
|
||||||
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
@ -64,4 +64,20 @@ class TestVariables: FunSpec({
|
|||||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
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 {
|
foobar {
|
||||||
str bar = "foo.bar"
|
str barbar = "foo.bar"
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,6 @@ main {
|
|||||||
str myBar = "main.bar"
|
str myBar = "main.bar"
|
||||||
sub start() {
|
sub start() {
|
||||||
txt.print(myBar)
|
txt.print(myBar)
|
||||||
txt.print(foo.bar)
|
txt.print(foobar.barbar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ internal fun compileFile(
|
|||||||
outputDir: Path = prog8tests.helpers.outputDir,
|
outputDir: Path = prog8tests.helpers.outputDir,
|
||||||
errors: IErrorReporter? = null,
|
errors: IErrorReporter? = null,
|
||||||
writeAssembly: Boolean = true,
|
writeAssembly: Boolean = true,
|
||||||
keepIR: Boolean = true,
|
|
||||||
optFloatExpr: Boolean = true
|
optFloatExpr: Boolean = true
|
||||||
) : CompilationResult? {
|
) : CompilationResult? {
|
||||||
val filepath = fileDir.resolve(fileName)
|
val filepath = fileDir.resolve(fileName)
|
||||||
@ -32,7 +31,6 @@ internal fun compileFile(
|
|||||||
quietAssembler = true,
|
quietAssembler = true,
|
||||||
asmListfile = false,
|
asmListfile = false,
|
||||||
experimentalCodegen = false,
|
experimentalCodegen = false,
|
||||||
keepIR = keepIR,
|
|
||||||
platform.name,
|
platform.name,
|
||||||
evalStackBaseAddress = null,
|
evalStackBaseAddress = null,
|
||||||
symbolDefs = emptyMap(),
|
symbolDefs = emptyMap(),
|
||||||
@ -53,12 +51,11 @@ internal fun compileText(
|
|||||||
sourceText: String,
|
sourceText: String,
|
||||||
errors: IErrorReporter? = null,
|
errors: IErrorReporter? = null,
|
||||||
writeAssembly: Boolean = true,
|
writeAssembly: Boolean = true,
|
||||||
keepIR: Boolean = true,
|
|
||||||
optFloatExpr: Boolean = true
|
optFloatExpr: Boolean = true
|
||||||
) : CompilationResult? {
|
) : CompilationResult? {
|
||||||
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
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
|
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||||
filePath.toFile().writeText(sourceText)
|
filePath.toFile().writeText(sourceText)
|
||||||
return compileFile(platform, optimize, filePath.parent, filePath.name,
|
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.C64Target
|
||||||
import prog8.code.target.Cx16Target
|
import prog8.code.target.Cx16Target
|
||||||
import prog8.code.target.VMTarget
|
import prog8.code.target.VMTarget
|
||||||
|
import prog8.intermediate.IRFileReader
|
||||||
|
import prog8.intermediate.IRSubroutine
|
||||||
|
import prog8.intermediate.Opcode
|
||||||
import prog8.vm.VmRunner
|
import prog8.vm.VmRunner
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
import kotlin.io.path.readText
|
import kotlin.io.path.readText
|
||||||
@ -50,7 +53,7 @@ main {
|
|||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
val othertarget = Cx16Target()
|
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 target = VMTarget()
|
||||||
val result = compileText(target, true, src, writeAssembly = true)!!
|
val result = compileText(target, true, src, writeAssembly = true)!!
|
||||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||||
@ -162,7 +165,7 @@ skipLABEL:
|
|||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
val othertarget = Cx16Target()
|
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 target = VMTarget()
|
||||||
val result = compileText(target, true, src, writeAssembly = true)!!
|
val result = compileText(target, true, src, writeAssembly = true)!!
|
||||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||||
@ -205,7 +208,7 @@ main {
|
|||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
val othertarget = Cx16Target()
|
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 target = VMTarget()
|
||||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||||
@ -232,7 +235,7 @@ main {
|
|||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
val othertarget = Cx16Target()
|
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 target = VMTarget()
|
||||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||||
@ -242,4 +245,86 @@ main {
|
|||||||
}
|
}
|
||||||
exc.message shouldContain("does not support non-IR asmsubs")
|
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.AstWalker
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.VMTarget
|
|
||||||
|
|
||||||
|
|
||||||
interface INamedStatement {
|
interface INamedStatement {
|
||||||
@ -268,6 +267,13 @@ class VarDecl(val type: VarDeclType,
|
|||||||
return copy
|
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? =
|
fun findInitializer(program: Program): Assignment? =
|
||||||
(parent as IStatementContainer).statements
|
(parent as IStatementContainer).statements
|
||||||
.asSequence()
|
.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: IAstVisitor) = visitor.visit(this)
|
||||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
fun hasReturnOrRts(target: ICompilationTarget): Boolean {
|
fun hasReturnOrRts(): Boolean {
|
||||||
return if(target.name!= VMTarget.NAME) {
|
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 ||
|
" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" 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
|
// check if there are double removes, keep only the last one
|
||||||
val removals = modifications.filter { it.first is IAstModification.Remove }
|
val removals = modifications.filter { it.first is IAstModification.Remove }
|
||||||
if(removals.isNotEmpty()) {
|
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.
|
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.
|
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.
|
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!
|
When using this option, it may no longer be possible to run the program correctly more than once!
|
||||||
*Experimental feature*: still has some problems!
|
*Experimental feature*: this feature has not been tested much yet.
|
||||||
|
|
||||||
``-optfloatx``
|
``-optfloatx``
|
||||||
Also optimize float expressions if optimizations are enabled.
|
Also optimize float expressions if optimizations are enabled.
|
||||||
@ -166,9 +166,6 @@ One or more .p8 module files
|
|||||||
``-asmlist``
|
``-asmlist``
|
||||||
Generate an assembler listing file as well.
|
Generate an assembler listing file as well.
|
||||||
|
|
||||||
``-keepIR``
|
|
||||||
Keep the IR code in a file (for targets that use it).
|
|
||||||
|
|
||||||
``-expericodegen``
|
``-expericodegen``
|
||||||
Use experimental code generation backend (*incomplete*).
|
Use experimental code generation backend (*incomplete*).
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ Language features
|
|||||||
- Variable data types include signed and unsigned bytes and words, arrays, strings.
|
- 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).
|
- 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.
|
- 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``
|
- 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.
|
- 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.
|
- 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).
|
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
|
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
|
||||||
to 16 bits, 64Kb)
|
to 16 bits, 64Kb)
|
||||||
|
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir) and to relabel the disk.
|
||||||
|
|
||||||
|
|
||||||
psg (cx16 only)
|
psg (cx16 only)
|
||||||
|
@ -400,9 +400,9 @@ library function instead)
|
|||||||
Special types: const and memory-mapped
|
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
|
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).
|
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,
|
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
|
.. data:: %breakpoint
|
||||||
|
|
||||||
Level: not at module scope.
|
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 {{ ... }}
|
.. data:: %asm {{ ... }}
|
||||||
|
|
||||||
Level: not at module scope.
|
Level: not at module scope.
|
||||||
Declares that a piece of *assembly code* is inside the curly braces.
|
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.
|
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).
|
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
|
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 ``}}``,
|
that the assembly code itself contains either of those. If it does contain a ``}}``,
|
||||||
it will confuse the parser.
|
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
|
Identifiers
|
||||||
@ -508,6 +513,7 @@ logical: ``not`` ``and`` ``or`` ``xor``
|
|||||||
Unlike most other programming languages, there is no short-circuit or McCarthy evaluation
|
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
|
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!
|
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``
|
range creation: ``to``
|
||||||
Creates a range of values from the LHS value to the RHS value, inclusive.
|
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.
|
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
|
Software stack for expression evaluation
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@ TODO
|
|||||||
|
|
||||||
For next release
|
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:
|
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 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: 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: 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 peephole opt: reuse registers in chunks (but keep result registers in mind that pass values out!)
|
||||||
- ir: add more optimizations in IRPeepholeOptimizer
|
- 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.
|
- 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!
|
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.....
|
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.
|
- 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.
|
but probably better to rewrite the 6502 codegen on top of the new Ast.
|
||||||
- generate WASM to eventually run prog8 on a browser canvas?
|
- 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 ...
|
- [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?
|
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?)
|
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)
|
- 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
|
- 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)
|
- Add a mechanism to allocate variables into golden ram (or segments really) (see GoldenRam class)
|
||||||
BUT that makes the handling of these types different between the scope they are defined in, and the
|
- block "golden" treated specially: every var in here will be allocated in the Golden ram area
|
||||||
scope they get passed in by reference... unless we make str and array types by-reference ALWAYS?
|
- that block can only contain variables.
|
||||||
BUT that makes simple code accessing them in the declared scope very slow because that then has to always go through
|
- the variables can NOT have initialization values, they will all be set to zero on startup (simple memset)
|
||||||
the pointer rather than directly referencing the variable symbol in the generated asm....
|
- just initialize them yourself in start() if you need a non-zero value
|
||||||
Or maybe make codegen smart to check if it's a subroutine parameter or local declared variable?
|
- 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:
|
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 c128 target, and flesh out its libraries.
|
||||||
- fix the problems in atari 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)
|
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
|
||||||
|
@ -43,7 +43,7 @@ main {
|
|||||||
txt.print_uw(count)
|
txt.print_uw(count)
|
||||||
txt.print(" primes\n")
|
txt.print(" primes\n")
|
||||||
|
|
||||||
float time = c64.RDTIM16() / 60.0
|
float time = c64.RDTIM16() as float / 60.0
|
||||||
floats.print_f(time)
|
floats.print_f(time)
|
||||||
txt.print(" sec total = ")
|
txt.print(" sec total = ")
|
||||||
floats.print_f(time/ITERS)
|
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 gfx2
|
||||||
%import textio
|
%import textio
|
||||||
%import test_stack
|
|
||||||
%import math
|
%import math
|
||||||
|
|
||||||
|
%option no_sysinit
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
gfx2.screen_mode(5)
|
gfx2.screen_mode(5)
|
||||||
|
|
||||||
; demo1()
|
demo1()
|
||||||
; sys.wait(3*60)
|
sys.wait(2*60)
|
||||||
demo2()
|
demo2()
|
||||||
|
|
||||||
gfx2.screen_mode(0)
|
gfx2.screen_mode(0)
|
||||||
txt.print("done!\n")
|
txt.print("done!\n")
|
||||||
|
|
||||||
test_stack.test()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub demo1() {
|
sub demo1() {
|
||||||
@ -192,7 +192,7 @@ main {
|
|||||||
|
|
||||||
ubyte tp
|
ubyte tp
|
||||||
for tp in 0 to 15 {
|
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 textio
|
||||||
%import floats
|
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
|
%option no_sysinit
|
||||||
|
|
||||||
main {
|
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() {
|
sub start() {
|
||||||
float f1
|
txt.print("= 10 ")
|
||||||
|
txt.print_ub(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.nl()
|
txt.nl()
|
||||||
|
zpvar++
|
||||||
|
|
||||||
floats.rndseedf(1.2345)
|
txt.print("= 20 ")
|
||||||
txt.spc()
|
txt.print_ub(zpvar2)
|
||||||
floats.print_f(floats.rndf())
|
|
||||||
txt.spc()
|
|
||||||
floats.print_f(floats.rndf())
|
|
||||||
txt.spc()
|
|
||||||
floats.print_f(floats.rndf())
|
|
||||||
txt.nl()
|
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
|
org.gradle.daemon=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
javaVersion=11
|
javaVersion=11
|
||||||
kotlinVersion=1.7.20
|
kotlinVersion=1.7.21
|
||||||
|
@ -42,8 +42,7 @@ class RequestParser : Take {
|
|||||||
symbolDefs = emptyMap(),
|
symbolDefs = emptyMap(),
|
||||||
quietAssembler = false,
|
quietAssembler = false,
|
||||||
asmListfile = false,
|
asmListfile = false,
|
||||||
experimentalCodegen = false,
|
experimentalCodegen = false
|
||||||
keepIR = false
|
|
||||||
)
|
)
|
||||||
val compilationResult = compileProgram(args)
|
val compilationResult = compileProgram(args)
|
||||||
return RsJson(Jsonding())
|
return RsJson(Jsonding())
|
||||||
|
@ -3,39 +3,57 @@ package prog8.intermediate
|
|||||||
import prog8.code.*
|
import prog8.code.*
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.*
|
import prog8.code.target.*
|
||||||
|
import java.io.StringReader
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import javax.xml.stream.XMLEventReader
|
||||||
|
import javax.xml.stream.XMLInputFactory
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.bufferedReader
|
import kotlin.io.path.inputStream
|
||||||
|
|
||||||
|
|
||||||
class IRFileReader {
|
class IRFileReader {
|
||||||
|
|
||||||
fun read(irSourceCode: CharSequence): IRProgram {
|
fun read(irSourceCode: String): IRProgram {
|
||||||
return parseProgram(irSourceCode.lineSequence().iterator())
|
StringReader(irSourceCode).use { stream ->
|
||||||
|
val reader = XMLInputFactory.newInstance().createXMLEventReader(stream)
|
||||||
|
try {
|
||||||
|
return parseProgram(reader)
|
||||||
|
} finally {
|
||||||
|
reader.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun read(irSourceFile: Path): IRProgram {
|
fun read(irSourceFile: Path): IRProgram {
|
||||||
println("Reading intermediate representation from $irSourceFile")
|
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 {
|
private fun parseProgram(reader: XMLEventReader): IRProgram {
|
||||||
val programPattern = Regex("<PROGRAM NAME=(.+)>")
|
require(reader.nextEvent().isStartDocument)
|
||||||
val line = lines.next()
|
val start = reader.nextEvent().asStartElement()
|
||||||
val match = programPattern.matchEntire(line) ?: throw IRParseException("invalid PROGRAM")
|
require(start.name.localPart=="PROGRAM") { "missing PROGRAM" }
|
||||||
val programName = match.groups[1]!!.value
|
val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value
|
||||||
val options = parseOptions(lines)
|
val options = parseOptions(reader)
|
||||||
val asmsymbols = parseAsmSymbols(lines)
|
val asmsymbols = parseAsmSymbols(reader)
|
||||||
val variables = parseVariables(lines, options.dontReinitGlobals)
|
val bss = parseBss(reader)
|
||||||
val memorymapped = parseMemMapped(lines)
|
val variables = parseVariables(reader, options.dontReinitGlobals)
|
||||||
val slabs = parseSlabs(lines)
|
val memorymapped = parseMemMapped(reader)
|
||||||
val initGlobals = parseInitGlobals(lines)
|
val slabs = parseSlabs(reader)
|
||||||
val blocks = parseBlocksUntilProgramEnd(lines)
|
val initGlobals = parseInitGlobals(reader)
|
||||||
|
val blocks = parseBlocksUntilProgramEnd(reader)
|
||||||
|
|
||||||
val st = IRSymbolTable(null)
|
val st = IRSymbolTable(null)
|
||||||
asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)}
|
asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)}
|
||||||
|
bss.forEach { st.add(it) }
|
||||||
variables.forEach { st.add(it) }
|
variables.forEach { st.add(it) }
|
||||||
memorymapped.forEach { st.add(it) }
|
memorymapped.forEach { st.add(it) }
|
||||||
slabs.forEach { st.add(it) }
|
slabs.forEach { st.add(it) }
|
||||||
@ -50,26 +68,13 @@ class IRFileReader {
|
|||||||
return program
|
return program
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseAsmSymbols(lines: Iterator<String>): Map<String, String> {
|
private fun parseOptions(reader: XMLEventReader): CompilationOptions {
|
||||||
val symbols = mutableMapOf<String, String>()
|
skipText(reader)
|
||||||
var line = lines.next()
|
val start = reader.nextEvent().asStartElement()
|
||||||
while(line.isBlank())
|
require(start.name.localPart=="OPTIONS") { "missing OPTIONS" }
|
||||||
line = lines.next()
|
val text = readText(reader).trim()
|
||||||
if(line!="<ASMSYMBOLS>")
|
require(reader.nextEvent().isEndElement)
|
||||||
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(lines: Iterator<String>): CompilationOptions {
|
|
||||||
var line = lines.next()
|
|
||||||
while(line.isBlank())
|
|
||||||
line = lines.next()
|
|
||||||
var target: ICompilationTarget = VMTarget()
|
var target: ICompilationTarget = VMTarget()
|
||||||
var outputType = OutputType.PRG
|
var outputType = OutputType.PRG
|
||||||
var launcher = CbmPrgLauncherType.NONE
|
var launcher = CbmPrgLauncherType.NONE
|
||||||
@ -80,37 +85,35 @@ class IRFileReader {
|
|||||||
var optimize = true
|
var optimize = true
|
||||||
var evalStackBaseAddress: UInt? = null
|
var evalStackBaseAddress: UInt? = null
|
||||||
var outputDir = Path("")
|
var outputDir = Path("")
|
||||||
if(line!="<OPTIONS>")
|
|
||||||
throw IRParseException("invalid OPTIONS")
|
if(text.isNotBlank()) {
|
||||||
while(true) {
|
text.lineSequence().forEach { line ->
|
||||||
line = lines.next()
|
val (name, value) = line.split('=', limit=2)
|
||||||
if(line=="</OPTIONS>")
|
when(name) {
|
||||||
break
|
"compTarget" -> {
|
||||||
val (name, value) = line.split('=', limit=2)
|
target = when(value) {
|
||||||
when(name) {
|
VMTarget.NAME -> VMTarget()
|
||||||
"compTarget" -> {
|
C64Target.NAME -> C64Target()
|
||||||
target = when(value) {
|
C128Target.NAME -> C128Target()
|
||||||
VMTarget.NAME -> VMTarget()
|
AtariTarget.NAME -> AtariTarget()
|
||||||
C64Target.NAME -> C64Target()
|
Cx16Target.NAME -> Cx16Target()
|
||||||
C128Target.NAME -> C128Target()
|
else -> throw IRParseException("invalid target $value")
|
||||||
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> {
|
private fun parseAsmSymbols(reader: XMLEventReader): Map<String, String> {
|
||||||
var line = lines.next()
|
skipText(reader)
|
||||||
while(line.isBlank())
|
val start = reader.nextEvent().asStartElement()
|
||||||
line = lines.next()
|
require(start.name.localPart=="ASMSYMBOLS") { "missing ASMSYMBOLS" }
|
||||||
if(line!="<VARIABLES>")
|
val text = readText(reader).trim()
|
||||||
throw IRParseException("invalid VARIABLES")
|
require(reader.nextEvent().isEndElement)
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
|
return if(text.isBlank())
|
||||||
}
|
emptyMap()
|
||||||
return variables
|
else
|
||||||
|
text.lineSequence().associate {
|
||||||
|
val (name, value) = it.split('=')
|
||||||
|
name to value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMemMapped(lines: Iterator<String>): List<StMemVar> {
|
private fun parseBss(reader: XMLEventReader): List<StStaticVariable> {
|
||||||
var line = lines.next()
|
skipText(reader)
|
||||||
while(line.isBlank())
|
val start = reader.nextEvent().asStartElement()
|
||||||
line = lines.next()
|
require(start.name.localPart=="BSS") { "missing BSS" }
|
||||||
if(line!="<MEMORYMAPPEDVARIABLES>")
|
val text = readText(reader).trim()
|
||||||
throw IRParseException("invalid MEMORYMAPPEDVARIABLES")
|
require(reader.nextEvent().isEndElement)
|
||||||
val memvars = mutableListOf<StMemVar>()
|
|
||||||
val mappedPattern = Regex("&(.+?)(\\[.+?\\])? (.+)=(.+)")
|
return if(text.isBlank())
|
||||||
while(true) {
|
emptyList()
|
||||||
line = lines.next()
|
else {
|
||||||
if(line=="</MEMORYMAPPEDVARIABLES>")
|
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+) (zp=(.+))?")
|
||||||
break
|
val bssVariables = mutableListOf<StStaticVariable>()
|
||||||
// examples:
|
text.lineSequence().forEach { line ->
|
||||||
// &uword main.start.mapped=49152
|
// example: uword main.start.qq2 zp=DONTCARE
|
||||||
// &ubyte[20] main.start.mappedarray=49408
|
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid BSS $line")
|
||||||
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
|
val (type, arrayspec, name, _, zpwish) = match.destructured
|
||||||
val (type, arrayspec, name, address) = match.destructured
|
if('.' !in name)
|
||||||
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
throw IRParseException("unscoped varname: $name")
|
||||||
val dt: DataType = parseDatatype(type, arraysize!=null)
|
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
||||||
memvars.add(StMemVar(name, dt, address.toUInt(), arraysize, Position.DUMMY))
|
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 {
|
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 {
|
private fun parseRegisterOrStatusflag(regs: String): RegisterOrStatusflag {
|
||||||
var reg: RegisterOrPair? = null
|
var reg: RegisterOrPair? = null
|
||||||
var sf: Statusflag? = null
|
var sf: Statusflag? = null
|
||||||
@ -479,6 +519,8 @@ class IRFileReader {
|
|||||||
return RegisterOrStatusflag(reg, sf)
|
return RegisterOrStatusflag(reg, sf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
|
||||||
|
|
||||||
private fun parsePosition(strpos: String): Position {
|
private fun parsePosition(strpos: String): Position {
|
||||||
// example: "[library:/prog8lib/virtual/textio.p8: line 5 col 2-4]"
|
// example: "[library:/prog8lib/virtual/textio.p8: line 5 col 2-4]"
|
||||||
val match = posPattern.matchEntire(strpos) ?: throw IRParseException("invalid Position")
|
val match = posPattern.matchEntire(strpos) ?: throw IRParseException("invalid Position")
|
||||||
@ -486,4 +528,16 @@ class IRFileReader {
|
|||||||
return Position(file, line.toInt(), startCol.toInt(), endCol.toInt())
|
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
|
package prog8.intermediate
|
||||||
|
|
||||||
import prog8.code.core.ArrayDatatypes
|
import prog8.code.core.*
|
||||||
import prog8.code.core.DataType
|
|
||||||
import prog8.code.core.InternalCompilerException
|
|
||||||
import prog8.code.core.NumericDatatypes
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.bufferedWriter
|
import kotlin.io.path.bufferedWriter
|
||||||
import kotlin.io.path.div
|
import kotlin.io.path.div
|
||||||
@ -11,17 +8,18 @@ import kotlin.io.path.div
|
|||||||
|
|
||||||
class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||||
private val outfile = outfileOverride ?: (irProgram.options.outputDir / ("${irProgram.name}.p8ir"))
|
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 numChunks = 0
|
||||||
private var numInstr = 0
|
private var numInstr = 0
|
||||||
|
|
||||||
|
|
||||||
fun write(): Path {
|
fun write(): Path {
|
||||||
println("Writing intermediate representation to $outfile")
|
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()
|
writeOptions()
|
||||||
writeAsmSymbols()
|
writeAsmSymbols()
|
||||||
writeVariableAllocations()
|
writeVariables()
|
||||||
|
|
||||||
out.write("\n<INITGLOBALS>\n")
|
out.write("\n<INITGLOBALS>\n")
|
||||||
if(!irProgram.options.dontReinitGlobals) {
|
if(!irProgram.options.dontReinitGlobals) {
|
||||||
@ -47,42 +45,46 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
|||||||
|
|
||||||
private fun writeBlocks() {
|
private fun writeBlocks() {
|
||||||
irProgram.blocks.forEach { block ->
|
irProgram.blocks.forEach { block ->
|
||||||
out.write("\n<BLOCK NAME=${block.name} ADDRESS=${block.address} ALIGN=${block.alignment} POS=${block.position}>\n")
|
out.write("\n<BLOCK NAME=\"${block.name}\" ADDRESS=\"${block.address?.toHex()}\" ALIGN=\"${block.alignment}\" POS=\"${block.position}\">\n")
|
||||||
block.inlineAssembly.forEach {
|
block.children.forEach { child ->
|
||||||
writeInlineAsm(it)
|
when(child) {
|
||||||
}
|
is IRAsmSubroutine -> {
|
||||||
block.subroutines.forEach {
|
val clobbers = child.clobbers.joinToString(",")
|
||||||
out.write("<SUB NAME=${it.name} RETURNTYPE=${it.returnType.toString().lowercase()} POS=${it.position}>\n")
|
val returns = child.returns.map { ret ->
|
||||||
out.write("<PARAMS>\n")
|
if(ret.reg.registerOrPair!=null) "${ret.reg.registerOrPair}:${ret.dt.toString().lowercase()}"
|
||||||
it.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") }
|
else "${ret.reg.statusflag}:${ret.dt.toString().lowercase()}"
|
||||||
out.write("</PARAMS>\n")
|
}.joinToString(",")
|
||||||
it.chunks.forEach { chunk ->
|
out.write("<ASMSUB NAME=\"${child.label}\" ADDRESS=\"${child.address?.toHex()}\" CLOBBERS=\"$clobbers\" RETURNS=\"$returns\" POS=\"${child.position}\">\n")
|
||||||
numChunks++
|
out.write("<ASMPARAMS>\n")
|
||||||
when (chunk) {
|
child.parameters.forEach { ret ->
|
||||||
is IRInlineAsmChunk -> writeInlineAsm(chunk)
|
val reg = if(ret.reg.registerOrPair!=null) ret.reg.registerOrPair.toString()
|
||||||
is IRInlineBinaryChunk -> writeInlineBytes(chunk)
|
else ret.reg.statusflag.toString()
|
||||||
is IRCodeChunk -> writeCodeChunk(chunk)
|
out.write("${ret.dt.toString().lowercase()} $reg\n")
|
||||||
else -> throw InternalCompilerException("invalid chunk")
|
}
|
||||||
|
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")
|
out.write("</BLOCK>\n")
|
||||||
}
|
}
|
||||||
@ -90,19 +92,19 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
|||||||
|
|
||||||
private fun writeCodeChunk(chunk: IRCodeChunk) {
|
private fun writeCodeChunk(chunk: IRCodeChunk) {
|
||||||
if(chunk.label!=null)
|
if(chunk.label!=null)
|
||||||
out.write("<C LABEL=${chunk.label}>\n")
|
out.write("<CODE LABEL=\"${chunk.label}\">\n")
|
||||||
else
|
else
|
||||||
out.write("<C>\n")
|
out.write("<CODE>\n")
|
||||||
chunk.instructions.forEach { instr ->
|
chunk.instructions.forEach { instr ->
|
||||||
numInstr++
|
numInstr++
|
||||||
out.write(instr.toString())
|
out.write(instr.toString())
|
||||||
out.write("\n")
|
out.write("\n")
|
||||||
}
|
}
|
||||||
out.write("</C>\n")
|
out.write("</CODE>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeInlineBytes(chunk: IRInlineBinaryChunk) {
|
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) ->
|
chunk.data.withIndex().forEach {(index, byte) ->
|
||||||
out.write(byte.toString(16).padStart(2,'0'))
|
out.write(byte.toString(16).padStart(2,'0'))
|
||||||
if(index and 63 == 63 && index < chunk.data.size-1)
|
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) {
|
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(chunk.assembly)
|
||||||
out.write("\n</INLINEASM>\n")
|
out.write("\n</INLINEASM>\n")
|
||||||
}
|
}
|
||||||
@ -126,23 +128,30 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
|||||||
for(range in irProgram.options.zpReserved) {
|
for(range in irProgram.options.zpReserved) {
|
||||||
out.write("zpReserved=${range.first},${range.last}\n")
|
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("optimize=${irProgram.options.optimize}\n")
|
||||||
out.write("dontReinitGlobals=${irProgram.options.dontReinitGlobals}\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")
|
out.write("outputDir=${irProgram.options.outputDir.toAbsolutePath()}\n")
|
||||||
// other options not yet useful here?
|
// other options not yet useful here?
|
||||||
out.write("</OPTIONS>\n")
|
out.write("</OPTIONS>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeVariableAllocations() {
|
private fun writeVariables() {
|
||||||
|
|
||||||
out.write("\n<VARIABLES>\n")
|
out.write("\n<BSS>\n")
|
||||||
for (variable in irProgram.st.allVariables()) {
|
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 typeStr = getTypeString(variable)
|
||||||
val value: String = when(variable.dt) {
|
val value: String = when(variable.dt) {
|
||||||
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString()
|
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString()
|
||||||
in NumericDatatypes -> (variable.onetimeInitializationNumericValue?.toInt() ?: "").toString()
|
in NumericDatatypes -> (variable.onetimeInitializationNumericValue?.toInt()?.toHex() ?: "").toString()
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
val encoded = irProgram.encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
|
val encoded = irProgram.encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
|
||||||
encoded.joinToString(",") { it.toInt().toString() }
|
encoded.joinToString(",") { it.toInt().toString() }
|
||||||
@ -158,9 +167,9 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
|||||||
if(variable.onetimeInitializationArrayValue!==null) {
|
if(variable.onetimeInitializationArrayValue!==null) {
|
||||||
variable.onetimeInitializationArrayValue!!.joinToString(",") {
|
variable.onetimeInitializationArrayValue!!.joinToString(",") {
|
||||||
if(it.number!=null)
|
if(it.number!=null)
|
||||||
it.number!!.toInt().toString()
|
it.number!!.toInt().toHex()
|
||||||
else
|
else
|
||||||
"&${it.addressOf!!.joinToString(".")}"
|
"@${it.addressOf!!.joinToString(".")}"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"" // array will be zero'd out at program start
|
"" // 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")
|
out.write("\n<MEMORYMAPPEDVARIABLES>\n")
|
||||||
for (variable in irProgram.st.allMemMappedVariables()) {
|
for (variable in irProgram.st.allMemMappedVariables()) {
|
||||||
val typeStr = getTypeString(variable)
|
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")
|
out.write("</MEMORYMAPPEDVARIABLES>\n")
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package prog8.intermediate
|
package prog8.intermediate
|
||||||
|
|
||||||
|
import prog8.code.core.toHex
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Intermediate Representation instructions for the IR Virtual machine.
|
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 not modified (input/readonly value)
|
||||||
>X = X is overwritten with output value (output value)
|
>X = X is overwritten with output value (output value)
|
||||||
<>X = X is modified (input + output)
|
<>X = X is modified (read as input + written as output)
|
||||||
TODO: also encode if memory is read/written/modified?
|
TODO: also encode if *memory* is read/written/modified?
|
||||||
*/
|
*/
|
||||||
@Suppress("BooleanLiteralArgument")
|
@Suppress("BooleanLiteralArgument")
|
||||||
val instructionFormats = mutableMapOf(
|
val instructionFormats = mutableMapOf(
|
||||||
@ -807,7 +809,7 @@ data class IRInstruction(
|
|||||||
result.add(",")
|
result.add(",")
|
||||||
}
|
}
|
||||||
value?.let {
|
value?.let {
|
||||||
result.add(it.toString())
|
result.add(it.toHex())
|
||||||
result.add(",")
|
result.add(",")
|
||||||
}
|
}
|
||||||
fpValue?.let {
|
fpValue?.let {
|
||||||
|
@ -70,29 +70,45 @@ class IRProgram(val name: String,
|
|||||||
|
|
||||||
fun linkChunks() {
|
fun linkChunks() {
|
||||||
fun getLabeledChunks(): Map<String?, IRCodeChunkBase> {
|
fun getLabeledChunks(): Map<String?, IRCodeChunkBase> {
|
||||||
return blocks.flatMap { it.subroutines }.flatMap { it.chunks }.associateBy { it.label } +
|
val result = mutableMapOf<String?, IRCodeChunkBase>()
|
||||||
blocks.flatMap { it.asmSubroutines }.map { it.asmChunk }.associateBy { it.label }
|
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()
|
val labeledChunks = getLabeledChunks()
|
||||||
|
|
||||||
if(globalInits.isNotEmpty()) {
|
if(globalInits.isNotEmpty()) {
|
||||||
if(globalInits.next==null) {
|
if(globalInits.next==null) {
|
||||||
|
// link globalinits to subsequent chunk
|
||||||
val firstBlock = blocks.firstOrNull()
|
val firstBlock = blocks.firstOrNull()
|
||||||
if(firstBlock!=null) {
|
if(firstBlock!=null && firstBlock.isNotEmpty()) {
|
||||||
if(firstBlock.inlineAssembly.isNotEmpty()) {
|
firstBlock.children.forEach { child ->
|
||||||
globalInits.next = firstBlock.inlineAssembly.first()
|
when(child) {
|
||||||
} else if(firstBlock.subroutines.isNotEmpty()) {
|
is IRAsmSubroutine -> throw AssemblyError("cannot link next to asmsub $child")
|
||||||
val firstSub = firstBlock.subroutines.first()
|
is IRCodeChunk -> globalInits.next = child
|
||||||
if(firstSub.chunks.isNotEmpty())
|
is IRInlineAsmChunk -> globalInits.next = child
|
||||||
globalInits.next = firstSub.chunks.first()
|
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) ->
|
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||||
|
|
||||||
fun nextChunk(): IRCodeChunkBase? = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null
|
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
|
chunk.next = next
|
||||||
else if(next is IRInlineAsmChunk)
|
else if(next is IRInlineAsmChunk)
|
||||||
chunk.next = next
|
chunk.next = next
|
||||||
|
else if(next is IRInlineBinaryChunk)
|
||||||
|
chunk.next =next
|
||||||
else
|
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() {
|
fun validate() {
|
||||||
blocks.forEach { block ->
|
blocks.forEach { block ->
|
||||||
if(block.inlineAssembly.isNotEmpty()) {
|
if(block.isNotEmpty()) {
|
||||||
require(block.inlineAssembly.first().label == block.name) { "first block chunk should have block name as its label" }
|
block.children.filterIsInstance<IRInlineAsmChunk>().forEach { chunk -> require(chunk.instructions.isEmpty()) }
|
||||||
}
|
block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
|
||||||
block.inlineAssembly.forEach { chunk ->
|
if(sub.chunks.isNotEmpty()) {
|
||||||
require(chunk.instructions.isEmpty())
|
require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" }
|
||||||
}
|
|
||||||
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" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
sub.chunks.forEach { chunk ->
|
||||||
require(chunk.instructions.isEmpty())
|
if (chunk is IRCodeChunk) {
|
||||||
chunk.instructions.forEach {
|
require(chunk.instructions.isNotEmpty() || chunk.label != null)
|
||||||
if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch)
|
if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump)
|
||||||
require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" }
|
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) }
|
globalInits.instructions.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) }
|
||||||
blocks.forEach {
|
blocks.forEach {block ->
|
||||||
it.inlineAssembly.forEach { chunk -> addUsed(chunk.usedRegisters()) }
|
block.children.forEach { child ->
|
||||||
it.subroutines.flatMap { sub->sub.chunks }.forEach { chunk -> addUsed(chunk.usedRegisters()) }
|
when(child) {
|
||||||
it.asmSubroutines.forEach { asmsub -> addUsed(asmsub.usedRegisters()) }
|
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)
|
return RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs)
|
||||||
@ -200,9 +234,8 @@ class IRBlock(
|
|||||||
val alignment: BlockAlignment,
|
val alignment: BlockAlignment,
|
||||||
val position: Position
|
val position: Position
|
||||||
) {
|
) {
|
||||||
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
|
// TODO not separate lists but just a single list of chunks, like IRSubroutine? (but these are not all chunks...)
|
||||||
val subroutines = mutableListOf<IRSubroutine>()
|
val children = mutableListOf<IIRBlockElement>()
|
||||||
val asmSubroutines = mutableListOf<IRAsmSubroutine>()
|
|
||||||
|
|
||||||
enum class BlockAlignment {
|
enum class BlockAlignment {
|
||||||
NONE,
|
NONE,
|
||||||
@ -210,32 +243,41 @@ class IRBlock(
|
|||||||
PAGE
|
PAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun plusAssign(sub: IRSubroutine) {
|
operator fun plusAssign(sub: IRSubroutine) { children += sub }
|
||||||
subroutines += 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 {
|
fun isEmpty(): Boolean = children.isEmpty() || children.all { it.isEmpty() }
|
||||||
val noAsm = inlineAssembly.isEmpty() || inlineAssembly.all { it.isEmpty() }
|
fun isNotEmpty(): Boolean = !isEmpty()
|
||||||
val noSubs = subroutines.isEmpty() || subroutines.all { it.isEmpty() }
|
|
||||||
val noAsmSubs = asmSubroutines.isEmpty() || asmSubroutines.all { it.isEmpty() }
|
|
||||||
return noAsm && noSubs && noAsmSubs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class IRSubroutine(val name: String,
|
|
||||||
val parameters: List<IRParam>,
|
sealed interface IIRBlockElement {
|
||||||
val returnType: DataType?,
|
val label: String?
|
||||||
val position: Position) {
|
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)
|
class IRParam(val name: String, val dt: DataType)
|
||||||
|
|
||||||
val chunks = mutableListOf<IRCodeChunkBase>()
|
val chunks = mutableListOf<IRCodeChunkBase>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require('.' in name) {"subroutine name is not scoped: $name"}
|
require('.' in label) {"subroutine name is not scoped: $label"}
|
||||||
require(!name.startsWith("main.main.")) {"subroutine name invalid main prefix: $name"}
|
require(!label.startsWith("main.main.")) {"subroutine name invalid main prefix: $label"}
|
||||||
|
|
||||||
// params and return value should not be str
|
// params and return value should not be str
|
||||||
require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"}
|
require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"}
|
||||||
@ -249,34 +291,41 @@ class IRSubroutine(val name: String,
|
|||||||
chunks+= chunk
|
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(
|
class IRAsmSubroutine(
|
||||||
val name: String,
|
override val label: String,
|
||||||
val address: UInt?,
|
val address: UInt?,
|
||||||
val clobbers: Set<CpuRegister>,
|
val clobbers: Set<CpuRegister>,
|
||||||
val parameters: List<Pair<DataType, RegisterOrStatusflag>>,
|
val parameters: List<IRAsmParam>,
|
||||||
val returns: List<Pair<DataType, RegisterOrStatusflag>>,
|
val returns: List<IRAsmParam>,
|
||||||
val asmChunk: IRInlineAsmChunk,
|
val asmChunk: IRInlineAsmChunk,
|
||||||
val position: Position
|
val position: Position
|
||||||
) {
|
): IIRBlockElement {
|
||||||
|
|
||||||
|
class IRAsmParam(val reg: RegisterOrStatusflag, val dt: DataType)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require('.' in name) { "subroutine name is not scoped: $name" }
|
require('.' in label) { "subroutine name is not scoped: $label" }
|
||||||
require(!name.startsWith("main.main.")) { "subroutine name invalid main prefix: $name" }
|
require(!label.startsWith("main.main.")) { "subroutine name invalid main prefix: $label" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val registersUsed by lazy { registersUsedInAssembly(asmChunk.isIR, asmChunk.assembly) }
|
private val registersUsed by lazy { registersUsedInAssembly(asmChunk.isIR, asmChunk.assembly) }
|
||||||
|
|
||||||
fun usedRegisters() = registersUsed
|
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>()
|
val instructions = mutableListOf<IRInstruction>()
|
||||||
|
|
||||||
abstract fun isEmpty(): Boolean
|
abstract override fun isEmpty(): Boolean
|
||||||
abstract fun isNotEmpty(): Boolean
|
abstract override fun isNotEmpty(): Boolean
|
||||||
abstract fun usedRegisters(): RegistersUsed
|
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