Compare commits

...

60 Commits
v8.7 ... v8.8

Author SHA1 Message Date
475e927178 version 8.8 2022-12-17 23:00:49 +01:00
ca7932c4f0 no longer do return value optimization with tempvar, this caused invalid code sometimes. 2022-12-14 22:33:16 +01:00
8ab47d3321 fix_autostart_square() now preserves X register correctly 2022-12-14 01:07:44 +01:00
def7e87151 fixed silly if-goto expression code in IR codegen where it used too many branching instructions 2022-12-12 22:47:15 +01:00
27568c2bef fixed silly code generated by some NOT-expressions (unused temporary) 2022-12-12 21:57:22 +01:00
0694a187d7 unsigned>0 now optimized into unsigned!=0 2022-12-12 20:37:57 +01:00
832601b36b workaround for black square issue at start 2022-12-11 11:48:41 +01:00
578969c34c optimize redundant rts/bra or rts/jmp generation in when statement 2022-12-10 17:21:15 +01:00
d1d0115aed removed unused option 'keepIR' 2022-12-09 18:44:44 +01:00
c89e6ebfab clarify 2022-12-08 22:21:45 +01:00
ca1089b881 optimized codegen for logical expressions with simple right operand (such as c64.READST() & $40 ) 2022-12-06 20:23:56 +01:00
a1d04f2aad added more $03xx vector definitions to C64/C128/CX16 syslib 2022-12-06 20:23:56 +01:00
bf0604133c fix error in IR for inline asm and BSS vars. 2022-12-04 16:48:44 +01:00
a82b2da16e Fix some FP related assignment issues in 6502 codegen. 2022-12-04 13:03:38 +01:00
f2273c0acc fix several FP rom routine addresses on cx16. 2022-12-03 19:56:54 +01:00
17bedac96c vm: memory is randomized on start instead of 0. P8ir file now has BSS segment. Vm clears BSS vars to 0. 2022-12-03 17:46:06 +01:00
4831fad27a x16 emulators are now launched with PULSE_LATENCY_MSEC=10 env setting to mitigate static noise 2022-12-03 16:19:26 +01:00
5e896cf582 preparing to add Golden RAM 2022-12-03 00:21:31 +01:00
add3491c57 fix possible vardecl issue for prefixed params 2022-11-30 22:56:54 +01:00
f470576822 it's now possible to use symbols that are the same name as 6502 instructions
because these are now prefixed internally before generating assembly.
2022-11-30 18:39:56 +01:00
10760a53a8 optimize cmp word equal/notequal 2022-11-29 20:14:35 +01:00
eee805183c don't overwrite temp vars in complex comparison expressions. Fixes #89 2022-11-29 04:13:25 +01:00
b8fb391022 - ir codegen now allows subroutine having the same name as its block
this is not possible for the 6502 codegen due to 64tass scoping limitation
2022-11-28 21:54:33 +01:00
3c698f1584 fileseek for writing not right now 2022-11-27 21:52:18 +01:00
2fad52d684 the adpcm example can now read wav files directly (so no need anymore to extract the binary frame data from them) 2022-11-27 21:37:40 +01:00
ec64a68a71 fixed compiler crash: unsigned = (-(unsigned as word) as uword) 2022-11-27 17:25:47 +01:00
db55562f6a fixed adpcm playback 2022-11-27 16:36:30 +01:00
d8409a9d2b fix compiler crash: if uwordvar > label 2022-11-26 14:39:03 +01:00
0d0ce6eec1 adpcm plays pcm 2022-11-24 21:03:50 +01:00
483f313eda ir: keep correct child node order in blocks 2022-11-24 01:19:48 +01:00
7b6c742178 fixed diskio.f_read() for small read sizes 2022-11-24 00:23:37 +01:00
d4a35ba6ff got rid of diskio.have_first_byte overhead 2022-11-23 21:53:36 +01:00
68b112837a fix cx16logo.logo() printing correct newlines 2022-11-23 02:25:20 +01:00
e2f20ebf94 fix crash on empty conditional branch statement (if_cc { } ) 2022-11-23 02:14:48 +01:00
f870e4965a added cx16diskio.f_seek() function to seek to a position in an opened file
f_open uses channel 12 now, f_open_w uses 13
2022-11-23 01:48:04 +01:00
7ebcb219d6 void func() now gives warning if func doesn't return a value 2022-11-22 22:54:40 +01:00
c21913a66b ir: keep order of children in block 2022-11-22 02:04:24 +01:00
77e956a29f API change: diskio.list_files doesn't have an internal buffer anymore, you now have to supply a buffer + size yourself. Renamed to list_filenames 2022-11-20 23:27:22 +01:00
08275c406a added chdir/mkdir/rmdir/relabel to cx16diskio 2022-11-20 22:59:44 +01:00
2931e1b87b diskio file lister routines now also put file type (prg, seq, dir) in new diskio.list_filetype variable 2022-11-20 20:22:09 +01:00
153b422496 cx16: retain display mode (composite etc) 2022-11-20 19:19:01 +01:00
0f6a6d6fea attempt to make gfx2 screen mode 0 cleanup more robust on real hardware 2022-11-18 22:53:28 +01:00
91fdb3e2d4 ir: store labels in blocks, but still useless 2022-11-17 00:37:45 +01:00
d8e87bd881 make uword xx = 1<<shift into a word shifting 2022-11-16 01:39:34 +01:00
922033c1b2 main block element order now remains the same as in source 2022-11-16 00:32:00 +01:00
df1793efbf fixed: word << 12 is suddenly an uword (with optimizer on) 2022-11-15 03:00:41 +01:00
836a2700f2 func(x>>1) no longer uses slow stack eval 2022-11-15 02:49:40 +01:00
8f3aaf77a1 fix optimizer hanging on uword xx :: xx >>= 8 / xx=msb(xx) 2022-11-15 01:40:13 +01:00
00c059e5b1 adding cx16/adpcm example 2022-11-15 01:17:28 +01:00
f4f355c74a added cx16/diskspeed example 2022-11-14 17:55:55 +01:00
b465fc5aaf fix bug in word array containment check (prog8_lib.containment_wordarray) that could hang the loop 2022-11-12 23:19:01 +01:00
2d78eaa48d fix gfx2 text color, added cx16 snow example 2022-11-12 22:08:07 +01:00
d08451bccc ir: Block can now contain inline binary 2022-11-12 20:17:23 +01:00
d8e785aed0 ir: fix too greedy chunk removal 2022-11-12 19:56:54 +01:00
267b6f49b5 IRFileReader parses the p8ir file with xml parser 2022-11-12 16:51:20 +01:00
e6688f4b9d clearer error for VM limitation cannot load label address as value 2022-11-12 13:45:02 +01:00
9d7b9771c2 p8ir file format is now valid XML 2022-11-11 23:35:52 +01:00
136a9a39de kotlin 1.7.21 2022-11-10 22:52:07 +01:00
3dcf628fdb fixed subroutine name shadow check 2022-11-10 22:51:37 +01:00
e614e9787a ir: write values as hex into p8ir file 2022-11-08 21:59:05 +01:00
107 changed files with 3346 additions and 1267 deletions

View File

@ -8,10 +8,6 @@ import prog8.code.core.*
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
*/
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
fun print() = printIndented(0)
override fun printProperties() { }
/**
* The table as a flat mapping of scoped names to the StNode.
* This gives the fastest lookup possible (no need to traverse tree nodes)
@ -138,29 +134,6 @@ open class StNode(val name: String,
}
}
fun printIndented(indent: Int) {
print(" ".repeat(indent))
when(type) {
StNodeType.GLOBAL -> print("SYMBOL-TABLE:")
StNodeType.BLOCK -> print("(B) ")
StNodeType.SUBROUTINE -> print("(S) ")
StNodeType.LABEL -> print("(L) ")
StNodeType.STATICVAR -> print("(V) ")
StNodeType.MEMVAR -> print("(M) ")
StNodeType.MEMORYSLAB -> print("(MS) ")
StNodeType.CONSTANT -> print("(C) ")
StNodeType.BUILTINFUNC -> print("(F) ")
StNodeType.ROMSUB -> print("(R) ")
}
printProperties()
println()
children.forEach { (_, node) -> node.printIndented(indent+1) }
}
open fun printProperties() {
print("$name ")
}
fun add(child: StNode) {
children[child.name] = child
child.parent = this
@ -201,18 +174,11 @@ class StStaticVariable(name: String,
require(length == onetimeInitializationStringValue.first.length+1)
}
}
override fun printProperties() {
print("$name dt=$dt zpw=$zpwish bss=$bss")
}
}
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
StNode(name, StNodeType.CONSTANT, position) {
override fun printProperties() {
print("$name dt=$dt value=$value")
}
}
@ -222,9 +188,6 @@ class StMemVar(name: String,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
position: Position) :
StNode(name, StNodeType.MEMVAR, position) {
override fun printProperties() {
print("$name dt=$dt address=${address.toHex()}")
}
}
class StMemorySlab(
@ -234,16 +197,10 @@ class StMemorySlab(
position: Position
):
StNode(name, StNodeType.MEMORYSLAB, position) {
override fun printProperties() {
print("$name size=$size align=$align")
}
}
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
StNode(name, StNodeType.SUBROUTINE, position) {
override fun printProperties() {
print(name)
}
}
@ -253,9 +210,6 @@ class StRomSub(name: String,
val returns: List<RegisterOrStatusflag>,
position: Position) :
StNode(name, StNodeType.ROMSUB, position) {
override fun printProperties() {
print("$name address=${address.toHex()}")
}
}

View File

@ -20,7 +20,6 @@ class CompilationOptions(val output: OutputType,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false,
var keepIR: Boolean = false,
var evalStackBaseAddress: UInt? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()

View File

@ -17,11 +17,11 @@ interface IMachineDefinition {
var ESTACK_HI: UInt
val PROGRAM_LOAD_ADDRESS : UInt
val opcodeNames: Set<String>
var zeropage: Zeropage
val cpu: CpuType
var zeropage: Zeropage
var golden: GoldenRam
fun initializeZeropage(compilerOptions: CompilationOptions)
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): String
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
@ -31,5 +31,7 @@ interface IMachineDefinition {
require(evalStackBaseAddress and 255u == 0u)
ESTACK_LO = evalStackBaseAddress
ESTACK_HI = evalStackBaseAddress + 256u
require(ESTACK_LO !in golden.region && ESTACK_HI !in golden.region) { "user-set ESTACK can't be in GOLDEN ram" }
}
}

View File

@ -5,21 +5,31 @@ import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
class ZeropageAllocationError(message: String) : Exception(message)
class MemAllocationError(message: String) : Exception(message)
abstract class Zeropage(protected val options: CompilationOptions) {
abstract class MemoryAllocator(protected val options: CompilationOptions) {
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
abstract fun allocate(name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError>
}
abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
data class ZpAllocation(val address: UInt, val dt: DataType, val size: Int)
// the variables allocated into Zeropage.
// name (scoped) ==> pair of address to (Datatype + bytesize)
val allocatedVariables = mutableMapOf<List<String>, ZpAllocation>()
val allocatedVariables = mutableMapOf<List<String>, VarAllocation>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
@ -41,17 +51,16 @@ abstract class Zeropage(protected val options: CompilationOptions) {
return free.windowed(2).any { it[0] == it[1] - 1u }
}
fun allocate(name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter
): Result<Pair<UInt, Int>, ZeropageAllocationError> {
override fun allocate(name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
if(options.zeropage== ZeropageType.DONTUSE)
return Err(ZeropageAllocationError("zero page usage has been disabled"))
return Err(MemAllocationError("zero page usage has been disabled"))
val size: Int =
when (datatype) {
@ -72,9 +81,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
else
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
memsize
} else return Err(ZeropageAllocationError("floating point option not enabled"))
} else return Err(MemAllocationError("floating point option not enabled"))
}
else -> return Err(ZeropageAllocationError("cannot put datatype $datatype in zeropage"))
else -> throw MemAllocationError("weird dt")
}
synchronized(this) {
@ -82,18 +91,18 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if(oneSeparateByteFree(candidate))
return Ok(Pair(makeAllocation(candidate, 1, datatype, name), 1))
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
}
return Ok(Pair(makeAllocation(free[0], 1, datatype, name), 1))
return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
}
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if (sequentialFree(candidate, size))
return Ok(Pair(makeAllocation(candidate, size, datatype, name), size))
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
}
}
}
return Err(ZeropageAllocationError("no more free space in ZP to allocate $size sequential bytes"))
return Err(MemAllocationError("no more free space in ZP to allocate $size sequential bytes"))
}
private fun reserve(range: UIntRange) = free.removeAll(range)
@ -103,9 +112,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
free.removeAll(address until address+size.toUInt())
if(name.isNotEmpty()) {
allocatedVariables[name] = when(datatype) {
in NumericDatatypes -> ZpAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
DataType.STR -> ZpAllocation(address, datatype, size)
in ArrayDatatypes -> ZpAllocation(address, datatype, size)
in NumericDatatypes -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
DataType.STR -> VarAllocation(address, datatype, size)
in ArrayDatatypes -> VarAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt")
}
}
@ -120,3 +129,37 @@ abstract class Zeropage(protected val options: CompilationOptions) {
abstract fun allocateCx16VirtualRegisters()
}
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
private var nextLocation: UInt = region.first
override fun allocate(
name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
val size: Int =
when (datatype) {
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
DataType.STR, in ArrayDatatypes -> {
options.compTarget.memorySize(datatype, numElements!!)
}
DataType.FLOAT -> {
if (options.floats) {
options.compTarget.memorySize(DataType.FLOAT)
} else return Err(MemAllocationError("floating point option not enabled"))
}
else -> throw MemAllocationError("weird dt")
}
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
val result = Ok(VarAllocation(nextLocation, datatype, size))
nextLocation += size.toUInt()
result
} else
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
}
}

View File

@ -1,7 +1,6 @@
package prog8.code.target.atari
import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import java.nio.file.Path
@ -19,6 +18,7 @@ class AtariMachineDefinition: IMachineDefinition {
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
@ -57,9 +57,8 @@ class AtariMachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
override fun initializeZeropage(compilerOptions: CompilationOptions) {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = AtariZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
override val opcodeNames = normal6502instructions
}

View File

@ -1,7 +1,6 @@
package prog8.code.target.c128
import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
@ -18,8 +17,8 @@ class C128MachineDefinition: IMachineDefinition {
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@ -47,9 +46,8 @@ class C128MachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
}
override val opcodeNames = normal6502instructions
}

View File

@ -18,8 +18,8 @@ class C64MachineDefinition: IMachineDefinition {
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@ -55,22 +55,9 @@ class C64MachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0xc000u until ESTACK_LO)
}
override val opcodeNames = normal6502instructions
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
internal val normal6502instructions = setOf(
"adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")

View File

@ -83,12 +83,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+reg*2).toUInt())
free.remove((5+reg*2).toUInt())
}

View File

@ -17,8 +17,8 @@ class CX16MachineDefinition: IMachineDefinition {
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
@ -50,28 +50,16 @@ class CX16MachineDefinition: IMachineDefinition {
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0x0600u until 0x0800u)
}
// 65c02 opcodes, these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "inx", "iny", "jmp", "jsr",
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
"sec", "sed", "sei",
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
"rmb", "smb", "stp", "wai")
}

View File

@ -58,12 +58,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
}
}
}

View File

@ -1,9 +1,6 @@
package prog8.code.target.virtual
import prog8.code.core.CompilationOptions
import prog8.code.core.CpuType
import prog8.code.core.IMachineDefinition
import prog8.code.core.Zeropage
import prog8.code.core.*
import java.nio.file.Path
import kotlin.io.path.isReadable
import kotlin.io.path.name
@ -20,8 +17,8 @@ class VirtualMachineDefinition: IMachineDefinition {
override var ESTACK_LO = 0u // not actually used
override var ESTACK_HI = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
@ -47,11 +44,9 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = false
override fun initializeZeropage(compilerOptions: CompilationOptions) {}
override val opcodeNames = emptySet<String>()
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
}
interface IVirtualMachineRunner {
fun runProgram(irSource: CharSequence)
fun runProgram(irSource: String)
}

View File

@ -375,7 +375,7 @@ class AsmGen(internal val program: Program,
}
}
DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
out(
"""
lda $indexName
@ -406,7 +406,7 @@ class AsmGen(internal val program: Program,
}
}
DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
out(
"""
lda $indexName
@ -605,7 +605,7 @@ class AsmGen(internal val program: Program,
}
private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
require(count in 257..65535)
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
val repeatLabel = program.makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
@ -667,7 +667,7 @@ $repeatLabel lda $counterVar
}
private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
require(count in 2..256)
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
val repeatLabel = program.makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
@ -794,7 +794,7 @@ $repeatLabel lda $counterVar
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
throw AssemblyError("only else part contains code, shoud have been switched already")
val jump = stmt.truepart.statements.first() as? Jump
val jump = stmt.truepart.statements.firstOrNull() as? Jump
if(jump!=null) {
// branch with only a jump (goto)
val instruction = branchInstruction(stmt.condition, false)
@ -812,11 +812,13 @@ $repeatLabel lda $counterVar
translate(stmt.elsepart)
} else {
if(stmt.elsepart.isEmpty()) {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = program.makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
if(stmt.truepart.isNotEmpty()) {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = program.makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
}
} else {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = program.makeLabel("branch_else")
@ -1068,11 +1070,20 @@ $repeatLabel lda $counterVar
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
if(ptrAndIndex.second.isSimple) {
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
} else {
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, true)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
@ -1080,9 +1091,16 @@ $repeatLabel lda $counterVar
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
if(ptrAndIndex.second.isSimple) {
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
} else {
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
out(" lda (P8ZP_SCRATCH_W2),y")
}
}
}
return true
@ -1576,8 +1594,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -1611,8 +1635,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -1648,8 +1678,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel")
}
@ -1687,8 +1724,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
}
@ -1727,8 +1771,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -1764,8 +1814,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -1807,8 +1863,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2")
}
@ -1851,8 +1914,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleLeftOperand(left, right, ::code))
return
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
}
@ -1892,8 +1962,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -1929,8 +2005,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -1974,8 +2056,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
}
@ -2022,8 +2111,15 @@ $repeatLabel lda $counterVar
return code(asmVariableName(left))
}
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
}
@ -2059,8 +2155,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -2093,8 +2195,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
@ -2129,8 +2237,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
}
@ -2168,8 +2283,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
}
@ -2204,8 +2326,17 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(right, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
restoreRegisterStack(CpuRegister.A, false)
}
out(" cmp P8ZP_SCRATCH_B1 | bne $jumpIfFalseLabel")
}
@ -2241,8 +2372,17 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(right, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
restoreRegisterStack(CpuRegister.A, false)
}
return code("P8ZP_SCRATCH_B1")
}
@ -2308,8 +2448,18 @@ $repeatLabel lda $counterVar
""")
}
else -> {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
out("""
cmp P8ZP_SCRATCH_W2
bne $jumpIfFalseLabel
@ -2384,8 +2534,18 @@ $repeatLabel lda $counterVar
+""")
}
else -> {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else if (right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
out("""
cmp P8ZP_SCRATCH_W2
bne +
@ -2780,7 +2940,7 @@ $repeatLabel lda $counterVar
val parameter = target.subroutineParameter
if(parameter!=null) {
val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs" }
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs ${sub.position}" }
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) {
@ -2866,17 +3026,27 @@ $repeatLabel lda $counterVar
}
}
internal fun popCpuStack(dt: DataType, targetAsmVarName: String) {
when(dt) {
in ByteDatatypes -> out(" pla | sta $targetAsmVarName")
in WordDatatypes -> out(" pla | sta $targetAsmVarName+1 | pla | sta $targetAsmVarName")
else -> throw AssemblyError("can't pop $dt")
}
}
internal fun pushCpuStack(dt: DataType, value: Expression) {
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(dt in ByteDatatypes) {
assignExpressionToRegister(value, RegisterOrPair.A, signed)
out(" pha")
} else {
} else if(dt in WordDatatypes) {
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
if (isTargetCpu(CpuType.CPU65c02))
out(" pha | phy")
else
out(" pha | tya | pha")
} else {
throw AssemblyError("can't push $dt")
}
}

View File

@ -44,7 +44,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods= optimizeJsrRts(linesByFour)
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@ -486,8 +486,11 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
return mods
}
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
val first = lines[0].value
@ -496,6 +499,28 @@ private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
}
else if (" rts" in first || "\trts" in first) {
if (" jmp" in second || "\tjmp" in second)
mods += Modification(lines[1].index, true, null)
else if (" bra" in second || "\tbra" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcc" in second || "\tbcc" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcs" in second || "\tbcs" in second)
mods += Modification(lines[1].index, true, null)
else if (" beq" in second || "\tbeq" in second)
mods += Modification(lines[1].index, true, null)
else if (" bne" in second || "\tbne" in second)
mods += Modification(lines[1].index, true, null)
else if (" bmi" in second || "\tbmi" in second)
mods += Modification(lines[1].index, true, null)
else if (" bpl" in second || "\tbpl" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvs" in second || "\tbvs" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvc" in second || "\tbvc" in second)
mods += Modification(lines[1].index, true, null)
}
}
return mods
}

View File

@ -254,15 +254,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
} else {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
}
}
else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
}
}
} else
@ -288,13 +300,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+""")
}
else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
} else {
asmgen.pushCpuStack(DataType.UWORD, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
}
}
}
} else
@ -306,7 +330,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
if(discardResult || fcall !is BuiltinFunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
slabname.linkParents(fcall)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))

View File

@ -124,7 +124,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
require(callee.isAsmSubroutine)
require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
if(callee.parameters.isEmpty())
return

View File

@ -202,16 +202,10 @@ internal class ProgramAndVarsGen(
asmsubs2asm(block.statements)
asmgen.out("")
asmgen.out("; subroutines in this block")
// First translate regular statements, and then put the subroutines at the end.
// (regular statements = everything except the initialization assignments;
// these will be part of the prog8_init_vars init routine generated below)
val initializers = blockVariableInitializers.getValue(block)
val statements = block.statements.filterNot { it in initializers }
val (subroutine, stmts) = statements.partition { it is Subroutine }
stmts.forEach { asmgen.translate(it) }
subroutine.forEach { asmgen.translate(it) }
val notInitializers = block.statements.filterNot { it in initializers }
notInitializers.forEach { asmgen.translate(it) }
if(!options.dontReinitGlobals) {
// generate subroutine to initialize block-level (global) variables
@ -435,13 +429,13 @@ internal class ProgramAndVarsGen(
private class ZpStringWithInitial(
val name: List<String>,
val alloc: Zeropage.ZpAllocation,
val alloc: MemoryAllocator.VarAllocation,
val value: Pair<String, Encoding>
)
private class ZpArrayWithInitial(
val name: List<String>,
val alloc: Zeropage.ZpAllocation,
val alloc: MemoryAllocator.VarAllocation,
val value: StArray
)

View File

@ -16,7 +16,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
private val zeropage = options.compTarget.machine.zeropage
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
init {
allocateZeropageVariables()

View File

@ -207,7 +207,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
init {
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source dt size must be less or equal to target dt size at $position"
}

View File

@ -27,13 +27,6 @@ internal class AssignmentAsmGen(private val program: Program,
translateNormalAssignment(assign)
}
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, origtarget.datatype, origtarget.scope,
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
} else origtarget
}
fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
@ -299,7 +292,7 @@ internal class AssignmentAsmGen(private val program: Program,
)
when (value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign)
"-" -> augmentableAsmGen.inplaceNegate(assign, true)
"~" -> augmentableAsmGen.inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
@ -385,48 +378,60 @@ internal class AssignmentAsmGen(private val program: Program,
if(!expr.inferType(program).isInteger)
return false
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
expr.left.isSimple && expr.right.isSimple) {
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
asmgen.restoreRegisterStack(CpuRegister.A, false)
when (expr.operator) {
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(assign.target, CpuRegister.A)
fun simpleLogicalBytesExpr() {
// both left and right expression operands are simple.
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
asmgen.restoreRegisterStack(CpuRegister.A, false)
when (expr.operator) {
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator")
}
return true
assignRegisterByte(assign.target, CpuRegister.A)
}
if(expr.left.inferType(program).isWords && expr.right.inferType(program).isWords &&
expr.left.isSimple && expr.right.isSimple) {
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
when (expr.operator) {
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
else -> throw AssemblyError("invalid operator")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
fun simpleLogicalWordsExpr() {
// both left and right expression operands are simple.
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
when (expr.operator) {
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
else -> throw AssemblyError("invalid operator")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
}
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
if (expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes) {
if (expr.right.isSimple) {
simpleLogicalBytesExpr()
return true
}
}
if (expr.left.inferType(program).isWords && expr.right.inferType(program).isWords) {
if (expr.right.isSimple) {
simpleLogicalWordsExpr()
return true
}
return true
}
return false
}
@ -622,6 +627,58 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
}
else if(expr.operator=="<<" || expr.operator==">>") {
val shifts = expr.right.constValue(program)?.number?.toInt()
if(shifts!=null) {
val dt = expr.left.inferType(program)
if(dt.isBytes && shifts in 0..7) {
val signed = dt istype DataType.BYTE
assignExpressionToRegister(expr.left, RegisterOrPair.A, signed)
if(expr.operator=="<<") {
repeat(shifts) {
asmgen.out(" asl a")
}
} else {
if(signed && shifts>0) {
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
} else {
repeat(shifts) {
asmgen.out(" lsr a")
}
}
}
assignRegisterByte(assign.target, CpuRegister.A)
return true
} else if(dt.isWords && shifts in 0..7) {
val signed = dt istype DataType.WORD
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
if(expr.operator=="<<") {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
repeat(shifts) {
asmgen.out(" asl a | rol P8ZP_SCRATCH_B1")
}
asmgen.out(" ldy P8ZP_SCRATCH_B1")
}
} else {
if(signed) {
// TODO("shift AY >> $shifts signed")
return false
} else {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
repeat(shifts) {
asmgen.out(" lsr P8ZP_SCRATCH_B1 | ror a")
}
asmgen.out(" ldy P8ZP_SCRATCH_B1")
}
}
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
}
}
return false
}
@ -757,7 +814,7 @@ internal class AssignmentAsmGen(private val program: Program,
if(variable.origin!=VarDeclOrigin.USERCODE) {
when(variable.datatype) {
DataType.STR -> {
require(elementDt.isBytes)
require(elementDt.isBytes) { "must be byte string ${variable.position}" }
val stringVal = variable.value as StringLiteral
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
@ -773,7 +830,7 @@ internal class AssignmentAsmGen(private val program: Program,
throw AssemblyError("containment check of floats not supported")
}
in ArrayDatatypes -> {
require(elementDt.isInteger)
require(elementDt.isInteger) { "must be integer array ${variable.position}" }
val arrayVal = variable.value as ArrayLiteral
val dt = elementDt.getOr(DataType.UNDEFINED)
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
@ -888,11 +945,9 @@ internal class AssignmentAsmGen(private val program: Program,
is NumericLiteral -> {
val address = (value.addressExpression as NumericLiteral).number.toUInt()
assignMemoryByteIntoWord(target, address, null)
return
}
is IdentifierReference -> {
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
return
}
is BinaryExpression -> {
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
@ -906,6 +961,7 @@ internal class AssignmentAsmGen(private val program: Program,
assignViaExprEval(value.addressExpression)
}
}
return
}
}
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
@ -914,7 +970,7 @@ internal class AssignmentAsmGen(private val program: Program,
// special case optimizations
if(target.kind== TargetStorageKind.VARIABLE) {
if(target.kind == TargetStorageKind.VARIABLE) {
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
@ -992,11 +1048,12 @@ internal class AssignmentAsmGen(private val program: Program,
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
return
} else {
if(!(valueDt isAssignableTo targetDt)) {
if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
return if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
// word to byte, just take the lsb
return assignCastViaLsbFunc(value, target)
assignCastViaLsbFunc(value, target)
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
// word to word, just assign
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
@ -1006,43 +1063,85 @@ internal class AssignmentAsmGen(private val program: Program,
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
// byte to word, just assign
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
}
else
} else
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
}
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
}
return
}
if(targetDt in IntegerDatatypes && valueDt in IntegerDatatypes && valueDt.isAssignableTo(targetDt)) {
require(targetDt in WordDatatypes && valueDt in ByteDatatypes) { "should be byte to word assignment ${origTypeCastExpression.position}"}
when(target.kind) {
// TargetStorageKind.VARIABLE -> {
// This has been handled already earlier on line 961.
// // byte to word, just assign to registers first, then assign to variable
// assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
// assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, targetDt)
// return
// }
TargetStorageKind.ARRAY -> {
// byte to word, just assign to registers first, then assign into array
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
TargetStorageKind.REGISTER -> {
// byte to word, just assign to registers
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
return
}
TargetStorageKind.STACK -> {
// byte to word, just assign to registers first, then push onto stack
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
asmgen.out("""
sta P8ESTACK_LO,x
tya
sta P8ESTACK_HI,x
dex""")
return
}
else -> throw AssemblyError("weird target")
}
}
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
when(valueDt) {
DataType.UBYTE -> {
assignExpressionToRegister(value, RegisterOrPair.Y, false)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.FREADUY")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
DataType.BYTE -> {
assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.FREADSA")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
DataType.UWORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.GIVUAYFAY")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
DataType.WORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.GIVAYFAY")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
else -> throw AssemblyError("invalid dt")
}
if(target.register==RegisterOrPair.FAC2) {
asmgen.out(" jsr floats.MOVEF")
}
return
} else {
// No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
asmgen.assignExpressionTo(origTypeCastExpression, target)
return
}
}
@ -1199,14 +1298,10 @@ internal class AssignmentAsmGen(private val program: Program,
DataType.UWORD, DataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | stz $targetAsmVarName+1")
" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
DataType.FLOAT -> {
when(regs) {
@ -2004,7 +2099,7 @@ internal class AssignmentAsmGen(private val program: Program,
// we make an exception in the type check for assigning something to a register pair AX, AY or XY
// these will be correctly typecasted from a byte to a word value here
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
require(target.datatype in ByteDatatypes)
require(target.datatype in ByteDatatypes) { "assign target must be byte type ${target.origAstTarget?.position ?: ""}"}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -2093,7 +2188,9 @@ internal class AssignmentAsmGen(private val program: Program,
}
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes) {
"assign target must be word type ${target.origAstTarget?.position ?: ""}"
}
if(target.datatype==DataType.FLOAT)
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")

View File

@ -16,14 +16,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
) {
fun translate(assign: AsmAssignment) {
require(assign.isAugmentable)
require(assign.source.kind== SourceStorageKind.EXPRESSION)
require(assign.source.kind == SourceStorageKind.EXPRESSION) {
"non-expression assign value should be handled elsewhere ${assign.position}"
}
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign)
"-" -> inplaceNegate(assign, false)
"~" -> inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
@ -1871,9 +1873,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
internal fun inplaceNegate(assign: AsmAssignment) {
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
val target = assign.target
when (assign.target.datatype) {
val datatype = if(ignoreDatatype) {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> DataType.BYTE
DataType.UWORD, DataType.WORD -> DataType.WORD
else -> target.datatype
}
} else target.datatype
when (datatype) {
DataType.BYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {

View File

@ -258,7 +258,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
else if(memory!=null) {
require(vmDt== IRDataType.BYTE)
require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
if(zero) {
if(memory.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }

View File

@ -4,13 +4,18 @@ import prog8.code.StRomSub
import prog8.code.StStaticVariable
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PassByValueDatatypes
import prog8.code.core.SignedDatatypes
import prog8.intermediate.*
internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun translateExpression(expr: PtExpression, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
require(codeGen.registers.peekNext() > resultRegister || resultRegister >= SyscallRegisterBase)
require(codeGen.registers.peekNext() > resultRegister || resultRegister >= SyscallRegisterBase) {
"no more registers for expression ${expr.position}"
}
return when (expr) {
is PtMachineRegister -> {
@ -586,7 +591,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: IRDataType, resultRegister: Int): IRCodeChunks {
require(vmDt!=IRDataType.FLOAT) {"floating-point modulo not supported"}
require(vmDt!=IRDataType.FLOAT) {"floating-point modulo not supported ${binExpr.position}"}
val result = mutableListOf<IRCodeChunkBase>()
val rightResultReg = codeGen.registers.nextFree()
if(binExpr.right is PtNumber) {

View File

@ -75,36 +75,36 @@ class IRCodeGen(
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
irProg.blocks.forEach { block ->
if(block.inlineAssembly.isNotEmpty()) {
val first = block.inlineAssembly.first()
block.children.firstOrNull { it is IRInlineAsmChunk }?.let { first->
first as IRInlineAsmChunk
if(first.label==null) {
val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next)
block.inlineAssembly.removeAt(0)
block.inlineAssembly.add(0, replacement)
block.children.removeAt(0)
block.children.add(0, replacement)
} else if(first.label != block.name) {
throw AssemblyError("first chunk in block has label that differs from block name")
}
}
block.subroutines.forEach { sub ->
block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
if(sub.chunks.isNotEmpty()) {
val first = sub.chunks.first()
if(first.label==null) {
val replacement = when(first) {
is IRCodeChunk -> {
val replacement = IRCodeChunk(sub.name, first.next)
val replacement = IRCodeChunk(sub.label, first.next)
replacement.instructions += first.instructions
replacement
}
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.name, first.assembly, first.isIR, first.next)
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.name, first.data, first.next)
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next)
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.label, first.data, first.next)
else -> throw AssemblyError("invalid chunk")
}
sub.chunks.removeAt(0)
sub.chunks.add(0, replacement)
} else if(first.label != sub.name) {
} else if(first.label != sub.label) {
val next = if(first is IRCodeChunk) first else null
sub.chunks.add(0, IRCodeChunk(sub.name, next))
sub.chunks.add(0, IRCodeChunk(sub.label, next))
}
}
}
@ -116,7 +116,7 @@ class IRCodeGen(
// note: we do still export the memory mapped symbols so a code generator can use those
// for instance when a piece of inlined assembly references them.
val replacements = mutableListOf<Triple<IRCodeChunkBase, Int, UInt>>()
irProg.blocks.asSequence().flatMap { it.subroutines }.flatMap { it.chunks }.forEach { chunk ->
irProg.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.withIndex().forEach {
(idx, instr) ->
val symbolExpr = instr.labelSymbol
@ -180,14 +180,12 @@ class IRCodeGen(
private fun flattenNestedSubroutines() {
// this moves all nested subroutines up to the block scope.
// also changes the name to be the fully scoped one, so it becomes unique at the top level.
// also moves the start() entrypoint as first subroutine.
val flattenedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
val flattenedAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
val removalsSubs = mutableListOf<Pair<PtSub, PtSub>>()
val removalsAsmSubs = mutableListOf<Pair<PtSub, PtAsmSub>>()
val renameSubs = mutableListOf<Pair<PtBlock, PtSub>>()
val renameAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
val entrypoint = program.entrypoint()
fun flattenNestedAsmSub(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
val flattened = PtAsmSub(asmsub.scopedName.joinToString("."),
@ -236,17 +234,8 @@ class IRCodeGen(
renameSubs.forEach { (parent, sub) ->
val renamedSub = PtSub(sub.scopedName.joinToString("."), sub.parameters, sub.returntype, sub.inline, sub.position)
sub.children.forEach { renamedSub.add(it) }
parent.children.remove(sub)
if (sub === entrypoint) {
// entrypoint sub must be first sub
val firstsub = parent.children.withIndex().firstOrNull() { it.value is PtSub || it.value is PtAsmSub }
if(firstsub!=null)
parent.add(firstsub.index, renamedSub)
else
parent.add(renamedSub)
} else {
parent.add(renamedSub)
}
val subindex = parent.children.indexOf(sub)
parent.children[subindex] = renamedSub // keep the order of nodes the same
}
renameAsmSubs.forEach { (parent, sub) ->
val renamedSub = PtAsmSub(sub.scopedName.joinToString("."),
@ -260,8 +249,8 @@ class IRCodeGen(
if(sub.children.isNotEmpty())
renamedSub.add(sub.children.single())
parent.children.remove(sub)
parent.add(renamedSub)
val subindex = parent.children.indexOf(sub)
parent.children[subindex] = renamedSub // keep the order of nodes the same
}
}
@ -291,12 +280,7 @@ class IRCodeGen(
}
is PtConditionalBranch -> translate(node)
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
is PtIncludeBinary -> {
val data = node.file.readBytes()
.drop(node.offset?.toInt() ?: 0)
.take(node.length?.toInt() ?: Int.MAX_VALUE)
listOf(IRInlineBinaryChunk(null, data.map { it.toUByte() }, null))
}
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
is PtAddressOf,
is PtContainmentCheck,
is PtMemoryByte,
@ -327,8 +311,50 @@ class IRCodeGen(
return chunks
}
private fun readBinaryData(node: PtIncludeBinary): Collection<UByte> {
return node.file.readBytes()
.drop(node.offset?.toInt() ?: 0)
.take(node.length?.toInt() ?: Int.MAX_VALUE)
.map { it.toUByte() }
}
private fun translate(branch: PtConditionalBranch): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val goto = branch.trueScope.children.firstOrNull() as? PtJump
if(goto is PtJump && branch.falseScope.children.isEmpty()) {
// special case the form: if_cc <condition> goto <place>
val address = goto.address?.toInt()
if(address!=null) {
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, value = address)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, value = address)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, value = address)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, value = address)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, value = address)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, value = address)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, value = address)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, value = address)
}
addInstr(result, branchIns, null)
} else {
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.targetName.joinToString(".")
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, labelSymbol = label)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, labelSymbol = label)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, labelSymbol = label)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, labelSymbol = label)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = label)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = label)
}
addInstr(result, branchIns, null)
}
return result
}
val elseLabel = createLabelName()
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
val branchIns = when(branch.condition) {
@ -338,8 +364,8 @@ class IRCodeGen(
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
BranchCondition.VC -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
BranchCondition.VS -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
}
addInstr(result, branchIns, null)
result += translateNode(branch.trueScope)
@ -877,6 +903,32 @@ class IRCodeGen(
val signed = ifElse.condition.left.type in arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
val irDt = irType(ifElse.condition.left.type)
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
if(goto!=null && ifElse.elseScope.children.isEmpty()) {
// special case the form: if <condition> goto <place>
val result = mutableListOf<IRCodeChunkBase>()
val leftReg = registers.nextFree()
val rightReg = registers.nextFree()
result += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
result += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
val opcode = when(ifElse.condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> Opcode.BLT
">" -> Opcode.BGT
"<=" -> Opcode.BLE
">=" -> Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if(goto.address!=null)
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, value = goto.address?.toInt()), null)
else if(goto.generatedLabel!=null)
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.identifier!!.targetName.joinToString(".")), null)
return result
}
fun translateNonZeroComparison(): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseBranch = when(ifElse.condition.operator) {
@ -1095,7 +1147,9 @@ class IRCodeGen(
is PtAsmSub -> {
if(child.address!=null) {
// romsub. No codegen needed: calls to this are jumping straight to the address.
require(child.children.isEmpty())
require(child.children.isEmpty()) {
"romsub should be empty at ${child.position}"
}
} else {
// regular asmsub
val assemblyChild = child.children.single() as PtInlineAssembly
@ -1106,8 +1160,8 @@ class IRCodeGen(
child.name,
child.address,
child.clobbers,
child.parameters.map { Pair(it.first.type, it.second) }, // note: the name of the asmsub param is not used anymore.
child.returnTypes.zip(child.retvalRegisters),
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.second, it.first.type) }, // note: the name of the asmsub param is not used anymore.
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) },
asmChunk,
child.position
)
@ -1116,6 +1170,12 @@ class IRCodeGen(
is PtInlineAssembly -> {
irBlock += IRInlineAsmChunk(null, child.assembly, child.isIR, null)
}
is PtIncludeBinary -> {
irBlock += IRInlineBinaryChunk(null, readBinaryData(child), null)
}
is PtLabel -> {
irBlock += IRCodeChunk(child.name, null)
}
else -> TODO("weird child node $child")
}
}

View File

@ -4,7 +4,7 @@ import prog8.intermediate.*
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
fun optimize() {
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
removeEmptyChunks(sub)
joinChunks(sub)
sub.chunks.withIndex().forEach { (index, chunk1) ->

View File

@ -7,15 +7,23 @@ import prog8.intermediate.*
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
fun optimize(): Int {
var numRemoved = removeSimpleUnlinked() + removeUnreachable()
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.label?.let { allLabeledChunks[it] = chunk }
}
}
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
// remove empty subs
irprog.blocks.forEach { block ->
block.subroutines.reversed().forEach { sub ->
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(libraryFilePrefix))
errors.warn("unused subroutine ${sub.name}", sub.position)
block.subroutines.remove(sub)
errors.warn("unused subroutine ${sub.label}", sub.position)
block.children.remove(sub)
numRemoved++
}
}
@ -32,14 +40,20 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
return numRemoved
}
private fun removeUnreachable(): Int {
val reachable = mutableSetOf(irprog.blocks.single { it.name=="main" }.subroutines.single { it.name=="main.start" }.chunks.first())
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
val entrypointSub = irprog.blocks.single { it.name=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
fun grow() {
val new = mutableSetOf<IRCodeChunkBase>()
reachable.forEach {
it.next?.let { next -> new += next }
it.instructions.forEach { instr -> instr.branchTarget?.let { target -> new += target} }
it.instructions.forEach { instr ->
if (instr.branchTarget == null)
instr.labelSymbol?.let { label -> allLabeledChunks[label]?.let { chunk -> new += chunk } }
else
new += instr.branchTarget!!
}
}
reachable += new
}
@ -55,13 +69,19 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
return removeUnlinkedChunks(reachable)
}
private fun removeSimpleUnlinked(): Int {
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach { it.branchTarget?.let { target -> linkedChunks += target } }
chunk.instructions.forEach {
if(it.branchTarget==null) {
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
} else {
linkedChunks += it.branchTarget!!
}
}
if (chunk.label == "main.start")
linkedChunks += chunk
}
@ -74,7 +94,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
linkedChunks: MutableSet<IRCodeChunkBase>
): Int {
var numRemoved = 0
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
if (chunk !in linkedChunks) {
if (chunk === sub.chunks[0]) {

View File

@ -20,7 +20,6 @@ class VmCodeGen(private val program: PtProgram,
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate()
// no need to check options.keepIR, as the VM file format *is* the IR file.
return VmAssemblyProgram(irProgram.name, irProgram)
}
}

View File

@ -36,7 +36,7 @@ class TestIRPeepholeOpt: FunSpec({
return makeIRProgram(listOf(chunk))
}
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
test("remove nops") {
val irProg = makeIRProgram(listOf(

View File

@ -193,11 +193,15 @@ class ExpressionSimplifier(private val program: Program,
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == ">=" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if(expr.operator == ">=" && rightVal?.number == 0.0) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
}
else if(expr.operator == ">" && rightVal?.number == 0.0) {
// unsigned > 0 --> unsigned != 0
return listOf(IAstModification.SetExpression({expr.operator="!="}, expr, parent))
}
}
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
@ -206,11 +210,15 @@ class ExpressionSimplifier(private val program: Program,
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == "<" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if(expr.operator == "<" && rightVal?.number == 0.0) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
}
else if(expr.operator == "<=" && rightVal?.number == 0.0) {
// unsigned <= 0 --> unsigned==0
return listOf(IAstModification.SetExpression({expr.operator="=="}, expr, parent))
}
}
// boolvar & 1 --> boolvar
@ -592,7 +600,7 @@ class ExpressionSimplifier(private val program: Program,
return NumericLiteral(targetDt, 0.0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
DataType.UWORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
@ -609,6 +617,25 @@ class ExpressionSimplifier(private val program: Program,
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
}
}
DataType.WORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
else if(amount==8) {
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(DataType.UBYTE, 0.0, expr.position)), expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
}
else -> {
}
}

View File

@ -174,7 +174,9 @@ class Inliner(val program: Program): AstWalker() {
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
if(sub.inline && sub.parameters.isEmpty()) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1])))
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
}
return if(sub.isAsmSubroutine) {
// simply insert the asm for the argument-less routine
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
@ -208,7 +210,9 @@ class Inliner(val program: Program): AstWalker() {
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
require(sub.statements.size==1 || (sub.statements.size==2 && isEmptyReturn(sub.statements[1])))
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
}
return if(sub.isAsmSubroutine) {
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
noModifications

View File

@ -357,51 +357,17 @@ class StatementOptimizer(private val program: Program,
}
}
// word = msb(word) , word=lsb(word)
// word = lsb(word)
if(assignment.target.inferType(program).isWords) {
var fcall = assignment.value as? FunctionCallExpression
if (fcall == null)
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
if (fcall != null && (fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))) {
if (fcall != null && (fcall.target.nameInSource == listOf("lsb"))) {
if (fcall.args.single() isSameAs assignment.target) {
return if (fcall.target.nameInSource == listOf("lsb")) {
// optimize word=lsb(word) ==> word &= $00ff
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
} else {
// optimize word=msb(word) ==> word >>= 8
val shift8 = BinaryExpression(fcall.args[0], ">>", NumericLiteral(DataType.UBYTE, 8.0, fcall.position), fcall.position)
val newAssign = Assignment(assignment.target, shift8, AssignmentOrigin.OPTIMIZER, fcall.position)
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
}
}
}
}
return noModifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
if(compTarget.name==VMTarget.NAME)
return noModifications
val returnvalue = returnStmt.value
if (returnvalue!=null) {
val dt = returnvalue.inferType(program).getOr(DataType.UNDEFINED)
if(dt!=DataType.UNDEFINED) {
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
// first assign to intermediary variable, then return that
val (returnVarName, _) = program.getTempVar(dt)
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, returnvalue, AssignmentOrigin.OPTIMIZER, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
)
// optimize word=lsb(word) ==> word &= $00ff
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
return listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
}
}
}

View File

@ -40,6 +40,7 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
}

View File

@ -23,5 +23,6 @@
<orderEntry type="module" module-name="codeGenExperimental" />
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" scope="TEST" />
</component>
</module>

View File

@ -12,9 +12,34 @@ c64 {
&ubyte COLOR = $00f1 ; cursor color
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&uword ICRNCH2 = $030c
&uword IQPLOP2 = $030e
&uword IGONE2 = $0310
; $0312 and $0313 are unused.
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
&uword IEXMON = $032e
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in

View File

@ -11,9 +11,36 @@ c64 {
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&ubyte SAREG = $030c ; register storage for A for SYS calls
&ubyte SXREG = $030d ; register storage for X for SYS calls
&ubyte SYREG = $030e ; register storage for Y for SYS calls
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
&uword USRADD = $0311 ; vector for the USR() basic command
; $0313 is unused.
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
&uword USERCMD = $032e
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in

View File

@ -1,6 +1,7 @@
; Cx16 specific disk drive I/O routines.
%import diskio
%import string
cx16diskio {
@ -34,8 +35,8 @@ cx16diskio {
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
}
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address
asmsub vload(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file has to have the usual 2 byte header (which will be skipped)
%asm {{
@ -76,8 +77,8 @@ internal_vload:
}}
}
asmsub vload_raw(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command BVLOAD "filename",device,bank,address
asmsub vload_raw(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file is read fully including the first two bytes.
%asm {{
@ -95,15 +96,6 @@ internal_vload:
return 0
diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read
if diskio.have_first_byte {
diskio.have_first_byte=false
@(bufferpointer) = diskio.first_byte
bufferpointer++
diskio.list_blocks++
num_bytes--
}
void c64.CHKIN(11) ; use #11 as input channel again
; commander X16 supports fast block-read via macptr() kernal call
uword size
@ -133,29 +125,23 @@ byte_read_loop: ; fallback if macptr() isn't supported on the device
lda bufferpointer+1
sta m_in_buffer+2
}}
repeat num_bytes {
while num_bytes {
if c64.READST() {
diskio.f_close()
if c64.READST() & $40 ; eof?
return diskio.list_blocks ; number of bytes read
return 0 ; error.
}
%asm {{
jsr c64.CHRIN
sta cx16.r5
m_in_buffer sta $ffff
inc m_in_buffer+1
bne +
inc m_in_buffer+2
+ inc diskio.list_blocks
bne +
inc diskio.list_blocks+1
+
}}
if cx16.r5==$0d { ; chance on I/o error status?
diskio.first_byte = c64.READST()
if diskio.first_byte & $40 {
diskio.f_close() ; end of file, close it
diskio.list_blocks-- ; don't count that last CHRIN read
}
if diskio.first_byte
return diskio.list_blocks ; number of bytes read
}
diskio.list_blocks++
num_bytes--
}
return diskio.list_blocks ; number of bytes read
}
@ -168,19 +154,78 @@ m_in_buffer sta $ffff
return 0
uword total_read = 0
if diskio.have_first_byte {
diskio.have_first_byte=false
@(bufferpointer) = diskio.first_byte
bufferpointer++
total_read = 1
}
while not c64.READST() {
uword size = cx16diskio.f_read(bufferpointer, 256)
total_read += size
bufferpointer += size
cx16.r0 = cx16diskio.f_read(bufferpointer, 256)
total_read += cx16.r0
bufferpointer += cx16.r0
}
return total_read
}
sub chdir(ubyte drivenumber, str path) {
; -- change current directory.
diskio.list_filename[0] = 'c'
diskio.list_filename[1] = 'd'
diskio.list_filename[2] = ':'
void string.copy(path, &diskio.list_filename+3)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub mkdir(ubyte drivenumber, str name) {
; -- make a new subdirectory.
diskio.list_filename[0] = 'm'
diskio.list_filename[1] = 'd'
diskio.list_filename[2] = ':'
void string.copy(name, &diskio.list_filename+3)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub rmdir(ubyte drivenumber, str name) {
; -- remove a subdirectory.
void string.find(name, '*')
if_cs
return ; refuse to act on a wildcard *
diskio.list_filename[0] = 'r'
diskio.list_filename[1] = 'd'
diskio.list_filename[2] = ':'
void string.copy(name, &diskio.list_filename+3)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub relabel(ubyte drivenumber, str name) {
; -- change the disk label.
diskio.list_filename[0] = 'r'
diskio.list_filename[1] = '-'
diskio.list_filename[2] = 'h'
diskio.list_filename[3] = ':'
void string.copy(name, &diskio.list_filename+4)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub f_seek(uword pos_hiword, uword pos_loword) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
ubyte[6] command = ['p',0,0,0,0,0]
command[1] = 12 ; f_open uses channel 12
command[2] = lsb(pos_loword)
command[3] = msb(pos_loword)
command[4] = lsb(pos_hiword)
command[5] = msb(pos_hiword)
send_command:
c64.SETNAM(sizeof(command), &command)
c64.SETLFS(15, diskio.last_drivenumber, 15)
void c64.OPEN()
c64.CLOSE(15)
}
; TODO see if we can get this to work as well:
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
; cx16diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
; cx16diskio.f_seek.command[2] = lsb(pos_loword)
; cx16diskio.f_seek.command[3] = msb(pos_loword)
; cx16diskio.f_seek.command[4] = lsb(pos_hiword)
; cx16diskio.f_seek.command[5] = msb(pos_hiword)
; goto cx16diskio.f_seek.send_command
; }
}

View File

@ -29,7 +29,7 @@ romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!]
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
@ -68,18 +68,19 @@ romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
; X16 additions
romsub $fe81 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe84 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe87 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe8a = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
romsub $fe8d = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe90 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe93 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe96 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe99 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
romsub $fe9c = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
romsub $fe9f = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
romsub $fea2 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
romsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
romsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {

View File

@ -82,9 +82,10 @@ gfx2 {
bpp = 2
}
else -> {
; back to default text mode and colors
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
c64.CINT() ; back to text mode
; back to default text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
c64.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
width = 0
height = 0
bpp = 0
@ -918,7 +919,7 @@ _done
y++
%asm {{
phx
ldx #1
ldx color
lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1
ldy #8

View File

@ -85,6 +85,18 @@ asmsub RDTIM16() -> uword @AY {
cx16 {
; irq, system and hardware vectors:
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&ubyte SAREG = $030c ; register storage for A for SYS calls
&ubyte SXREG = $030d ; register storage for X for SYS calls
&ubyte SYREG = $030e ; register storage for Y for SYS calls
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
&uword USRADD = $0311 ; vector for the USR() basic command
; $0313 is unused.
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
@ -290,8 +302,9 @@ cx16 {
&ubyte d2ier = via2+14
&ubyte d2ora = via2+15
&ubyte ym2151adr = $9f40
&ubyte ym2151dat = $9f41
; YM-2151 sound chip
&ubyte YM_ADDRESS = $9f40
&ubyte YM_DATA = $9f41
const uword extdev = $9f60
@ -612,12 +625,22 @@ asmsub init_system() {
%asm {{
sei
cld
lda VERA_DC_VIDEO
and #%00000111 ; retain chroma + output mode
sta P8ZP_SCRATCH_REG
lda #$80
sta VERA_CTRL
sta VERA_CTRL ; reset vera
stz $01 ; select rom bank 0 (enable kernal)
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda VERA_DC_VIDEO
and #%11111000
ora P8ZP_SCRATCH_REG
sta VERA_DC_VIDEO ; keep old output mode
ldy #0
clc
jsr c64.PLOT ; force a call to PLOT to avoid autostart black square issue, also see textio.fix_autostart_square()
lda #$90 ; black
jsr c64.CHROUT
lda #1 ; swap fg/bg

View File

@ -21,6 +21,26 @@ sub home() {
txt.chrout(19)
}
asmsub fix_autostart_square() {
; Here's a possible work around for weird issue that prints a black character after first call to c64.PLOT()
; if you're also using c64.CINT() yourself. The default prog8 program initialization (which calls CINT) already performs this workaround.
; The problem occurs when a program is autostarded in the emulator with -run -prg test.prg,
; or when the program is saved as AUTOBOOT.X16 and loaded on boot like that.
%asm {{
phx
sec
jsr c64.PLOT
clc
jsr c64.PLOT
lda #' '
jsr c64.CHROUT ; overwrite the black square
clc
jsr c64.PLOT ; cursor back to original position
plx
rts
}}
}
sub nl() {
txt.chrout('\n')
}

View File

@ -14,9 +14,10 @@ cx16logo {
sub logo() {
uword strptr
for strptr in logo_lines
for strptr in logo_lines {
txt.print(strptr)
txt.nl()
txt.nl()
}
}
str[] logo_lines = [

View File

@ -10,12 +10,12 @@ diskio {
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
c64.SETNAM(1, "$")
c64.SETLFS(13, drivenumber, 0)
c64.SETLFS(12, drivenumber, 0)
ubyte status = 1
void c64.OPEN() ; open 13,8,0,"$"
void c64.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(13) ; use #13 as input channel
void c64.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
@ -53,7 +53,7 @@ diskio {
io_error:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(13)
c64.CLOSE(12)
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
@ -69,12 +69,12 @@ io_error:
; -- Returns pointer to disk name string or 0 if failure.
c64.SETNAM(1, "$")
c64.SETLFS(13, drivenumber, 0)
c64.SETLFS(12, drivenumber, 0)
ubyte okay = false
void c64.OPEN() ; open 13,8,0,"$"
void c64.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(13) ; use #13 as input channel
void c64.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
@ -96,7 +96,7 @@ io_error:
io_error:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(13)
c64.CLOSE(12)
if okay
return &list_filename
return 0
@ -107,33 +107,37 @@ io_error:
uword list_pattern
uword list_blocks
bool iteration_in_progress = false
ubyte @zp first_byte
bool have_first_byte
ubyte last_drivenumber = 8 ; which drive was last used for a f_open operation?
str list_filetype = "???" ; prg, seq, dir
str list_filename = "?" * 50
; ----- get a list of files (uses iteration functions internally) -----
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. Returns number of files.
const uword filenames_buf_size = 800
uword filenames_buffer = memory("filenames", filenames_buf_size, 0)
sub list_filenames(ubyte drivenumber, uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
uword buffer_start = filenames_buffer
ubyte files_found = 0
if lf_start_list(drivenumber, pattern_ptr) {
while lf_next_entry() {
@(name_ptrs) = lsb(filenames_buffer)
name_ptrs++
@(name_ptrs) = msb(filenames_buffer)
name_ptrs++
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
files_found++
if filenames_buffer - buffer_start > filenames_buf_size-18
break
if files_found == max_names
break
if list_filetype!="dir" {
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
files_found++
if filenames_buffer - buffer_start > filenames_buf_size-20 {
@(filenames_buffer)=0
lf_end_list()
sys.set_carry()
return files_found
}
}
}
lf_end_list()
}
@(filenames_buffer)=0
sys.clear_carry()
return files_found
}
@ -170,7 +174,7 @@ io_error:
sub lf_next_entry() -> bool {
; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks and list_filename.
; results will be found in list_blocks, list_filename, and list_filetype.
; if it returns false though, there are no more entries (or an error occurred).
if not iteration_in_progress
@ -207,6 +211,12 @@ io_error:
@(nameptr) = 0
do {
cx16.r15L = c64.CHRIN()
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
list_filetype[0] = cx16.r15L
list_filetype[1] = c64.CHRIN()
list_filetype[2] = c64.CHRIN()
while c64.CHRIN() {
; read the rest of the entry until the end
}
@ -238,7 +248,7 @@ close_end:
}
; ----- iterative file loader functions (uses io channel 11) -----
; ----- iterative file loader functions (uses io channel 12) -----
sub f_open(ubyte drivenumber, uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read
@ -246,17 +256,19 @@ close_end:
f_close()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(11, drivenumber, 0)
void c64.OPEN() ; open 11,8,0,"filename"
c64.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
last_drivenumber = drivenumber
void c64.OPEN() ; open 12,8,12,"filename"
if_cc {
if c64.READST()==0 {
iteration_in_progress = true
have_first_byte = false
void c64.CHKIN(11) ; use #11 as input channel
void c64.CHKIN(12) ; use #12 as input channel
if_cc {
first_byte = c64.CHRIN() ; read first byte to test for file not found
void c64.CHRIN() ; read first byte to test for file not found
if not c64.READST() {
have_first_byte = true
c64.CLOSE(12) ; close file because we already consumed first byte
void c64.OPEN() ; re-open the file
void c64.CHKIN(12)
return true
}
}
@ -275,15 +287,6 @@ close_end:
return 0
list_blocks = 0 ; we reuse this variable for the total number of bytes read
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
list_blocks++
num_bytes--
}
void c64.CHKIN(11) ; use #11 as input channel again
%asm {{
lda bufferpointer
@ -291,29 +294,23 @@ close_end:
lda bufferpointer+1
sta m_in_buffer+2
}}
repeat num_bytes {
while num_bytes {
if c64.READST() {
f_close()
if c64.READST() & $40 ; eof?
return list_blocks ; number of bytes read
return 0 ; error.
}
%asm {{
jsr c64.CHRIN
sta cx16.r5
m_in_buffer sta $ffff
inc m_in_buffer+1
bne +
inc m_in_buffer+2
+ inc list_blocks
bne +
inc list_blocks+1
+
}}
if cx16.r5==$0d { ; chance on I/o error status?
first_byte = c64.READST()
if first_byte & $40 {
f_close() ; end of file, close it
list_blocks-- ; don't count that last CHRIN read
}
if first_byte
return list_blocks ; number of bytes read
}
list_blocks++
num_bytes--
}
return list_blocks ; number of bytes read
}
@ -324,17 +321,10 @@ m_in_buffer sta $ffff
return 0
uword total_read = 0
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
total_read = 1
}
while not c64.READST() {
uword size = f_read(bufferpointer, 256)
total_read += size
bufferpointer += size
cx16.r0 = f_read(bufferpointer, 256)
total_read += cx16.r0
bufferpointer += cx16.r0
}
return total_read
}
@ -348,16 +338,9 @@ m_in_buffer sta $ffff
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #11
jsr c64.CHKIN ; use channel 11 again for input
ldx #12
jsr c64.CHKIN ; use channel 12 again for input
ldy #0
lda have_first_byte
beq _loop
lda #0
sta have_first_byte
lda first_byte
sta (P8ZP_SCRATCH_W1),y
iny
_loop jsr c64.CHRIN
sta (P8ZP_SCRATCH_W1),y
beq _end
@ -378,23 +361,23 @@ _end rts
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
c64.CLRCHN()
c64.CLOSE(11)
c64.CLOSE(12)
iteration_in_progress = false
}
}
; ----- iterative file saver functions (uses io channel 14) -----
; ----- iterative file writing functions (uses io channel 13) -----
sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write
f_close_w()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(14, drivenumber, 1)
void c64.OPEN() ; open 14,8,1,"filename"
c64.SETLFS(13, drivenumber, 1)
void c64.OPEN() ; open 13,8,1,"filename"
if_cc {
void c64.CHKOUT(14) ; use #14 as input channel
c64.CHKOUT(13) ; use #13 as output channel
return not c64.READST()
}
f_close_w()
@ -402,9 +385,9 @@ _end rts
}
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given umber of bytes to the currently open file
; -- write the given number of bytes to the currently open file
if num_bytes!=0 {
void c64.CHKOUT(14) ; use #14 as input channel again
c64.CHKOUT(13) ; use #13 as output channel again
repeat num_bytes {
c64.CHROUT(@(bufferpointer))
bufferpointer++
@ -417,7 +400,7 @@ _end rts
sub f_close_w() {
; -- end an iterative file writing session (close channels).
c64.CLRCHN()
c64.CLOSE(14)
c64.CLOSE(13)
}
@ -436,10 +419,10 @@ _end rts
goto io_error
while not c64.READST() {
first_byte = c64.CHRIN()
if first_byte=='\r' or first_byte=='\n'
cx16.r5L = c64.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
@(messageptr) = first_byte
@(messageptr) = cx16.r5L
messageptr++
}
@(messageptr) = 0
@ -458,7 +441,7 @@ io_error:
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword @shared end_address = address + size
first_byte = 0 ; result var reuse
cx16.r0L = 0
%asm {{
lda address
@ -476,12 +459,12 @@ io_error:
}}
if_cc
first_byte = c64.READST()==0
cx16.r0L = c64.READST()==0
c64.CLRCHN()
c64.CLOSE(1)
return first_byte
return cx16.r0L
}
; Use kernal LOAD routine to load the given program file in memory.

View File

@ -1082,6 +1082,7 @@ containment_wordarray .proc
iny
cmp (P8ZP_SCRATCH_W2),y
beq _found
dey
+ dey
dey
cpy #254

View File

@ -1 +1 @@
8.7
8.8

View File

@ -41,10 +41,9 @@ private fun compileMain(args: Array<String>): Boolean {
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val keepIR by cli.option(ArgType.Boolean, fullName = "keepIR", description = "keep the IR code file (for targets that use it)")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)")
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental)")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
@ -127,7 +126,6 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
keepIR == true,
compilationTarget,
evalStackAddr,
processedSymbols,
@ -192,7 +190,6 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
keepIR == true,
compilationTarget,
evalStackAddr,
processedSymbols,

View File

@ -38,7 +38,6 @@ class CompilerArguments(val filepath: Path,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val experimentalCodegen: Boolean,
val keepIR: Boolean,
val compilationTarget: String,
val evalStackBaseAddress: UInt?,
val symbolDefs: Map<String, String>,
@ -81,7 +80,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen
keepIR = args.keepIR
evalStackBaseAddress = args.evalStackBaseAddress
outputDir = args.outputDir.normalize()
symbolDefs = args.symbolDefs
@ -95,7 +93,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
processAst(program, args.errors, compilationOptions)
if (compilationOptions.optimize) {
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program)
optimizeAst(
@ -108,7 +106,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
postprocessAst(program, args.errors, compilationOptions)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
// printProgram(program)
determineProgramLoadAddress(program, compilationOptions, args.errors)
@ -382,7 +380,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
callGraph.checkRecursiveCalls(errors)
program.verifyFunctionArgTypes(errors)
errors.report()
program.moveMainAndStartToFirst()
program.moveMainBlockAsFirst()
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
}
@ -391,7 +389,7 @@ private fun createAssemblyAndAssemble(program: Program,
errors: IErrorReporter,
compilerOptions: CompilationOptions
): Boolean {
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report()
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
@ -402,7 +400,7 @@ private fun createAssemblyAndAssemble(program: Program,
// to help clean up the code that still depends on them.
// removeAllVardeclsFromAst(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -251,7 +251,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(inlineAssembly: InlineAssembly) {
if(inlineAssembly.hasReturnOrRts(compilerOptions.compTarget))
if(inlineAssembly.hasReturnOrRts())
count++
}
}
@ -509,7 +509,7 @@ internal class AstChecker(private val program: Program,
val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCallExpression)
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
@ -843,8 +843,15 @@ internal class AstChecker(private val program: Program,
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
if(!leftIDt.isKnown || !rightIDt.isKnown) {
// check if maybe one of the operands is a label, this would need a '&'
if (!leftIDt.isKnown && expr.left !is FunctionCallExpression)
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.left.position)
if (!rightIDt.isKnown && expr.right !is FunctionCallExpression)
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.right.position)
return // hopefully this error will be detected elsewhere
}
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
@ -1028,6 +1035,23 @@ internal class AstChecker(private val program: Program,
if(targetStatement!=null) {
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
if(functionCallStatement.void) {
when(targetStatement) {
is BuiltinFunctionPlaceholder -> {
if(!builtinFunctionReturnType(targetStatement.name).isKnown)
errors.warn("redundant void", functionCallStatement.position)
}
is Label -> {
errors.warn("redundant void", functionCallStatement.position)
}
is Subroutine -> {
if(targetStatement.returntypes.isEmpty())
errors.warn("redundant void", functionCallStatement.position)
}
else -> {}
}
}
}
val funcName = functionCallStatement.target.nameInSource

View File

@ -12,6 +12,7 @@ import prog8.ast.statements.VarDeclOrigin
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
@ -26,6 +27,21 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
val boolRemover = BoolRemover(this)
boolRemover.visit(this)
boolRemover.applyModifications()
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
val finder = AsmInstructionNamesFinder(compilerOptions.compTarget)
finder.visit(this)
if(finder.foundAny()) {
val replacer = AsmInstructionNamesReplacer(
finder.blocks,
finder.subroutines,
finder.variables,
finder.labels)
replacer.visit(this)
replacer.applyModifications()
}
}
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
fixer.visit(this)
while (errors.noErrors() && fixer.applyModifications() > 0) {
@ -129,35 +145,19 @@ internal fun Program.variousCleanups(errors: IErrorReporter, options: Compilatio
}
}
internal fun Program.moveMainAndStartToFirst() {
internal fun Program.moveMainBlockAsFirst() {
// The module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
// the "main" block containing the entrypoint is moved to the top in there.
// sortModules()
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint
val mod = start.definingModule
val block = start.definingBlock
moveModuleToFront(mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
val module = this.entrypoint.definingModule
val block = this.entrypoint.definingBlock
moveModuleToFront(module)
module.remove(block)
val afterDirective = module.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
module.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
module.statements.add(afterDirective, block)
}
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
@ -168,9 +168,9 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
return false
}
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
internal fun Subroutine.hasRtsInAsm(): Boolean {
return statements
.asSequence()
.filterIsInstance<InlineAssembly>()
.any { it.hasReturnOrRts(compTarget) }
.any { it.hasReturnOrRts() }
}

View File

@ -10,6 +10,7 @@ import prog8.ast.walk.IAstVisitor
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
@ -28,9 +29,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
override fun visit(block: Block) {
if(block.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name]
if(existing!=null) {
if(block.isInLibrary)
@ -50,9 +48,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
if(existingInSameScope!=null && existingInSameScope!==decl)
nameError(decl.name, decl.position, existingInSameScope)
@ -74,9 +69,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
override fun visit(subroutine: Subroutine) {
if(subroutine.name in compTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", subroutine.position)
} else {
@ -86,7 +79,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
val existing = subroutine.lookup(listOf(subroutine.name))
if (existing != null && existing !== subroutine) {
if(existing.parent!==existing.parent)
if(existing.parent!==subroutine.parent && existing is Subroutine)
nameShadowWarning(subroutine.name, existing.position, subroutine)
else
nameError(subroutine.name, existing.position, subroutine)
@ -107,10 +100,12 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
if(subroutine.name == subroutine.definingBlock.name) {
// subroutines cannot have the same name as their enclosing block,
// because this causes symbol scoping issues in the resulting assembly source
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
if(compTarget.name != VMTarget.NAME) {
if (subroutine.name == subroutine.definingBlock.name) {
// subroutines cannot have the same name as their enclosing block,
// because this causes symbol scoping issues in the resulting assembly source
errors.err("name conflict '${subroutine.name}', also defined at ${subroutine.definingBlock.position} (64tass scope nesting limitation)", subroutine.position)
}
}
}
@ -118,11 +113,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
override fun visit(label: Label) {
if(label.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", label.position)
} else {
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)

View File

@ -37,11 +37,6 @@ internal class BeforeAsmAstChanger(val program: Program,
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
// move all subroutines to the bottom of the block
val subs = block.statements.filterIsInstance<Subroutine>()
block.statements.removeAll(subs)
block.statements.addAll(subs)
// adjust global variables initialization
if(options.dontReinitGlobals) {
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
@ -142,7 +137,7 @@ internal class BeforeAsmAstChanger(val program: Program,
mods += IAstModification.InsertLast(returnStmt, subroutine)
} else {
val last = subroutine.statements.last()
if((last !is InlineAssembly || !last.hasReturnOrRts(options.compTarget)) && last !is Return) {
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
if(lastStatement !is Return) {
val returnStmt = Return(null, subroutine.position)
@ -169,7 +164,7 @@ internal class BeforeAsmAstChanger(val program: Program,
}
if (!subroutine.inline || !options.optimize) {
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) {
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm()) {
// make sure the NOT INLINED asm subroutine actually has a rts at the end
// (non-asm routines get a Return statement as needed, above)
mods += if(options.compTarget.name==VMTarget.NAME)

View File

@ -45,6 +45,21 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator == "not") {
// first check if we're already part of a "boolean" expresion (i.e. comparing against 0)
// if so, simplify THAT whole expression rather than making it more complicated
if(parent is BinaryExpression && parent.right.constValue(program)?.number==0.0) {
if(parent.operator=="==") {
// (NOT X)==0 --> X!=0
val replacement = BinaryExpression(expr.expression, "!=", NumericLiteral.optimalInteger(0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
} else if(parent.operator=="!=") {
// (NOT X)!=0 --> X==0
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
}
}
// not(not(x)) -> x
if((expr.expression as? PrefixExpression)?.operator=="not")
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))

View File

@ -314,9 +314,7 @@ internal class StatementReorderer(val program: Program,
AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position),
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
),
true,
assign.position
), false, assign.position
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
}
@ -328,7 +326,7 @@ internal class StatementReorderer(val program: Program,
assign.value as? IdentifierReference ?: assign.value,
identifier
),
true,
false,
assign.position
)
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))

View File

@ -62,6 +62,8 @@ internal class SymbolTableMaker: IAstVisitor {
when(decl.type) {
VarDeclType.VAR -> {
var initialNumeric = (decl.value as? NumericLiteral)?.number
if(initialNumeric==0.0)
initialNumeric=null // variable will go into BSS and this will be set to 0
val initialStringLit = decl.value as? StringLiteral
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
val initialArrayLit = decl.value as? ArrayLiteral
@ -79,10 +81,8 @@ internal class SymbolTableMaker: IAstVisitor {
false
else if(decl.isArray)
initialArray.isNullOrEmpty()
else {
if(dontReinitGlobals) initialNumeric = initialNumeric ?: 0.0
else
initialNumeric == null
}
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
}
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)

View File

@ -47,6 +47,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val rightCv = expr.right.constValue(program)
if(leftDt.isKnown && rightDt.isKnown) {
if(expr.operator=="<<" && leftDt.isBytes) {
// uword ww = 1 << shift --> make the '1' a word constant
val leftConst = expr.left.constValue(program)
if(leftConst!=null) {
val leftConstAsWord =
if(leftDt.istype(DataType.UBYTE))
NumericLiteral(DataType.UWORD, leftConst.number, leftConst.position)
else
NumericLiteral(DataType.WORD, leftConst.number, leftConst.position)
val modifications = mutableListOf<IAstModification>()
if (parent is Assignment) {
if (parent.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
if(rightDt.isBytes)
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
}
} else if (parent is TypecastExpression && parent.type == DataType.UWORD && parent.parent is Assignment) {
val assign = parent.parent as Assignment
if (assign.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
if(rightDt.isBytes)
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
}
}
if(modifications.isNotEmpty())
return modifications
}
}
if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) {
// see if any of the operands needs conversion to bool
val modifications = mutableListOf<IAstModification>()

View File

@ -8,6 +8,8 @@ import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.statements.ConditionalBranch
import prog8.ast.statements.IfElse
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
@ -219,5 +221,22 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
return noModifications
}
override fun after(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> {
if(branch.truepart.isEmpty() && branch.elsepart.isEmpty()) {
errors.warn("removing empty conditional branch", branch.position)
return listOf(IAstModification.Remove(branch, parent as IStatementContainer))
}
return noModifications
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty()) {
errors.warn("removing empty if-else statement", ifElse.position)
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
}
return noModifications
}
}

View File

@ -52,8 +52,8 @@ class TestAstChecks: FunSpec({
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain ":7:28: assignment value is invalid"
errors.errors[1] shouldContain ":8:28: assignment value is invalid"
errors.errors[0] shouldContain ":7:28: invalid assignment value, maybe forgot '&'"
errors.errors[1] shouldContain ":8:28: invalid assignment value, maybe forgot '&'"
}
test("can't do str or array expression without using address-of") {

View File

@ -33,7 +33,6 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
keepIR = false,
compilationTarget = target.name,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),
@ -99,14 +98,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
"circles",
"cobramk3-gfx",
"colorbars",
"cube3d",
"datetime",
"diskspeed",
"fileseek",
"highresbitmap",
"kefrenbars",
"keyboardhandler",
"mandelbrot",
"mandelbrot-gfx-colors",
"multipalette",
"rasterbars",
"sincos",
"snow",
"tehtriz",
"testgfx2",
),
@ -184,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val (displayName, filepath) = prepareTestFiles(it, false, target)
test(displayName) {
val src = filepath.readText()
compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null
compileText(target, false, src, writeAssembly = true, keepIR=true) shouldNotBe null
compileText(target, false, src, writeAssembly = true) shouldNotBe null
compileText(target, false, src, writeAssembly = true) shouldNotBe null
}
}
})

View File

@ -40,7 +40,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
strLits[0].value shouldBe "main.bar"
strLits[1].value shouldBe "foo.bar"
strLits[0].definingScope.name shouldBe "main"
strLits[1].definingScope.name shouldBe "foo"
strLits[1].definingScope.name shouldBe "foobar"
}
}

View File

@ -50,7 +50,6 @@ class TestCompilerOptionSourcedirs: FunSpec({
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
keepIR = false,
compilationTarget = Cx16Target.NAME,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),

View 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" }
}
})

View File

@ -11,13 +11,16 @@ class TestLaunchEmu: FunSpec({
test("test launch virtualmachine via target") {
val target = VMTarget()
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
tmpfile.writeText("""<PROGRAM NAME=test>
tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test">
<OPTIONS>
</OPTIONS>
<ASMSYMBOLS>
</ASMSYMBOLS>
<BSS>
</BSS>
<VARIABLES>
</VARIABLES>
@ -30,7 +33,7 @@ class TestLaunchEmu: FunSpec({
<INITGLOBALS>
</INITGLOBALS>
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]>
<BLOCK NAME="main" ADDRESS="null" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
</BLOCK>
</PROGRAM>
""")

View File

@ -124,7 +124,7 @@ class TestSubroutines: FunSpec({
asmfunc.isAsmSubroutine shouldBe true
asmfunc.statements.single() shouldBe instanceOf<InlineAssembly>()
(asmfunc.statements.single() as InlineAssembly).assembly.trim() shouldBe "rts"
asmfunc.hasRtsInAsm(C64Target()) shouldBe true
asmfunc.hasRtsInAsm() shouldBe true
func.isAsmSubroutine shouldBe false
withClue("str param should have been changed to uword") {
asmfunc.parameters.single().type shouldBe DataType.UWORD

View File

@ -14,6 +14,7 @@ import prog8.ast.statements.VarDecl
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@ -934,4 +935,34 @@ main {
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("various floating point casts don't crash the compiler") {
val text="""
%import floats
main {
sub score() -> ubyte {
cx16.r15++
return 5
}
sub start() {
float @shared total = 0
ubyte bb = 5
cx16.r0 = 5
total += cx16.r0 as float
total += score() as float
uword ww = 5
total += ww as float
total += bb as float
float result = score() as float
total += result
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), true, text, writeAssembly = true) shouldNotBe null
}
})

View File

@ -188,7 +188,7 @@ class TestC64Zeropage: FunSpec({
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
val loc = result.getOrElse { throw it } .first
val loc = result.getOrElse { throw it } .address
loc shouldBeGreaterThan 3u
loc shouldNotBeIn zp.free
val num = zp.availableBytes() / 2
@ -216,18 +216,18 @@ class TestC64Zeropage: FunSpec({
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp.availableBytes() shouldBe 18
zp.allocate(emptyList(), DataType.WORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x04u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x06u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0au
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9bu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9eu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xa5u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xb0u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xbeu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0eu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x92u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x96u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0xf9u
zp.allocate(emptyList(), DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
zp.availableBytes() shouldBe 0
}

View File

@ -888,9 +888,11 @@ class TestProg8Parser: FunSpec( {
bool bb
ubyte cc
if cc in [' ', '@', 0] {
cx16.r0L++
}
if cc in "email" {
cx16.r0L++
}
bb = 99 in array

View File

@ -3,13 +3,19 @@ package prog8tests.ast
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.instanceOf
import prog8.ast.IFunctionCall
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.Assignment
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.VarDecl
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.compiler.printProgram
import prog8tests.helpers.compileText
class TestVarious: FunSpec({
@ -129,5 +135,31 @@ main {
}"""
compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("bitshift left of const byte converted to word") {
val src="""
main {
sub start() {
ubyte shift = 10
uword value = 1<<shift
value++
value = 1<<shift
value++
}
}"""
val result = compileText(C64Target(), optimize=false, src, writeAssembly=false)!!
printProgram(result.program)
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val assign1expr = (stmts[3] as Assignment).value as BinaryExpression
val assign2expr = (stmts[5] as Assignment).value as BinaryExpression
assign1expr.operator shouldBe "<<"
val leftval1 = assign1expr.left.constValue(result.program)!!
leftval1.type shouldBe DataType.UWORD
leftval1.number shouldBe 1.0
val leftval2 = assign2expr.left.constValue(result.program)!!
leftval2.type shouldBe DataType.UWORD
leftval2.number shouldBe 1.0
}
})

View File

@ -11,6 +11,7 @@ import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8.code.target.c64.C64Zeropage
import prog8.codegen.cpu6502.AsmGen
import prog8.compiler.astprocessing.SymbolTableMaker
@ -173,4 +174,48 @@ main {
val result = compileText(C64Target(), false, text, writeAssembly = true)
result shouldNotBe null
}
"identifiers can have the names of cpu instructions" {
val text="""
%import textio
nop {
sub lda(ubyte sec) -> ubyte {
asl:
ubyte brk = sec
sec++
brk += sec
return brk
}
}
main {
sub ffalse(ubyte arg) -> ubyte {
arg++
return 0
}
sub ftrue(ubyte arg) -> ubyte {
arg++
return 128
}
sub start() {
ubyte col = 10
ubyte row = 20
txt.print_ub(nop.lda(42))
txt.nl()
txt.print_uw(nop.lda.asl)
void ffalse(99)
void ftrue(99)
}
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)
result shouldNotBe null
val result2 = compileText(VMTarget(), false, text, writeAssembly = true)
result2 shouldNotBe null
}
})

View File

@ -63,5 +63,21 @@ class TestVariables: FunSpec({
"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("negation of unsigned via casts") {
val text = """
main {
sub start() {
cx16.r0L = -(cx16.r0L as byte) as ubyte
cx16.r0 = -(cx16.r0 as word) as uword
ubyte ub
uword uw
ub = -(ub as byte) as ubyte
uw = -(uw as word) as uword
}
}
"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
})

View File

@ -1,2 +1,2 @@
bar .text "foo.bar",0
barbar .text "foo.bar",0

View File

@ -1,3 +1,3 @@
foo {
str bar = "foo.bar"
foobar {
str barbar = "foo.bar"
}

View File

@ -4,6 +4,6 @@ main {
str myBar = "main.bar"
sub start() {
txt.print(myBar)
txt.print(foo.bar)
txt.print(foobar.barbar)
}
}

View File

@ -17,7 +17,6 @@ internal fun compileFile(
outputDir: Path = prog8tests.helpers.outputDir,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
keepIR: Boolean = true,
optFloatExpr: Boolean = true
) : CompilationResult? {
val filepath = fileDir.resolve(fileName)
@ -32,7 +31,6 @@ internal fun compileFile(
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
keepIR = keepIR,
platform.name,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),
@ -53,12 +51,11 @@ internal fun compileText(
sourceText: String,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
keepIR: Boolean = true,
optFloatExpr: Boolean = true
) : CompilationResult? {
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
filePath.toFile().writeText(sourceText)
return compileFile(platform, optimize, filePath.parent, filePath.name,
errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr, keepIR=keepIR)
errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
}

View File

@ -10,6 +10,9 @@ import prog8.ast.statements.Assignment
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.intermediate.IRFileReader
import prog8.intermediate.IRSubroutine
import prog8.intermediate.Opcode
import prog8.vm.VmRunner
import prog8tests.helpers.compileText
import kotlin.io.path.readText
@ -50,7 +53,7 @@ main {
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
@ -162,7 +165,7 @@ skipLABEL:
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
@ -205,7 +208,7 @@ main {
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!!
@ -232,7 +235,7 @@ main {
}
}"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!!
@ -242,4 +245,86 @@ main {
}
exc.message shouldContain("does not support non-IR asmsubs")
}
test("addresses from labels/subroutines not yet supported in VM") {
val src = """
main {
sub start() {
mylabel:
ubyte variable
uword @shared pointer1 = &main.start
uword @shared pointer2 = &start
uword @shared pointer3 = &main.start.mylabel
uword @shared pointer4 = &mylabel
uword[] @shared ptrs = [&variable, &start, &main.start, &mylabel, &main.start.mylabel]
}
}
"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
val exc = shouldThrow<Exception> {
VmRunner().runProgram(virtfile.readText())
}
exc.message shouldContain("cannot yet load a label address as a value")
}
test("nesting with overlapping names is ok (doesn't work for 64tass)") {
val src="""
%import textio
%zeropage basicsafe
main {
sub start() {
main()
main.start.start()
main.main()
sub main() {
cx16.r0++
}
sub start() {
cx16.r0++
}
}
sub main() {
cx16.r0++
}
}"""
val target = VMTarget()
compileText(target, false, src, writeAssembly = true) shouldNotBe null
}
test("compile virtual: short code for if-goto") {
val src = """
main {
sub start() {
if_cc
goto ending
if_cs
goto ending
if cx16.r0 goto ending
if cx16.r0==0 goto ending
if cx16.r0!=0 goto ending
if cx16.r0s>0 goto ending
if cx16.r0s<0 goto ending
ending:
}
}"""
val result = compileText(VMTarget(), true, src, writeAssembly = true)!!
result.program.entrypoint.statements.size shouldBe 9
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
val irProgram = IRFileReader().read(virtfile)
val start = irProgram.blocks[0].children[0] as IRSubroutine
val instructions = start.chunks.flatMap { c->c.instructions }
instructions.size shouldBe 18
instructions.last().opcode shouldBe Opcode.RETURN
}
})

View File

@ -7,7 +7,6 @@ import prog8.ast.expressions.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.VMTarget
interface INamedStatement {
@ -268,6 +267,13 @@ class VarDecl(val type: VarDeclType,
return copy
}
fun renamed(newName: String): VarDecl {
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize, newName, value,
isArray, sharedWithAsm, subroutineParameter, position)
copy.allowInitializeWithZero = this.allowInitializeWithZero
return copy
}
fun findInitializer(program: Program): Assignment? =
(parent as IStatementContainer).statements
.asSequence()
@ -630,12 +636,12 @@ class InlineAssembly(val assembly: String, val isIR: Boolean, override val posit
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun hasReturnOrRts(target: ICompilationTarget): Boolean {
return if(target.name!= VMTarget.NAME) {
fun hasReturnOrRts(): Boolean {
return if(isIR) {
" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly || " jumpa" in assembly || "\tjumpa" in assembly
} else {
" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
} else {
" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly || " jumpa" in assembly || "\tjumpa" in assembly
}
}

View File

@ -190,7 +190,7 @@ abstract class AstWalker {
}
}
fun applyModifications(): Int {
open fun applyModifications(): Int {
// check if there are double removes, keep only the last one
val removals = modifications.filter { it.first is IAstModification.Remove }
if(removals.isNotEmpty()) {

View File

@ -140,8 +140,8 @@ One or more .p8 module files
Don't create code to reinitialize the global (block level) variables on every run of the program.
Also means that all such variables are no longer placed in the zeropage.
Sometimes the program will be a lot shorter when using this, but sometimes the opposite happens.
When using this option, it is no longer be possible to run the program correctly more than once!
*Experimental feature*: still has some problems!
When using this option, it may no longer be possible to run the program correctly more than once!
*Experimental feature*: this feature has not been tested much yet.
``-optfloatx``
Also optimize float expressions if optimizations are enabled.
@ -166,9 +166,6 @@ One or more .p8 module files
``-asmlist``
Generate an assembler listing file as well.
``-keepIR``
Keep the IR code in a file (for targets that use it).
``-expericodegen``
Use experimental code generation backend (*incomplete*).

View File

@ -70,7 +70,7 @@ Language features
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- High-level code optimizations, such as const-folding (zero-allocation constants that are optimized away in expressions), expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``abs``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.

View File

@ -364,6 +364,7 @@ diskio module, to deal with loading of potentially large files in to banked ram
Routines to directly load data into video ram are also present (vload and vload_raw).
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
to 16 bits, 64Kb)
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir) and to relabel the disk.
psg (cx16 only)

View File

@ -400,9 +400,9 @@ library function instead)
Special types: const and memory-mapped
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When using ``const``, the value of the 'variable' can no longer be changed.
When using ``const``, the value of the 'variable' cannot be changed; it has become a compile-time constant value instead.
You'll have to specify the initial value expression. This value is then used
by the compiler everywhere you refer to the constant (and no storage is allocated
by the compiler everywhere you refer to the constant (and no memory is allocated
for the constant itself). This is only valid for the simple numeric types (byte, word, float).
When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,

View File

@ -195,17 +195,22 @@ Directives
.. data:: %breakpoint
Level: not at module scope.
Defines a debugging breakpoint at this location. See :ref:`debugging`
Defines a debugging breakpoint at this location. See :ref:`debugging`
.. data:: %asm {{ ... }}
Level: not at module scope.
Declares that a piece of *assembly code* is inside the curly braces.
This code will be copied as-is into the generated output assembly source file.
The assembler syntax used should be for the 3rd party cross assembler tool that Prog8 uses (64tass).
Note that the start and end markers are both *double curly braces* to minimize the chance
that the assembly code itself contains either of those. If it does contain a ``}}``,
it will confuse the parser.
Declares that a piece of *assembly code* is inside the curly braces.
This code will be copied as-is into the generated output assembly source file.
The assembler syntax used should be for the 3rd party cross assembler tool that Prog8 uses (64tass).
Note that the start and end markers are both *double curly braces* to minimize the chance
that the assembly code itself contains either of those. If it does contain a ``}}``,
it will confuse the parser.
If you use the correct scoping rules you can access symbols from the prog8 program from inside
the assembly code. Sometimes you'll have to declare a variable in prog8 with `@shared` if it
is only used in such assembly code. For symbols just consisting of 3 letters, prog8 will
add a special prefix to them, read more about this in :ref:`three-letter-prefixing`.
Identifiers
@ -508,6 +513,7 @@ logical: ``not`` ``and`` ``or`` ``xor``
Unlike most other programming languages, there is no short-circuit or McCarthy evaluation
for the logical ``and`` and ``or`` operators. This means that prog8 currently always evaluates
all operands from these logical expressions, even when one of them already determines the outcome!
If you don't want this to happen, you have to split and nest the if-statements yourself.
range creation: ``to``
Creates a range of values from the LHS value to the RHS value, inclusive.

View File

@ -13,6 +13,21 @@ Especially when you're dealing with interrupts or re-entrant routines: don't mod
that you not own or else you will break stuff.
.. _three-letter-prefixing:
Three-letter symbols prefixing in Assembly
------------------------------------------
Symbols consisting of three letters such as "brk" or "tax" will confuse the assembler that
thinks these are cpu instructions. It will likely fail to assemble the program correctly.
Because of this, prog8 will prefix every 3-letter symbol with "``p8p_``" automatically during compilation.
So "tax" will become "p8p_tax" in the resulting assembly code.
If you're referencing symbols from the prog8 program in hand-written assembly code, you have to take
this into account. Either prefix the 3-letter symbols in the assembly with "``p8p_``" as well, or just
choose a shorter or longer symbol name in the first place.
Software stack for expression evaluation
----------------------------------------

View File

@ -3,8 +3,6 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- ir: register allocation per data type a specific allocation, so we are certain when a reg is used it's just for one specific datatype
- ir: write addresses as hex into p8ir file
...
@ -20,12 +18,16 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- create BSS section in output program and put StStaticVariables in there with bss=true. Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE! So requires self-modifying code
- 6502 codegen: create BSS section in output assembly code and put StStaticVariables in there with bss=true.
Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE if possible.
Note that bss can still contain variables that have @zp tag and those are already dealt with differently
- bss: subroutine parameters don't have to be set to 0.
- ir: mechanism to determine for chunks which registers are getting input values from "outside"
- ir: mechanism to determine for chunks which registers are passing values out? (i.e. are used again in another chunk)
- ir: peephole opt: renumber registers in chunks to start with 1 again every time (but keep entry values in mind!)
- ir peephole opt: reuse registers in chunks (but keep result registers in mind that pass values out!)
- ir: add more optimizations in IRPeepholeOptimizer
- vm: somehow be able to load a label address as value? (VmProgramLoader)
- see if we can let for loops skip the loop if end<start, like other programming languages. Without adding a lot of code size/duplicating the loop condition.
this is documented behavior to now loop around but it's too easy to forget about!
Lot of work because of so many special cases in ForLoopsAsmgen.....
@ -33,23 +35,25 @@ Compiler:
- createAssemblyAndAssemble(): make it possible to actually get rid of the VarDecl nodes by fixing the rest of the code mentioned there.
but probably better to rewrite the 6502 codegen on top of the new Ast.
- generate WASM to eventually run prog8 on a browser canvas?
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``p8v_``? Or not worth it (most 3 letter opcodes as variables are nonsensical anyway)
then we can get rid of the instruction lists in the machinedefinitions as well?
- [problematic due to using 64tass:] add a compiler option to not remove unused subroutines. this allows for building library programs. But this won't work with 64tass's .proc ...
Perhaps replace all uses of .proc/.pend by .block/.bend will fix that with a compiler flag?
But all library code written in asm uses .proc already..... (search/replace when writing the actual asm?)
Once new codegen is written that is based on the IR, this point is moot anyway as that will have its own dead code removal.
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that)
- add special (u)word array type (or modifier?) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing
- ast: don't rewrite by-reference parameter type to uword, but keep the original type (str, array)
BUT that makes the handling of these types different between the scope they are defined in, and the
scope they get passed in by reference... unless we make str and array types by-reference ALWAYS?
BUT that makes simple code accessing them in the declared scope very slow because that then has to always go through
the pointer rather than directly referencing the variable symbol in the generated asm....
Or maybe make codegen smart to check if it's a subroutine parameter or local declared variable?
- Add a mechanism to allocate variables into golden ram (or segments really) (see GoldenRam class)
- block "golden" treated specially: every var in here will be allocated in the Golden ram area
- that block can only contain variables.
- the variables can NOT have initialization values, they will all be set to zero on startup (simple memset)
- just initialize them yourself in start() if you need a non-zero value
- OR.... do all this automatically if 'golden' is enabled as a compiler option? So compiler allocates in ZP first, then Golden Ram, then regular ram
- OR.... make all this more generic and use some %segment option to create real segments for 64tass?
- (need separate step in codegen and IR to write the "golden" variables)
Libraries:
- duplicate diskio for cx16 (get rid of cx16diskio, just copy diskio and tweak everything) + documentation
- get f_seek_w working like in the BASIC program - this needs the changes to diskio.f_open to use suffixes ,p,m
- fix the problems in c128 target, and flesh out its libraries.
- fix the problems in atari target, and flesh out its libraries.
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)

View File

@ -43,7 +43,7 @@ main {
txt.print_uw(count)
txt.print(" primes\n")
float time = c64.RDTIM16() / 60.0
float time = c64.RDTIM16() as float / 60.0
floats.print_f(time)
txt.print(" sec total = ")
floats.print_f(time/ITERS)

112
examples/cx16/diskspeed.p8 Normal file
View 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
View 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)
}
}
}

Binary file not shown.

View 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:
}

Binary file not shown.

View 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:

View 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
View 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 {
}
}
}

View File

@ -1,22 +1,22 @@
%import gfx2
%import textio
%import test_stack
%import math
%option no_sysinit
%zeropage basicsafe
main {
sub start() {
gfx2.screen_mode(5)
; demo1()
; sys.wait(3*60)
demo1()
sys.wait(2*60)
demo2()
gfx2.screen_mode(0)
txt.print("done!\n")
test_stack.test()
}
sub demo1() {
@ -192,7 +192,7 @@ main {
ubyte tp
for tp in 0 to 15 {
gfx2.text(19+tp,20+tp*11, 5, sc:"ScreenCODE text! 1234![]<>#$%&*()")
gfx2.text(19+tp,20+tp*11, 7, sc:"ScreenCODE text! 1234![]<>#$%&*()")
}
}

View File

@ -1,27 +1,52 @@
%import textio
%import floats
%zeropage basicsafe
%option no_sysinit
main {
ubyte @requirezp zpvar = 10
ubyte @zp zpvar2 = 20
uword empty
ubyte[10] bssarray
uword[10] bsswordarray
ubyte[10] nonbssarray = 99
str name="irmen"
sub start() {
float f1
floats.rndseedf(-1.2345)
txt.spc()
floats.print_f(floats.rndf())
txt.spc()
floats.print_f(floats.rndf())
txt.spc()
floats.print_f(floats.rndf())
txt.print("= 10 ")
txt.print_ub(zpvar)
txt.nl()
zpvar++
floats.rndseedf(1.2345)
txt.spc()
floats.print_f(floats.rndf())
txt.spc()
floats.print_f(floats.rndf())
txt.spc()
floats.print_f(floats.rndf())
txt.print("= 20 ")
txt.print_ub(zpvar2)
txt.nl()
zpvar2++
txt.print("= 0 ")
txt.print_uw(empty)
txt.nl()
empty++
txt.print("+ 0 ")
txt.print_ub(bssarray[1])
txt.nl()
bssarray[1]++
txt.print("+ 0 ")
txt.print_uw(bsswordarray[1])
txt.nl()
bsswordarray[1]++
txt.print("+ 99 ")
txt.print_ub(nonbssarray[1])
txt.nl()
nonbssarray[1]++
txt.print("+ r ")
txt.chrout(name[1])
txt.nl()
name[1] = (name[1] as ubyte +1)
txt.print("try running again.\n")
}
}

View File

@ -4,4 +4,4 @@ org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
javaVersion=11
kotlinVersion=1.7.20
kotlinVersion=1.7.21

View File

@ -42,8 +42,7 @@ class RequestParser : Take {
symbolDefs = emptyMap(),
quietAssembler = false,
asmListfile = false,
experimentalCodegen = false,
keepIR = false
experimentalCodegen = false
)
val compilationResult = compileProgram(args)
return RsJson(Jsonding())

View File

@ -3,39 +3,57 @@ package prog8.intermediate
import prog8.code.*
import prog8.code.core.*
import prog8.code.target.*
import java.io.StringReader
import java.nio.file.Path
import javax.xml.stream.XMLEventReader
import javax.xml.stream.XMLInputFactory
import kotlin.io.path.Path
import kotlin.io.path.bufferedReader
import kotlin.io.path.inputStream
class IRFileReader {
fun read(irSourceCode: CharSequence): IRProgram {
return parseProgram(irSourceCode.lineSequence().iterator())
fun read(irSourceCode: String): IRProgram {
StringReader(irSourceCode).use { stream ->
val reader = XMLInputFactory.newInstance().createXMLEventReader(stream)
try {
return parseProgram(reader)
} finally {
reader.close()
}
}
}
fun read(irSourceFile: Path): IRProgram {
println("Reading intermediate representation from $irSourceFile")
irSourceFile.bufferedReader().use { reader ->
return parseProgram(reader.lineSequence().iterator())
irSourceFile.inputStream().use { stream ->
val reader = XMLInputFactory.newInstance().createXMLEventReader(stream)
try {
return parseProgram(reader)
} finally {
reader.close()
}
}
}
private fun parseProgram(lines: Iterator<String>): IRProgram {
val programPattern = Regex("<PROGRAM NAME=(.+)>")
val line = lines.next()
val match = programPattern.matchEntire(line) ?: throw IRParseException("invalid PROGRAM")
val programName = match.groups[1]!!.value
val options = parseOptions(lines)
val asmsymbols = parseAsmSymbols(lines)
val variables = parseVariables(lines, options.dontReinitGlobals)
val memorymapped = parseMemMapped(lines)
val slabs = parseSlabs(lines)
val initGlobals = parseInitGlobals(lines)
val blocks = parseBlocksUntilProgramEnd(lines)
private fun parseProgram(reader: XMLEventReader): IRProgram {
require(reader.nextEvent().isStartDocument)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="PROGRAM") { "missing PROGRAM" }
val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value
val options = parseOptions(reader)
val asmsymbols = parseAsmSymbols(reader)
val bss = parseBss(reader)
val variables = parseVariables(reader, options.dontReinitGlobals)
val memorymapped = parseMemMapped(reader)
val slabs = parseSlabs(reader)
val initGlobals = parseInitGlobals(reader)
val blocks = parseBlocksUntilProgramEnd(reader)
val st = IRSymbolTable(null)
asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)}
bss.forEach { st.add(it) }
variables.forEach { st.add(it) }
memorymapped.forEach { st.add(it) }
slabs.forEach { st.add(it) }
@ -50,26 +68,13 @@ class IRFileReader {
return program
}
private fun parseAsmSymbols(lines: Iterator<String>): Map<String, String> {
val symbols = mutableMapOf<String, String>()
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<ASMSYMBOLS>")
throw IRParseException("invalid ASMSYMBOLS")
while(true) {
line = lines.next()
if(line=="</ASMSYMBOLS>")
return symbols
val (name, value) = line.split('=')
symbols[name] = value
}
}
private fun parseOptions(reader: XMLEventReader): CompilationOptions {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="OPTIONS") { "missing OPTIONS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
private fun parseOptions(lines: Iterator<String>): CompilationOptions {
var line = lines.next()
while(line.isBlank())
line = lines.next()
var target: ICompilationTarget = VMTarget()
var outputType = OutputType.PRG
var launcher = CbmPrgLauncherType.NONE
@ -80,37 +85,35 @@ class IRFileReader {
var optimize = true
var evalStackBaseAddress: UInt? = null
var outputDir = Path("")
if(line!="<OPTIONS>")
throw IRParseException("invalid OPTIONS")
while(true) {
line = lines.next()
if(line=="</OPTIONS>")
break
val (name, value) = line.split('=', limit=2)
when(name) {
"compTarget" -> {
target = when(value) {
VMTarget.NAME -> VMTarget()
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
AtariTarget.NAME -> AtariTarget()
Cx16Target.NAME -> Cx16Target()
else -> throw IRParseException("invalid target $value")
if(text.isNotBlank()) {
text.lineSequence().forEach { line ->
val (name, value) = line.split('=', limit=2)
when(name) {
"compTarget" -> {
target = when(value) {
VMTarget.NAME -> VMTarget()
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
AtariTarget.NAME -> AtariTarget()
Cx16Target.NAME -> Cx16Target()
else -> throw IRParseException("invalid target $value")
}
}
"output" -> outputType = OutputType.valueOf(value)
"launcher" -> launcher = CbmPrgLauncherType.valueOf(value)
"zeropage" -> zeropage = ZeropageType.valueOf(value)
"loadAddress" -> loadAddress = value.toUInt()
"dontReinitGlobals" -> dontReinitGlobals = value.toBoolean()
"evalStackBaseAddress" -> evalStackBaseAddress = if(value=="null") null else parseIRValue(value).toUInt()
"zpReserved" -> {
val (zpstart, zpend) = value.split(',')
zpReserved.add(UIntRange(zpstart.toUInt(), zpend.toUInt()))
}
"outputDir" -> outputDir = Path(value)
"optimize" -> optimize = value.toBoolean()
else -> throw IRParseException("illegal OPTION $name")
}
"output" -> outputType = OutputType.valueOf(value)
"launcher" -> launcher = CbmPrgLauncherType.valueOf(value)
"zeropage" -> zeropage = ZeropageType.valueOf(value)
"loadAddress" -> loadAddress = value.toUInt()
"dontReinitGlobals" -> dontReinitGlobals = value.toBoolean()
"evalStackBaseAddress" -> evalStackBaseAddress = if(value=="null") null else value.toUInt()
"zpReserved" -> {
val (start, end) = value.split(',')
zpReserved.add(UIntRange(start.toUInt(), end.toUInt()))
}
"outputDir" -> outputDir = Path(value)
"optimize" -> optimize = value.toBoolean()
else -> throw IRParseException("illegal OPTION $name")
}
}
@ -130,85 +133,354 @@ class IRFileReader {
)
}
private fun parseVariables(lines: Iterator<String>, dontReinitGlobals: Boolean): List<StStaticVariable> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<VARIABLES>")
throw IRParseException("invalid VARIABLES")
val variables = mutableListOf<StStaticVariable>()
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+)=(.*?) (zp=(.+))?")
while(true) {
line = lines.next()
if(line=="</VARIABLES>")
break
// examples:
// uword main.start.qq2=0 zp=REQUIRE_ZP
// ubyte[6] main.start.namestring=105,114,109,101,110,0
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
val (type, arrayspec, name, value, _, zpwish) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
val bss: Boolean
var initNumeric: Double? = null
var initArray: StArray? = null
when(dt) {
in NumericDatatypes -> {
if(value.isBlank()) {
require(!dontReinitGlobals)
bss = true
} else {
require(dontReinitGlobals)
bss = false
initNumeric = value.toDouble()
}
}
in ArrayDatatypes -> {
if(value.isBlank()) {
bss = true
initArray = emptyList()
} else {
bss = false
initArray = value.split(',').map {
if (it.startsWith('&'))
StArrayElement(null, it.drop(1).split('.'))
else
StArrayElement(it.toDouble(), null)
}
}
}
DataType.STR -> throw IRParseException("STR should have been converted to byte array")
else -> throw IRParseException("weird dt")
}
private fun parseAsmSymbols(reader: XMLEventReader): Map<String, String> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="ASMSYMBOLS") { "missing ASMSYMBOLS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
}
return variables
return if(text.isBlank())
emptyMap()
else
text.lineSequence().associate {
val (name, value) = it.split('=')
name to value
}
}
private fun parseMemMapped(lines: Iterator<String>): List<StMemVar> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<MEMORYMAPPEDVARIABLES>")
throw IRParseException("invalid MEMORYMAPPEDVARIABLES")
val memvars = mutableListOf<StMemVar>()
val mappedPattern = Regex("&(.+?)(\\[.+?\\])? (.+)=(.+)")
while(true) {
line = lines.next()
if(line=="</MEMORYMAPPEDVARIABLES>")
break
// examples:
// &uword main.start.mapped=49152
// &ubyte[20] main.start.mappedarray=49408
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
val (type, arrayspec, name, address) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
memvars.add(StMemVar(name, dt, address.toUInt(), arraysize, Position.DUMMY))
private fun parseBss(reader: XMLEventReader): List<StStaticVariable> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="BSS") { "missing BSS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+) (zp=(.+))?")
val bssVariables = mutableListOf<StStaticVariable>()
text.lineSequence().forEach { line ->
// example: uword main.start.qq2 zp=DONTCARE
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid BSS $line")
val (type, arrayspec, name, _, zpwish) = match.destructured
if('.' !in name)
throw IRParseException("unscoped varname: $name")
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
bssVariables.add(StStaticVariable(name, dt, true, null, null, null, arraysize, zp, Position.DUMMY))
}
return bssVariables
}
return memvars
}
private fun parseVariables(reader: XMLEventReader, dontReinitGlobals: Boolean): List<StStaticVariable> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="VARIABLES") { "missing VARIABLES" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+)=(.*?) (zp=(.+))?")
val variables = mutableListOf<StStaticVariable>()
text.lineSequence().forEach { line ->
// examples:
// uword main.start.qq2=0 zp=REQUIRE_ZP
// ubyte[6] main.start.namestring=105,114,109,101,110,0
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
val (type, arrayspec, name, value, _, zpwish) = match.destructured
if('.' !in name)
throw IRParseException("unscoped varname: $name")
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
val bss: Boolean
var initNumeric: Double? = null
var initArray: StArray? = null
when(dt) {
in NumericDatatypes -> {
if(value.isBlank()) {
require(!dontReinitGlobals)
bss = true
} else {
require(dontReinitGlobals)
bss = false
initNumeric = parseIRValue(value).toDouble()
}
}
in ArrayDatatypes -> {
if(value.isBlank()) {
bss = true
initArray = emptyList()
} else {
bss = false
initArray = value.split(',').map {
if (it.startsWith('@'))
StArrayElement(null, it.drop(1).split('.'))
else
StArrayElement(parseIRValue(it).toDouble(), null)
}
}
}
DataType.STR -> throw IRParseException("STR should have been converted to byte array")
else -> throw IRParseException("weird dt")
}
require(!bss) { "bss var should be in BSS section" }
variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
}
return variables
}
}
private fun parseMemMapped(reader: XMLEventReader): List<StMemVar> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="MEMORYMAPPEDVARIABLES") { "missing MEMORYMAPPEDVARIABLES" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
val memvars = mutableListOf<StMemVar>()
val mappedPattern = Regex("@(.+?)(\\[.+?\\])? (.+)=(.+)")
text.lineSequence().forEach { line ->
// examples:
// @uword main.start.mapped=49152
// @ubyte[20] main.start.mappedarray=49408
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
val (type, arrayspec, name, address) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
memvars.add(StMemVar(name, dt, parseIRValue(address).toUInt(), arraysize, Position.DUMMY))
}
memvars
}
}
private fun parseSlabs(reader: XMLEventReader): List<StMemorySlab> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="MEMORYSLABS") { "missing MEMORYSLABS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
val slabs = mutableListOf<StMemorySlab>()
val slabPattern = Regex("SLAB (.+) (.+) (.+)")
text.lineSequence().forEach { line ->
// example: "SLAB slabname 4096 0"
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid SLAB $line")
val (name, size, align) = match.destructured
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), Position.DUMMY))
}
slabs
}
}
private fun parseInitGlobals(reader: XMLEventReader): IRCodeChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="INITGLOBALS") { "missing INITGLOBALS" }
skipText(reader)
val chunk: IRCodeChunk = if(reader.peek().isStartElement)
parseCodeChunk(reader)
else
IRCodeChunk(null, null)
skipText(reader)
require(reader.nextEvent().isEndElement)
return chunk
}
private fun parseCodeChunk(reader: XMLEventReader): IRCodeChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="CODE") { "missing CODE" }
val label = start.attributes.asSequence().singleOrNull { it.name.localPart == "LABEL" }?.value?.ifBlank { null }
val text = readText(reader).trim()
val chunk = IRCodeChunk(label, null)
if(text.isNotBlank()) {
text.lineSequence().forEach { line ->
if (line.isNotBlank() && !line.startsWith(';')) {
val result = parseIRCodeLine(line, null, mutableMapOf())
result.fold(
ifLeft = {
chunk += it
},
ifRight = {
throw IRParseException("code chunk should not contain a separate label line anymore, this should be the proper label of a new separate chunk")
}
)
}
}
}
require(reader.nextEvent().isEndElement)
return chunk
}
private fun parseBlocksUntilProgramEnd(reader: XMLEventReader): List<IRBlock> {
val blocks = mutableListOf<IRBlock>()
skipText(reader)
while(reader.peek().isStartElement) {
blocks.add(parseBlock(reader))
skipText(reader)
}
return blocks
}
private fun parseBlock(reader: XMLEventReader): IRBlock {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="BLOCK") { "missing BLOCK" }
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
val block = IRBlock(
attrs.getValue("NAME"),
if(attrs.getValue("ADDRESS")=="null") null else parseIRValue(attrs.getValue("ADDRESS")).toUInt(),
IRBlock.BlockAlignment.valueOf(attrs.getValue("ALIGN")),
parsePosition(attrs.getValue("POS")))
skipText(reader)
while(reader.peek().isStartElement) {
when(reader.peek().asStartElement().name.localPart) {
"SUB" -> block += parseSubroutine(reader)
"ASMSUB" -> block += parseAsmSubroutine(reader)
"INLINEASM" -> block += parseInlineAssembly(reader)
"BYTES" -> block += parseBinaryBytes(reader)
"CODE" -> {
val chunk = parseCodeChunk(reader)
if(chunk.isNotEmpty() || chunk.label==null)
throw IRParseException("code chunk in block should only contain a label name")
block += chunk
}
else -> throw IRParseException("invalid line in BLOCK: ${reader.peek()}")
}
skipText(reader)
}
require(reader.nextEvent().isEndElement)
return block
}
private fun parseSubroutine(reader: XMLEventReader): IRSubroutine {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="SUB") { "missing SUB" }
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
val returntype = attrs.getValue("RETURNTYPE")
skipText(reader)
val sub = IRSubroutine(attrs.getValue("NAME"),
parseParameters(reader),
if(returntype=="null") null else parseDatatype(returntype, false),
parsePosition(attrs.getValue("POS")))
skipText(reader)
while(reader.peek().isStartElement) {
when(reader.peek().asStartElement().name.localPart) {
"CODE" -> sub += parseCodeChunk(reader)
"BYTES" -> sub += parseBinaryBytes(reader)
"INLINEASM" -> sub += parseInlineAssembly(reader)
else -> throw IRParseException("invalid line in SUB: ${reader.peek()}")
}
skipText(reader)
}
require(reader.nextEvent().isEndElement)
return sub
}
private fun parseParameters(reader: XMLEventReader): List<IRSubroutine.IRParam> {
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="PARAMS") { "missing PARAMS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
text.lines().map { line ->
val (datatype, name) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
// val orig = variables.single { it.dt==dt && it.name==name}
IRSubroutine.IRParam(name, dt)
}
}
}
private fun parseBinaryBytes(reader: XMLEventReader): IRInlineBinaryChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="BYTES") { "missing BYTES" }
val label = start.attributes.asSequence().singleOrNull { it.name.localPart == "LABEL" }?.value?.ifBlank { null }
val text = readText(reader).replace("\n", "")
require(reader.nextEvent().isEndElement)
val bytes = text.windowed(2, step = 2).map { it.toUByte(16) }
return IRInlineBinaryChunk(label, bytes, null)
}
private fun parseAsmSubroutine(reader: XMLEventReader): IRAsmSubroutine {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="ASMSUB") { "missing ASMSUB" }
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
val params = parseAsmParameters(reader)
val assembly = parseInlineAssembly(reader)
skipText(reader)
require(reader.nextEvent().isEndElement)
val clobbers = attrs.getValue("CLOBBERS")
val clobberRegs = if(clobbers.isBlank()) emptyList() else clobbers.split(',').map { CpuRegister.valueOf(it) }
val returnsSpec = attrs.getValue("RETURNS")
val returns = if(returnsSpec.isNullOrBlank()) emptyList() else returnsSpec.split(',').map { rs ->
val (regstr, dtstr) = rs.split(':')
val dt = parseDatatype(dtstr, false)
val regsf = parseRegisterOrStatusflag(regstr)
IRAsmSubroutine.IRAsmParam(regsf, dt)
}
return IRAsmSubroutine(
attrs.getValue("NAME"),
if(attrs.getValue("ADDRESS")=="null") null else parseIRValue(attrs.getValue("ADDRESS")).toUInt(),
clobberRegs.toSet(),
params,
returns,
assembly,
parsePosition(attrs.getValue("POS"))
)
}
private fun parseAsmParameters(reader: XMLEventReader): List<IRAsmSubroutine.IRAsmParam> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="ASMPARAMS") { "missing ASMPARAMS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
text.lines().map { line ->
val (datatype, regOrSf) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
val regsf = parseRegisterOrStatusflag(regOrSf)
IRAsmSubroutine.IRAsmParam(regsf, dt)
}
}
}
private fun parseInlineAssembly(reader: XMLEventReader): IRInlineAsmChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="INLINEASM") { "missing INLINEASM" }
val label = start.attributes.asSequence().single { it.name.localPart == "LABEL" }.value.ifBlank { null }
val isIr = start.attributes.asSequence().single { it.name.localPart == "IR" }.value.toBoolean()
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return IRInlineAsmChunk(label, text, isIr, null)
}
private fun parseDatatype(type: String, isArray: Boolean): DataType {
@ -236,238 +508,6 @@ class IRFileReader {
}
}
private fun parseSlabs(lines: Iterator<String>): List<StMemorySlab> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<MEMORYSLABS>")
throw IRParseException("invalid MEMORYSLABS")
val slabs = mutableListOf<StMemorySlab>()
val slabPattern = Regex("SLAB (.+) (.+) (.+)")
while(true) {
line = lines.next()
if(line=="</MEMORYSLABS>")
break
// example: "SLAB slabname 4096 0"
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid SLAB $line")
val (name, size, align) = match.destructured
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), Position.DUMMY))
}
return slabs
}
private fun parseInitGlobals(lines: Iterator<String>): IRCodeChunk {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<INITGLOBALS>")
throw IRParseException("invalid INITGLOBALS")
line = lines.next()
var chunk = IRCodeChunk(null, null)
if(line=="<C>") {
chunk = parseCodeChunk(line, lines)!!
line = lines.next()
}
if(line!="</INITGLOBALS>")
throw IRParseException("missing INITGLOBALS close tag")
return chunk
}
private fun parseBlocksUntilProgramEnd(lines: Iterator<String>): List<IRBlock> {
val blocks = mutableListOf<IRBlock>()
while(true) {
var line = lines.next()
while (line.isBlank())
line = lines.next()
if (line == "</PROGRAM>")
break
blocks.add(parseBlock(line, lines))
}
return blocks
}
private val blockPattern = Regex("<BLOCK NAME=(.+) ADDRESS=(.+) ALIGN=(.+) POS=(.+)>")
private val inlineAsmPattern = Regex("<INLINEASM LABEL=(.*) IR=(.+)>")
private val bytesPattern = Regex("<BYTES LABEL=(.*)>")
private val asmsubPattern = Regex("<ASMSUB NAME=(.+) ADDRESS=(.+) CLOBBERS=(.*) RETURNS=(.*) POS=(.+)>")
private val subPattern = Regex("<SUB NAME=(.+) RETURNTYPE=(.+) POS=(.+)>")
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private fun parseBlock(startline: String, lines: Iterator<String>): IRBlock {
var line = startline
if(!line.startsWith("<BLOCK "))
throw IRParseException("invalid BLOCK")
val match = blockPattern.matchEntire(line) ?: throw IRParseException("invalid BLOCK")
val (name, address, align, position) = match.destructured
val addressNum = if(address=="null") null else address.toUInt()
val block = IRBlock(name, addressNum, IRBlock.BlockAlignment.valueOf(align), parsePosition(position))
while(true) {
line = lines.next()
if(line.isBlank())
continue
if(line=="</BLOCK>")
return block
if(line.startsWith("<SUB ")) {
val sub = parseSubroutine(line, lines)
block += sub
} else if(line.startsWith("<ASMSUB ")) {
val sub = parseAsmSubroutine(line, lines)
block += sub
} else if(line.startsWith("<INLINEASM ")) {
val asm = parseInlineAssembly(line, lines)
block += asm
} else
throw IRParseException("invalid line in BLOCK")
}
}
private fun parseInlineAssembly(startline: String, lines: Iterator<String>): IRInlineAsmChunk {
// <INLINEASM LABEL=optional-label IR=true>
val match = inlineAsmPattern.matchEntire(startline) ?: throw IRParseException("invalid INLINEASM")
val label = match.groupValues[1]
val isIr = match.groupValues[2].toBoolean()
val asmlines = mutableListOf<String>()
var line = lines.next()
while(line!="</INLINEASM>") {
asmlines.add(line)
line = lines.next()
}
return IRInlineAsmChunk(label, asmlines.joinToString("\n"), isIr, null)
}
private fun parseAsmSubroutine(startline: String, lines: Iterator<String>): IRAsmSubroutine {
// <ASMSUB NAME=main.testasmsub ADDRESS=null CLOBBERS=A,Y POS=[examples/test.p8: line 14 col 6-11]>
val match = asmsubPattern.matchEntire(startline) ?: throw IRParseException("invalid ASMSUB")
val (scopedname, address, clobbers, returnSpec, pos) = match.destructured
// parse PARAMS
var line = lines.next()
if(line!="<PARAMS>")
throw IRParseException("missing PARAMS")
val params = mutableListOf<Pair<DataType, RegisterOrStatusflag>>()
while(true) {
line = lines.next()
if(line=="</PARAMS>")
break
val (datatype, regOrSf) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
val regsf = parseRegisterOrStatusflag(regOrSf)
params += Pair(dt, regsf)
}
line = lines.next()
val asm = parseInlineAssembly(line, lines)
while(line!="</ASMSUB>")
line = lines.next()
val clobberRegs = if(clobbers.isBlank()) emptyList() else clobbers.split(',').map { CpuRegister.valueOf(it) }
val returns = mutableListOf<Pair<DataType, RegisterOrStatusflag>>()
returnSpec.split(',').forEach{ rs ->
val (regstr, dtstr) = rs.split(':')
val dt = parseDatatype(dtstr, false)
val regsf = parseRegisterOrStatusflag(regstr)
returns.add(Pair(dt, regsf))
}
return IRAsmSubroutine(
scopedname,
if(address=="null") null else address.toUInt(),
clobberRegs.toSet(),
params,
returns,
asm,
parsePosition(pos)
)
}
private fun parseSubroutine(startline: String, lines: Iterator<String>): IRSubroutine {
// <SUB NAME=main.start.nested.nested2 RETURNTYPE=null POS=[examples/test.p8: line 54 col 14-16]>
val match = subPattern.matchEntire(startline) ?: throw IRParseException("invalid SUB")
val (name, returntype, pos) = match.destructured
val sub = IRSubroutine(name,
parseParameters(lines),
if(returntype=="null") null else parseDatatype(returntype, false),
parsePosition(pos))
while(true) {
val line = lines.next()
if(line=="</SUB>")
return sub
val chunk = if(line.startsWith("<C"))
parseCodeChunk(line, lines)
else if(line.startsWith("<BYTES "))
parseBinaryBytes(line, lines)
else if(line.startsWith("<INLINEASM "))
parseInlineAssembly(line, lines)
else
throw IRParseException("invalid sub child node")
if (chunk == null)
break
else
sub += chunk
}
val line = lines.next()
if(line=="</SUB>")
throw IRParseException("missing SUB close tag")
return sub
}
private fun parseBinaryBytes(startline: String, lines: Iterator<String>): IRInlineBinaryChunk {
val match = bytesPattern.matchEntire(startline) ?: throw IRParseException("invalid BYTES")
val label = match.groupValues[1]
val bytes = mutableListOf<UByte>()
var line = lines.next()
while(line!="</BYTES>") {
line.trimEnd().windowed(size=2, step=2) {
bytes.add(it.toString().toUByte(16))
}
line = lines.next()
}
return IRInlineBinaryChunk(label, bytes, null)
}
private fun parseParameters(lines: Iterator<String>): List<IRSubroutine.IRParam> {
var line = lines.next()
if(line!="<PARAMS>")
throw IRParseException("missing PARAMS")
val params = mutableListOf<IRSubroutine.IRParam>()
while(true) {
line = lines.next()
if(line=="</PARAMS>")
return params
val (datatype, name) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
// val orig = variables.single { it.dt==dt && it.name==name}
params.add(IRSubroutine.IRParam(name, dt))
}
}
private fun parseCodeChunk(firstline: String, lines: Iterator<String>): IRCodeChunk? {
if(!firstline.startsWith("<C")) {
if(firstline=="</SUB>")
return null
else
throw IRParseException("invalid or empty <C>ODE chunk")
}
val label = if(firstline.startsWith("<C LABEL="))
firstline.split('=', limit = 2)[1].dropLast(1)
else
null
val chunk = IRCodeChunk(label, null)
while(true) {
val line = lines.next()
if (line == "</C>")
return chunk
if (line.isBlank() || line.startsWith(';'))
continue
val result = parseIRCodeLine(line, null, mutableMapOf())
result.fold(
ifLeft = {
chunk += it
},
ifRight = {
throw IRParseException("code chunk should not contain a separate label line anymore, this should be the proper label of a new separate chunk")
}
)
}
}
private fun parseRegisterOrStatusflag(regs: String): RegisterOrStatusflag {
var reg: RegisterOrPair? = null
var sf: Statusflag? = null
@ -479,6 +519,8 @@ class IRFileReader {
return RegisterOrStatusflag(reg, sf)
}
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private fun parsePosition(strpos: String): Position {
// example: "[library:/prog8lib/virtual/textio.p8: line 5 col 2-4]"
val match = posPattern.matchEntire(strpos) ?: throw IRParseException("invalid Position")
@ -486,4 +528,16 @@ class IRFileReader {
return Position(file, line.toInt(), startCol.toInt(), endCol.toInt())
}
private fun readText(reader: XMLEventReader): String {
val sb = StringBuilder()
while(reader.peek().isCharacters) {
sb.append(reader.nextEvent().asCharacters().data)
}
return sb.toString()
}
private fun skipText(reader: XMLEventReader) {
while(reader.peek().isCharacters)
reader.nextEvent()
}
}

View File

@ -1,9 +1,6 @@
package prog8.intermediate
import prog8.code.core.ArrayDatatypes
import prog8.code.core.DataType
import prog8.code.core.InternalCompilerException
import prog8.code.core.NumericDatatypes
import prog8.code.core.*
import java.nio.file.Path
import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
@ -11,17 +8,18 @@ import kotlin.io.path.div
class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private val outfile = outfileOverride ?: (irProgram.options.outputDir / ("${irProgram.name}.p8ir"))
private val out = outfile.bufferedWriter()
private val out = outfile.bufferedWriter(charset=Charsets.UTF_8)
private var numChunks = 0
private var numInstr = 0
fun write(): Path {
println("Writing intermediate representation to $outfile")
out.write("<PROGRAM NAME=${irProgram.name}>\n")
out.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
out.write("<PROGRAM NAME=\"${irProgram.name}\">\n")
writeOptions()
writeAsmSymbols()
writeVariableAllocations()
writeVariables()
out.write("\n<INITGLOBALS>\n")
if(!irProgram.options.dontReinitGlobals) {
@ -47,42 +45,46 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private fun writeBlocks() {
irProgram.blocks.forEach { block ->
out.write("\n<BLOCK NAME=${block.name} ADDRESS=${block.address} ALIGN=${block.alignment} POS=${block.position}>\n")
block.inlineAssembly.forEach {
writeInlineAsm(it)
}
block.subroutines.forEach {
out.write("<SUB NAME=${it.name} RETURNTYPE=${it.returnType.toString().lowercase()} POS=${it.position}>\n")
out.write("<PARAMS>\n")
it.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") }
out.write("</PARAMS>\n")
it.chunks.forEach { chunk ->
numChunks++
when (chunk) {
is IRInlineAsmChunk -> writeInlineAsm(chunk)
is IRInlineBinaryChunk -> writeInlineBytes(chunk)
is IRCodeChunk -> writeCodeChunk(chunk)
else -> throw InternalCompilerException("invalid chunk")
out.write("\n<BLOCK NAME=\"${block.name}\" ADDRESS=\"${block.address?.toHex()}\" ALIGN=\"${block.alignment}\" POS=\"${block.position}\">\n")
block.children.forEach { child ->
when(child) {
is IRAsmSubroutine -> {
val clobbers = child.clobbers.joinToString(",")
val returns = child.returns.map { ret ->
if(ret.reg.registerOrPair!=null) "${ret.reg.registerOrPair}:${ret.dt.toString().lowercase()}"
else "${ret.reg.statusflag}:${ret.dt.toString().lowercase()}"
}.joinToString(",")
out.write("<ASMSUB NAME=\"${child.label}\" ADDRESS=\"${child.address?.toHex()}\" CLOBBERS=\"$clobbers\" RETURNS=\"$returns\" POS=\"${child.position}\">\n")
out.write("<ASMPARAMS>\n")
child.parameters.forEach { ret ->
val reg = if(ret.reg.registerOrPair!=null) ret.reg.registerOrPair.toString()
else ret.reg.statusflag.toString()
out.write("${ret.dt.toString().lowercase()} $reg\n")
}
out.write("</ASMPARAMS>\n")
writeInlineAsm(child.asmChunk)
out.write("</ASMSUB>\n")
}
is IRCodeChunk -> writeCodeChunk(child)
is IRInlineAsmChunk -> writeInlineAsm(child)
is IRInlineBinaryChunk -> writeInlineBytes(child)
is IRSubroutine -> {
out.write("<SUB NAME=\"${child.label}\" RETURNTYPE=\"${child.returnType.toString().lowercase()}\" POS=\"${child.position}\">\n")
out.write("<PARAMS>\n")
child.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") }
out.write("</PARAMS>\n")
child.chunks.forEach { chunk ->
numChunks++
when (chunk) {
is IRInlineAsmChunk -> writeInlineAsm(chunk)
is IRInlineBinaryChunk -> writeInlineBytes(chunk)
is IRCodeChunk -> writeCodeChunk(chunk)
else -> throw InternalCompilerException("invalid chunk")
}
}
out.write("</SUB>\n")
}
}
out.write("</SUB>\n")
}
block.asmSubroutines.forEach {
val clobbers = it.clobbers.joinToString(",")
val returns = it.returns.map { (dt, reg) ->
if(reg.registerOrPair!=null) "${reg.registerOrPair}:${dt.toString().lowercase()}"
else "${reg.statusflag}:${dt.toString().lowercase()}"
}.joinToString(",")
out.write("<ASMSUB NAME=${it.name} ADDRESS=${it.address} CLOBBERS=$clobbers RETURNS=$returns POS=${it.position}>\n")
out.write("<PARAMS>\n")
it.parameters.forEach { (dt, regOrSf) ->
val reg = if(regOrSf.registerOrPair!=null) regOrSf.registerOrPair.toString()
else regOrSf.statusflag.toString()
out.write("${dt.toString().lowercase()} $reg\n")
}
out.write("</PARAMS>\n")
writeInlineAsm(it.asmChunk)
out.write("</ASMSUB>\n")
}
out.write("</BLOCK>\n")
}
@ -90,19 +92,19 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private fun writeCodeChunk(chunk: IRCodeChunk) {
if(chunk.label!=null)
out.write("<C LABEL=${chunk.label}>\n")
out.write("<CODE LABEL=\"${chunk.label}\">\n")
else
out.write("<C>\n")
out.write("<CODE>\n")
chunk.instructions.forEach { instr ->
numInstr++
out.write(instr.toString())
out.write("\n")
}
out.write("</C>\n")
out.write("</CODE>\n")
}
private fun writeInlineBytes(chunk: IRInlineBinaryChunk) {
out.write("<BYTES LABEL=${chunk.label ?: ""}>\n")
out.write("<BYTES LABEL=\"${chunk.label ?: ""}\">\n")
chunk.data.withIndex().forEach {(index, byte) ->
out.write(byte.toString(16).padStart(2,'0'))
if(index and 63 == 63 && index < chunk.data.size-1)
@ -112,7 +114,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
}
private fun writeInlineAsm(chunk: IRInlineAsmChunk) {
out.write("<INLINEASM LABEL=${chunk.label ?: ""} IR=${chunk.isIR}>\n")
out.write("<INLINEASM LABEL=\"${chunk.label ?: ""}\" IR=\"${chunk.isIR}\">\n")
out.write(chunk.assembly)
out.write("\n</INLINEASM>\n")
}
@ -126,23 +128,30 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
for(range in irProgram.options.zpReserved) {
out.write("zpReserved=${range.first},${range.last}\n")
}
out.write("loadAddress=${irProgram.options.loadAddress}\n")
out.write("loadAddress=${irProgram.options.loadAddress.toHex()}\n")
out.write("optimize=${irProgram.options.optimize}\n")
out.write("dontReinitGlobals=${irProgram.options.dontReinitGlobals}\n")
out.write("evalStackBaseAddress=${irProgram.options.evalStackBaseAddress}\n")
out.write("evalStackBaseAddress=${irProgram.options.evalStackBaseAddress?.toHex()}\n")
out.write("outputDir=${irProgram.options.outputDir.toAbsolutePath()}\n")
// other options not yet useful here?
out.write("</OPTIONS>\n")
}
private fun writeVariableAllocations() {
private fun writeVariables() {
out.write("\n<VARIABLES>\n")
for (variable in irProgram.st.allVariables()) {
out.write("\n<BSS>\n")
for (variable in irProgram.st.allVariables().filter { it.bss }) {
val typeStr = getTypeString(variable)
// bss variables have no initialization value
out.write("$typeStr ${variable.name} zp=${variable.zpwish}\n")
}
out.write("</BSS>\n<VARIABLES>\n")
for (variable in irProgram.st.allVariables().filter { !it.bss }) {
val typeStr = getTypeString(variable)
val value: String = when(variable.dt) {
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString()
in NumericDatatypes -> (variable.onetimeInitializationNumericValue?.toInt() ?: "").toString()
in NumericDatatypes -> (variable.onetimeInitializationNumericValue?.toInt()?.toHex() ?: "").toString()
DataType.STR -> {
val encoded = irProgram.encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
encoded.joinToString(",") { it.toInt().toString() }
@ -158,9 +167,9 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
if(variable.onetimeInitializationArrayValue!==null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") {
if(it.number!=null)
it.number!!.toInt().toString()
it.number!!.toInt().toHex()
else
"&${it.addressOf!!.joinToString(".")}"
"@${it.addressOf!!.joinToString(".")}"
}
} else {
"" // array will be zero'd out at program start
@ -175,7 +184,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
out.write("\n<MEMORYMAPPEDVARIABLES>\n")
for (variable in irProgram.st.allMemMappedVariables()) {
val typeStr = getTypeString(variable)
out.write("&$typeStr ${variable.name}=${variable.address}\n")
out.write("@$typeStr ${variable.name}=${variable.address.toHex()}\n")
}
out.write("</MEMORYMAPPEDVARIABLES>\n")

View File

@ -1,5 +1,7 @@
package prog8.intermediate
import prog8.code.core.toHex
/*
Intermediate Representation instructions for the IR Virtual machine.
@ -519,8 +521,8 @@ data class InstructionFormat(val datatype: IRDataType?,
/*
<X = X is not modified (input/readonly value)
>X = X is overwritten with output value (output value)
<>X = X is modified (input + output)
TODO: also encode if memory is read/written/modified?
<>X = X is modified (read as input + written as output)
TODO: also encode if *memory* is read/written/modified?
*/
@Suppress("BooleanLiteralArgument")
val instructionFormats = mutableMapOf(
@ -807,7 +809,7 @@ data class IRInstruction(
result.add(",")
}
value?.let {
result.add(it.toString())
result.add(it.toHex())
result.add(",")
}
fpValue?.let {

View File

@ -70,29 +70,45 @@ class IRProgram(val name: String,
fun linkChunks() {
fun getLabeledChunks(): Map<String?, IRCodeChunkBase> {
return blocks.flatMap { it.subroutines }.flatMap { it.chunks }.associateBy { it.label } +
blocks.flatMap { it.asmSubroutines }.map { it.asmChunk }.associateBy { it.label }
val result = mutableMapOf<String?, IRCodeChunkBase>()
blocks.forEach { block ->
block.children.forEach { child ->
when(child) {
is IRAsmSubroutine -> result[child.asmChunk.label] = child.asmChunk
is IRCodeChunk -> result[child.label] = child
is IRInlineAsmChunk -> result[child.label] = child
is IRInlineBinaryChunk -> result[child.label] = child
is IRSubroutine -> result.putAll(child.chunks.associateBy { it.label })
}
}
}
return result
}
val labeledChunks = getLabeledChunks()
if(globalInits.isNotEmpty()) {
if(globalInits.next==null) {
// link globalinits to subsequent chunk
val firstBlock = blocks.firstOrNull()
if(firstBlock!=null) {
if(firstBlock.inlineAssembly.isNotEmpty()) {
globalInits.next = firstBlock.inlineAssembly.first()
} else if(firstBlock.subroutines.isNotEmpty()) {
val firstSub = firstBlock.subroutines.first()
if(firstSub.chunks.isNotEmpty())
globalInits.next = firstSub.chunks.first()
if(firstBlock!=null && firstBlock.isNotEmpty()) {
firstBlock.children.forEach { child ->
when(child) {
is IRAsmSubroutine -> throw AssemblyError("cannot link next to asmsub $child")
is IRCodeChunk -> globalInits.next = child
is IRInlineAsmChunk -> globalInits.next = child
is IRInlineBinaryChunk -> globalInits.next = child
is IRSubroutine -> {
if(child.chunks.isNotEmpty())
globalInits.next = child.chunks.first()
}
}
}
}
}
}
blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
fun linkSubroutineChunks(sub: IRSubroutine) {
sub.chunks.withIndex().forEach { (index, chunk) ->
fun nextChunk(): IRCodeChunkBase? = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null
@ -109,8 +125,10 @@ class IRProgram(val name: String,
chunk.next = next
else if(next is IRInlineAsmChunk)
chunk.next = next
else if(next is IRInlineBinaryChunk)
chunk.next =next
else
throw AssemblyError("code chunk flows into following non-executable chunk")
throw AssemblyError("code chunk followed by invalid chunk type $next")
}
}
@ -134,36 +152,46 @@ class IRProgram(val name: String,
}
}
}
blocks.forEach { block ->
block.children.forEachIndexed { index, child ->
val next = if(index<block.children.size-1) block.children[index+1] as? IRCodeChunkBase else null
when (child) {
is IRAsmSubroutine -> child.asmChunk.next = next
is IRCodeChunk -> child.next = next
is IRInlineAsmChunk -> child.next = next
is IRInlineBinaryChunk -> child.next = next
is IRSubroutine -> linkSubroutineChunks(child)
}
}
}
}
fun validate() {
blocks.forEach { block ->
if(block.inlineAssembly.isNotEmpty()) {
require(block.inlineAssembly.first().label == block.name) { "first block chunk should have block name as its label" }
}
block.inlineAssembly.forEach { chunk ->
require(chunk.instructions.isEmpty())
}
block.subroutines.forEach { sub ->
if(sub.chunks.isNotEmpty()) {
require(sub.chunks.first().label == sub.name) { "first chunk in subroutine should have sub name as its label" }
}
sub.chunks.forEach { chunk ->
if (chunk is IRCodeChunk) {
require(chunk.instructions.isNotEmpty() || chunk.label != null)
if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump)
require(chunk.next == null) { "chunk ending with a jump shouldn't be linked to next" }
else {
// if chunk is NOT the last in the block, it needs to link to next.
val isLast = sub.chunks.last() === chunk
require(isLast || chunk.next != null) { "chunk needs to be linked to next" }
}
if(block.isNotEmpty()) {
block.children.filterIsInstance<IRInlineAsmChunk>().forEach { chunk -> require(chunk.instructions.isEmpty()) }
block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
if(sub.chunks.isNotEmpty()) {
require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" }
}
else
require(chunk.instructions.isEmpty())
chunk.instructions.forEach {
if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch)
require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" }
sub.chunks.forEach { chunk ->
if (chunk is IRCodeChunk) {
require(chunk.instructions.isNotEmpty() || chunk.label != null)
if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump)
require(chunk.next == null) { "chunk ending with a jump shouldn't be linked to next" }
else {
// if chunk is NOT the last in the block, it needs to link to next.
val isLast = sub.chunks.last() === chunk
require(isLast || chunk.next != null) { "chunk needs to be linked to next" }
}
}
else
require(chunk.instructions.isEmpty())
chunk.instructions.forEach {
if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch)
require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" }
}
}
}
}
@ -184,10 +212,16 @@ class IRProgram(val name: String,
}
globalInits.instructions.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) }
blocks.forEach {
it.inlineAssembly.forEach { chunk -> addUsed(chunk.usedRegisters()) }
it.subroutines.flatMap { sub->sub.chunks }.forEach { chunk -> addUsed(chunk.usedRegisters()) }
it.asmSubroutines.forEach { asmsub -> addUsed(asmsub.usedRegisters()) }
blocks.forEach {block ->
block.children.forEach { child ->
when(child) {
is IRAsmSubroutine -> addUsed(child.usedRegisters())
is IRCodeChunk -> addUsed(child.usedRegisters())
is IRInlineAsmChunk -> addUsed(child.usedRegisters())
is IRInlineBinaryChunk -> addUsed(child.usedRegisters())
is IRSubroutine -> child.chunks.forEach { chunk -> addUsed(chunk.usedRegisters()) }
}
}
}
return RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs)
@ -200,9 +234,8 @@ class IRBlock(
val alignment: BlockAlignment,
val position: Position
) {
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
val subroutines = mutableListOf<IRSubroutine>()
val asmSubroutines = mutableListOf<IRAsmSubroutine>()
// TODO not separate lists but just a single list of chunks, like IRSubroutine? (but these are not all chunks...)
val children = mutableListOf<IIRBlockElement>()
enum class BlockAlignment {
NONE,
@ -210,32 +243,41 @@ class IRBlock(
PAGE
}
operator fun plusAssign(sub: IRSubroutine) {
subroutines += sub
operator fun plusAssign(sub: IRSubroutine) { children += sub }
operator fun plusAssign(sub: IRAsmSubroutine) { children += sub }
operator fun plusAssign(asm: IRInlineAsmChunk) { children += asm }
operator fun plusAssign(binary: IRInlineBinaryChunk) { children += binary }
operator fun plusAssign(irCodeChunk: IRCodeChunk) {
// this is for a separate label in the block scope. (random code statements are not allowed)
require(irCodeChunk.isEmpty() && irCodeChunk.label!=null)
children += irCodeChunk
}
operator fun plusAssign(sub: IRAsmSubroutine) { asmSubroutines += sub }
operator fun plusAssign(asm: IRInlineAsmChunk) { inlineAssembly += asm }
fun isEmpty(): Boolean {
val noAsm = inlineAssembly.isEmpty() || inlineAssembly.all { it.isEmpty() }
val noSubs = subroutines.isEmpty() || subroutines.all { it.isEmpty() }
val noAsmSubs = asmSubroutines.isEmpty() || asmSubroutines.all { it.isEmpty() }
return noAsm && noSubs && noAsmSubs
}
fun isEmpty(): Boolean = children.isEmpty() || children.all { it.isEmpty() }
fun isNotEmpty(): Boolean = !isEmpty()
}
class IRSubroutine(val name: String,
val parameters: List<IRParam>,
val returnType: DataType?,
val position: Position) {
sealed interface IIRBlockElement {
val label: String?
fun isEmpty(): Boolean
fun isNotEmpty(): Boolean
}
class IRSubroutine(
override val label: String,
val parameters: List<IRParam>,
val returnType: DataType?,
val position: Position): IIRBlockElement {
class IRParam(val name: String, val dt: DataType)
val chunks = mutableListOf<IRCodeChunkBase>()
init {
require('.' in name) {"subroutine name is not scoped: $name"}
require(!name.startsWith("main.main.")) {"subroutine name invalid main prefix: $name"}
require('.' in label) {"subroutine name is not scoped: $label"}
require(!label.startsWith("main.main.")) {"subroutine name invalid main prefix: $label"}
// params and return value should not be str
require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"}
@ -249,34 +291,41 @@ class IRSubroutine(val name: String,
chunks+= chunk
}
fun isEmpty(): Boolean = chunks.isEmpty() || chunks.all { it.isEmpty() }
override fun isEmpty(): Boolean = chunks.isEmpty() || chunks.all { it.isEmpty() }
override fun isNotEmpty(): Boolean = !isEmpty()
}
class IRAsmSubroutine(
val name: String,
override val label: String,
val address: UInt?,
val clobbers: Set<CpuRegister>,
val parameters: List<Pair<DataType, RegisterOrStatusflag>>,
val returns: List<Pair<DataType, RegisterOrStatusflag>>,
val parameters: List<IRAsmParam>,
val returns: List<IRAsmParam>,
val asmChunk: IRInlineAsmChunk,
val position: Position
) {
): IIRBlockElement {
class IRAsmParam(val reg: RegisterOrStatusflag, val dt: DataType)
init {
require('.' in name) { "subroutine name is not scoped: $name" }
require(!name.startsWith("main.main.")) { "subroutine name invalid main prefix: $name" }
require('.' in label) { "subroutine name is not scoped: $label" }
require(!label.startsWith("main.main.")) { "subroutine name invalid main prefix: $label" }
}
private val registersUsed by lazy { registersUsedInAssembly(asmChunk.isIR, asmChunk.assembly) }
fun usedRegisters() = registersUsed
fun isEmpty(): Boolean = if(address==null) asmChunk.isEmpty() else false
override fun isEmpty(): Boolean = if(address==null) asmChunk.isEmpty() else false
override fun isNotEmpty(): Boolean = !isEmpty()
}
sealed class IRCodeChunkBase(val label: String?, var next: IRCodeChunkBase?) {
sealed class IRCodeChunkBase(override val label: String?, var next: IRCodeChunkBase?): IIRBlockElement {
val instructions = mutableListOf<IRInstruction>()
abstract fun isEmpty(): Boolean
abstract fun isNotEmpty(): Boolean
abstract override fun isEmpty(): Boolean
abstract override fun isNotEmpty(): Boolean
abstract fun usedRegisters(): RegistersUsed
}

Some files were not shown because too many files have changed in this diff Show More