mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aa0ec5abd | ||
|
|
b6eef3612f | ||
|
|
666d62dd7a | ||
|
|
44ee4b989f | ||
|
|
18790d867c | ||
|
|
d6b8936376 | ||
|
|
4d840c7db8 | ||
|
|
4d2b21816d | ||
|
|
2d34fdd28f | ||
|
|
68abda1219 | ||
|
|
f778f08f76 | ||
|
|
ac1bd2fb7b | ||
|
|
4b7b1379d9 | ||
|
|
e560e2ab3f | ||
|
|
1e441c2ddf | ||
|
|
93ce74eeb1 | ||
|
|
f718f4251b | ||
|
|
4644c9b621 | ||
|
|
197081f10d | ||
|
|
00b717cde8 | ||
|
|
34aa917ca4 | ||
|
|
a38ddcb364 | ||
|
|
5b9576df4e | ||
|
|
310219e5d7 | ||
|
|
a0deb463c9 | ||
|
|
90ddec2ad8 | ||
|
|
f6b03d5a78 | ||
|
|
f531daa872 | ||
|
|
046dceb5c2 | ||
|
|
dcc1f00048 | ||
|
|
05f935b598 | ||
|
|
f2d27403c5 | ||
|
|
473efbe67a | ||
|
|
aeabf0f324 | ||
|
|
80ab552ad8 | ||
|
|
7d4695c5b2 | ||
|
|
5189eaca36 | ||
|
|
cfb31377fc | ||
|
|
a07c52e112 | ||
|
|
8e1071aa89 | ||
|
|
7cb9a6ba60 | ||
|
|
350dc731f1 | ||
|
|
f690f58bd4 | ||
|
|
4bc65e9ef7 | ||
|
|
2d600da8b6 | ||
|
|
35af53828a |
No files matched your search
@@ -212,7 +212,7 @@ class StMemVar(name: String,
|
||||
}
|
||||
|
||||
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, position: Position) :
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
||||
override fun printProperties() {
|
||||
print(name)
|
||||
@@ -220,7 +220,7 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, position:
|
||||
}
|
||||
|
||||
|
||||
class StRomSub(name: String, val address: UInt, parameters: List<StSubroutineParameter>, position: Position) :
|
||||
class StRomSub(name: String, val address: UInt, val parameters: List<StSubroutineParameter>, val returnTypes: List<DataType>, position: Position) :
|
||||
StNode(name, StNodeType.ROMSUB, position) {
|
||||
override fun printProperties() {
|
||||
print("$name address=${address.toHex()}")
|
||||
|
||||
@@ -12,6 +12,15 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
init {
|
||||
if(type==DataType.BOOL)
|
||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||
if(type==DataType.UNDEFINED) {
|
||||
@Suppress("LeakingThis")
|
||||
when(this) {
|
||||
is PtBuiltinFunctionCall -> { /* void function call */ }
|
||||
is PtFunctionCall -> { /* void function call */ }
|
||||
is PtIdentifier -> { /* non-variable identifier */ }
|
||||
else -> throw IllegalArgumentException("type should be known @$position")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
|
||||
@@ -8,6 +8,7 @@ class PtAsmSub(
|
||||
val address: UInt?,
|
||||
val clobbers: Set<CpuRegister>,
|
||||
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
|
||||
val returnTypes: List<DataType>,
|
||||
val retvalRegisters: List<RegisterOrStatusflag>,
|
||||
val inline: Boolean,
|
||||
position: Position
|
||||
|
||||
@@ -3,11 +3,6 @@ package prog8.code.core
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
interface IMachineFloat {
|
||||
fun toDouble(): Double
|
||||
fun makeFloatFillAsm(): String
|
||||
}
|
||||
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65c02,
|
||||
@@ -27,7 +22,7 @@ interface IMachineDefinition {
|
||||
val cpu: CpuType
|
||||
|
||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||
fun getFloat(num: Number): IMachineFloat
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
|
||||
@@ -3,7 +3,7 @@ package prog8.code.core
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%")
|
||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
||||
|
||||
|
||||
@@ -117,4 +117,6 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
require(size>0)
|
||||
return free.containsAll((address until address+size.toUInt()).toList())
|
||||
}
|
||||
|
||||
abstract fun allocateCx16VirtualRegisters()
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = TODO("float from number")
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.output == OutputType.XEX)
|
||||
|
||||
@@ -12,7 +12,6 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
override val SCRATCH_W1 = 0xcdu // temp storage 1 for a word $cd+$ce
|
||||
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(
|
||||
ZeropageType.FLOATSAFE,
|
||||
@@ -40,6 +39,14 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
}
|
||||
|
||||
val distictFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distictFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
TODO("Not known if atari can put the virtual regs in ZP")
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
|
||||
@@ -38,6 +38,14 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
}
|
||||
|
||||
val distictFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distictFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
TODO("Not known if C128 can put the virtual regs in ZP")
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.code.target.c64
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.core.Zeropage
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
@@ -29,10 +26,9 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
@@ -48,8 +44,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zeropage locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x03, 0x04, 0x10, 0x11, 0x12,
|
||||
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
@@ -60,7 +56,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
@@ -69,6 +65,32 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
}
|
||||
|
||||
val distictFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distictFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// 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)
|
||||
// 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
|
||||
free.remove((4+reg*2).toUInt())
|
||||
free.remove((5+reg*2).toUInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
package prog8.code.target.cbm
|
||||
|
||||
import prog8.code.core.IMachineFloat
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte): IMachineFloat {
|
||||
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte) {
|
||||
|
||||
companion object {
|
||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
@@ -58,7 +57,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
}
|
||||
}
|
||||
|
||||
override fun toDouble(): Double {
|
||||
fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0.toInt() - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
@@ -67,7 +66,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
return if (sign) -result else result
|
||||
}
|
||||
|
||||
override fun makeFloatFillAsm(): String {
|
||||
fun makeFloatFillAsm(): String {
|
||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||
|
||||
@@ -20,7 +20,7 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
|
||||
@@ -43,18 +43,27 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
val distictFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distictFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
// note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// however, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||
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
|
||||
}
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
|
||||
override fun getFloat(num: Number) = TODO("float from number")
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return listOf("syslib")
|
||||
|
||||
@@ -311,13 +311,12 @@ class AsmGen(internal val program: Program,
|
||||
is Subroutine -> programGen.translateSubroutine(stmt)
|
||||
is InlineAssembly -> translate(stmt)
|
||||
is BuiltinFunctionCallStatement -> builtinFunctionsAsmGen.translateFunctioncallStatement(stmt)
|
||||
is FunctionCallStatement -> functioncallAsmGen.translateFunctionCallStatement(stmt) // TODO try to remove this last usage of FunctionCallStatement node in the codegen.
|
||||
is FunctionCallStatement -> functioncallAsmGen.translateFunctionCallStatement(stmt)
|
||||
is Assignment -> assignmentAsmGen.translate(stmt)
|
||||
is Jump -> {
|
||||
val (asmLabel, indirect) = getJumpTarget(stmt)
|
||||
jmp(asmLabel, indirect)
|
||||
}
|
||||
is GoSub -> translate(stmt)
|
||||
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
|
||||
is Label -> translate(stmt)
|
||||
is ConditionalBranch -> translate(stmt)
|
||||
@@ -441,15 +440,9 @@ class AsmGen(internal val program: Program,
|
||||
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
|
||||
functioncallAsmGen.saveXbeforeCall(functionCall)
|
||||
|
||||
internal fun saveXbeforeCall(gosub: GoSub) =
|
||||
functioncallAsmGen.saveXbeforeCall(gosub)
|
||||
|
||||
internal fun restoreXafterCall(functionCall: IFunctionCall) =
|
||||
functioncallAsmGen.restoreXafterCall(functionCall)
|
||||
|
||||
internal fun restoreXafterCall(gosub: GoSub) =
|
||||
functioncallAsmGen.restoreXafterCall(gosub)
|
||||
|
||||
internal fun translateNormalAssignment(assign: AsmAssignment) =
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
|
||||
@@ -872,18 +865,6 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(gosub: GoSub) {
|
||||
val tgt = gosub.identifier.targetSubroutine(program)
|
||||
if(tgt!=null && tgt.isAsmSubroutine) {
|
||||
// no need to rescue X , this has been taken care of already
|
||||
out(" jsr ${getJumpTarget(gosub)}")
|
||||
} else {
|
||||
saveXbeforeCall(gosub)
|
||||
out(" jsr ${getJumpTarget(gosub)}")
|
||||
restoreXafterCall(gosub)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getJumpTarget(jump: Jump): Pair<String, Boolean> {
|
||||
val ident = jump.identifier
|
||||
val label = jump.generatedLabel
|
||||
@@ -903,8 +884,6 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
private fun getJumpTarget(gosub: GoSub): String = asmSymbolName(gosub.identifier)
|
||||
|
||||
private fun translate(ret: Return, withRts: Boolean=true) {
|
||||
ret.value?.let { returnvalue ->
|
||||
val sub = ret.definingSubroutine!!
|
||||
@@ -2874,7 +2853,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, target.datatype, scope, variableAsmName = asmVariableName(target.scopedName))
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, this, target.datatype, scope, variableAsmName = asmVariableName(target.scopedName))
|
||||
if (dt in ByteDatatypes) {
|
||||
out(" pla")
|
||||
assignRegister(RegisterOrPair.A, tgt)
|
||||
|
||||
@@ -59,7 +59,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
// TODO more assembly optimizations
|
||||
// TODO more assembly peephole optimizations
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
@@ -139,20 +139,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
|
||||
val bank = fcall.args[0].constValue(program)?.number?.toInt()
|
||||
val address = fcall.args[1].constValue(program)?.number?.toInt()
|
||||
if(bank==null || address==null)
|
||||
throw AssemblyError("callfar (jsrfar) requires constant arguments")
|
||||
|
||||
if(address !in 0xa000..0xbfff)
|
||||
throw AssemblyError("callfar done on address outside of cx16 banked ram")
|
||||
if(bank==0)
|
||||
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
|
||||
|
||||
val address = fcall.args[1].constValue(program)?.number?.toInt() ?: 0
|
||||
val argAddrArg = fcall.args[2]
|
||||
if(bank==null)
|
||||
throw AssemblyError("callfar (jsrfar) bank has to be a constant")
|
||||
if(fcall.args[1].constValue(program) == null) {
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY, false)
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1 ; store jsrfar address word")
|
||||
}
|
||||
|
||||
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||
asmgen.out("""
|
||||
jsr cx16.jsrfar
|
||||
.word ${address.toHex()}
|
||||
+ .word ${address.toHex()}
|
||||
.byte ${bank.toHex()}""")
|
||||
} else {
|
||||
when(argAddrArg) {
|
||||
@@ -162,7 +161,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
|
||||
jsr cx16.jsrfar
|
||||
.word ${address.toHex()}
|
||||
+ .word ${address.toHex()}
|
||||
.byte ${bank.toHex()}
|
||||
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
|
||||
}
|
||||
@@ -170,7 +169,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda ${argAddrArg.number.toHex()}
|
||||
jsr cx16.jsrfar
|
||||
.word ${address.toHex()}
|
||||
+ .word ${address.toHex()}
|
||||
.byte ${bank.toHex()}
|
||||
sta ${argAddrArg.number.toHex()}""")
|
||||
}
|
||||
@@ -322,9 +321,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
||||
val target =
|
||||
if(resultToStack)
|
||||
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
|
||||
AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, null)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
|
||||
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, asmgen)
|
||||
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
allocations.allocateMemorySlab(name, size, align)
|
||||
@@ -336,7 +335,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
||||
else {
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,7 +648,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,7 +669,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,7 +692,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +703,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
||||
else {
|
||||
asmgen.out(" jsr math.randbyte")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
"rndw" -> {
|
||||
@@ -712,7 +711,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
||||
else {
|
||||
asmgen.out(" jsr math.randword")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("wrong func")
|
||||
@@ -866,9 +865,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
val mr0 = fcall.args[0] as? DirectMemoryRead
|
||||
val mr1 = fcall.args[1] as? DirectMemoryRead
|
||||
if (mr0 != null)
|
||||
needAsave = mr0.addressExpression !is NumericLiteral && mr0.addressExpression !is IdentifierReference
|
||||
needAsave = mr0.addressExpression !is NumericLiteral
|
||||
if (mr1 != null)
|
||||
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral && mr1.addressExpression !is IdentifierReference)
|
||||
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral)
|
||||
}
|
||||
when(reg) {
|
||||
RegisterOrPair.AX -> {
|
||||
@@ -1085,7 +1084,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
}
|
||||
}
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, conv.dt, null, variableAsmName = varname)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, conv.dt, null, variableAsmName = varname)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
@@ -1101,7 +1100,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
}
|
||||
}
|
||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, program, asmgen)
|
||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, asmgen)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
@@ -519,12 +519,37 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if(rightVal!=null && rightVal==2) {
|
||||
translateExpressionInternal(expr.left)
|
||||
when(leftDt) {
|
||||
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||
DataType.BYTE -> asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x")
|
||||
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bpl +
|
||||
inc P8ESTACK_LO+1,x
|
||||
lda P8ESTACK_LO+1,x
|
||||
+ asl a
|
||||
ror P8ESTACK_LO+1,x""")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_HI+1,x
|
||||
bpl ++
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+ lda P8ESTACK_HI+1,x
|
||||
+ asl a
|
||||
ror P8ESTACK_HI+1,x
|
||||
ror P8ESTACK_LO+1,x""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||
@@ -35,17 +38,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
|
||||
internal fun saveXbeforeCall(gosub: GoSub) {
|
||||
val sub = gosub.identifier.targetSubroutine(program)
|
||||
if(sub?.shouldSaveX()==true) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(sub.shouldSaveX()) {
|
||||
@@ -57,17 +49,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreXafterCall(gosub: GoSub) {
|
||||
val sub = gosub.identifier.targetSubroutine(program)
|
||||
if(sub?.shouldSaveX()==true) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
|
||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
||||
@@ -81,11 +62,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
||||
val subAsmName = asmgen.asmSymbolName(call.target)
|
||||
|
||||
if(!isExpression && !sub.isAsmSubroutine) {
|
||||
if(!optimizeIntArgsViaRegisters(sub))
|
||||
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
|
||||
}
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline && asmgen.options.optimize) {
|
||||
@@ -234,10 +210,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
} else {
|
||||
val target: AsmAssignTarget =
|
||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, sub, register = register)
|
||||
else {
|
||||
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
||||
AsmAssignTarget.fromRegisters(register, signed, sub, program, asmgen)
|
||||
AsmAssignTarget.fromRegisters(register, signed, sub, asmgen)
|
||||
}
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
|
||||
@@ -44,6 +44,15 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
if(errors.noErrors()) {
|
||||
program.allBlocks.forEach { block2asm(it) }
|
||||
|
||||
// the global list of all floating point constants for the whole program
|
||||
asmgen.out("; global float constants")
|
||||
for (flt in allocator.globalFloatConsts) {
|
||||
val floatFill = compTarget.machine.getFloatAsmBytes(flt.key)
|
||||
val floatvalue = flt.key
|
||||
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
||||
}
|
||||
|
||||
memorySlabs()
|
||||
footer()
|
||||
}
|
||||
@@ -70,7 +79,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
|
||||
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
||||
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
||||
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition:
|
||||
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
|
||||
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
|
||||
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
|
||||
asmgen.out(".endweak")
|
||||
@@ -170,14 +179,6 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
// the global list of all floating point constants for the whole program
|
||||
asmgen.out("; global float constants")
|
||||
for (flt in allocator.globalFloatConsts) {
|
||||
val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm()
|
||||
val floatvalue = flt.key
|
||||
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
||||
}
|
||||
|
||||
// program end
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
}
|
||||
@@ -215,9 +216,9 @@ internal class ProgramAndVarsGen(
|
||||
if(!options.dontReinitGlobals) {
|
||||
// generate subroutine to initialize block-level (global) variables
|
||||
if (initializers.isNotEmpty()) {
|
||||
asmgen.out("prog8_init_vars\t.proc\n")
|
||||
asmgen.out("prog8_init_vars\t.block\n")
|
||||
initializers.forEach { assign -> asmgen.translate(assign) }
|
||||
asmgen.out(" rts\n .pend")
|
||||
asmgen.out(" rts\n .bend")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,17 +270,27 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
asmgen.out("")
|
||||
|
||||
val asmStartScope: String
|
||||
val asmEndScope: String
|
||||
if(sub.definingBlock.options().contains("force_output")) {
|
||||
asmStartScope = ".block"
|
||||
asmEndScope = ".bend"
|
||||
} else {
|
||||
asmStartScope = ".proc"
|
||||
asmEndScope = ".pend"
|
||||
}
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
if(sub.asmAddress!=null)
|
||||
return // already done at the memvars section
|
||||
|
||||
// asmsub with most likely just an inline asm in it
|
||||
asmgen.out("${sub.name}\t.proc")
|
||||
asmgen.out("${sub.name}\t$asmStartScope")
|
||||
sub.statements.forEach { asmgen.translate(it) }
|
||||
asmgen.out(" .pend\n")
|
||||
asmgen.out(" $asmEndScope\n")
|
||||
} else {
|
||||
// regular subroutine
|
||||
asmgen.out("${sub.name}\t.proc")
|
||||
asmgen.out("${sub.name}\t$asmStartScope")
|
||||
|
||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
||||
require(scope.type==StNodeType.SUBROUTINE)
|
||||
@@ -308,7 +319,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||
if(sub.parameters.size==1) {
|
||||
val dt = sub.parameters[0].type
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
||||
if(dt in ByteDatatypes)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
else
|
||||
@@ -316,8 +327,8 @@ internal class ProgramAndVarsGen(
|
||||
} else {
|
||||
require(sub.parameters.size==2)
|
||||
// 2 simple byte args, first in A, second in Y
|
||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||
}
|
||||
@@ -356,7 +367,7 @@ internal class ProgramAndVarsGen(
|
||||
.map { it.value as StStaticVariable }
|
||||
nonZpVariables2asm(variables)
|
||||
|
||||
asmgen.out(" .pend\n")
|
||||
asmgen.out(" $asmEndScope\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +449,7 @@ internal class ProgramAndVarsGen(
|
||||
val result = mutableListOf<ZpStringWithInitial>()
|
||||
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
||||
for (variable in vars) {
|
||||
val svar = symboltable.lookup(variable.key) as StStaticVariable // TODO faster in flat lookup table
|
||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
||||
if(svar.initialStringValue!=null)
|
||||
result.add(ZpStringWithInitial(variable.key, variable.value, svar.initialStringValue!!))
|
||||
}
|
||||
@@ -449,7 +460,7 @@ internal class ProgramAndVarsGen(
|
||||
val result = mutableListOf<ZpArrayWithInitial>()
|
||||
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
||||
for (variable in vars) {
|
||||
val svar = symboltable.lookup(variable.key) as StStaticVariable // TODO faster in flat lookup table
|
||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
||||
if(svar.initialArrayValue!=null)
|
||||
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.initialArrayValue!!))
|
||||
}
|
||||
@@ -496,7 +507,7 @@ internal class ProgramAndVarsGen(
|
||||
if(initialValue==0) {
|
||||
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
|
||||
} else {
|
||||
val floatFill = compTarget.machine.getFloat(initialValue).makeFloatFillAsm()
|
||||
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
|
||||
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
|
||||
}
|
||||
}
|
||||
@@ -555,7 +566,7 @@ internal class ProgramAndVarsGen(
|
||||
DataType.ARRAY_F -> {
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
val floatFills = array.map {
|
||||
compTarget.machine.getFloat(it.number!!).makeFloatFillAsm()
|
||||
compTarget.machine.getFloatAsmBytes(it.number!!)
|
||||
}
|
||||
asmgen.out(varname)
|
||||
for (f in array.zip(floatFills))
|
||||
|
||||
@@ -29,7 +29,6 @@ internal enum class SourceStorageKind {
|
||||
}
|
||||
|
||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
val scope: Subroutine?,
|
||||
@@ -70,28 +69,28 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
if(reg.statusflag!=null)
|
||||
throw AssemblyError("can't assign value to processor statusflag directly")
|
||||
else
|
||||
return AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
||||
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
||||
}
|
||||
}
|
||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
}
|
||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, asmgen: AsmGen): AsmAssignTarget =
|
||||
when(registers) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||
RegisterOrPair.FAC1,
|
||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, register = registers)
|
||||
RegisterOrPair.R0,
|
||||
RegisterOrPair.R1,
|
||||
RegisterOrPair.R2,
|
||||
@@ -107,7 +106,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
RegisterOrPair.R12,
|
||||
RegisterOrPair.R13,
|
||||
RegisterOrPair.R14,
|
||||
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
|
||||
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
|
||||
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, origtarget.datatype, origtarget.scope,
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, origtarget.datatype, origtarget.scope,
|
||||
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
|
||||
} else origtarget
|
||||
}
|
||||
@@ -220,9 +220,25 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
|
||||
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
|
||||
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
|
||||
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
|
||||
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
|
||||
RegisterOrPair.AX -> assignVirtualRegister(assign.target, RegisterOrPair.AX)
|
||||
RegisterOrPair.AY -> assignVirtualRegister(assign.target, RegisterOrPair.AY)
|
||||
RegisterOrPair.XY -> assignVirtualRegister(assign.target, RegisterOrPair.XY)
|
||||
RegisterOrPair.R0 -> assignVirtualRegister(assign.target, RegisterOrPair.R0)
|
||||
RegisterOrPair.R1 -> assignVirtualRegister(assign.target, RegisterOrPair.R1)
|
||||
RegisterOrPair.R2 -> assignVirtualRegister(assign.target, RegisterOrPair.R2)
|
||||
RegisterOrPair.R3 -> assignVirtualRegister(assign.target, RegisterOrPair.R3)
|
||||
RegisterOrPair.R4 -> assignVirtualRegister(assign.target, RegisterOrPair.R4)
|
||||
RegisterOrPair.R5 -> assignVirtualRegister(assign.target, RegisterOrPair.R5)
|
||||
RegisterOrPair.R6 -> assignVirtualRegister(assign.target, RegisterOrPair.R6)
|
||||
RegisterOrPair.R7 -> assignVirtualRegister(assign.target, RegisterOrPair.R7)
|
||||
RegisterOrPair.R8 -> assignVirtualRegister(assign.target, RegisterOrPair.R8)
|
||||
RegisterOrPair.R9 -> assignVirtualRegister(assign.target, RegisterOrPair.R9)
|
||||
RegisterOrPair.R10 -> assignVirtualRegister(assign.target, RegisterOrPair.R10)
|
||||
RegisterOrPair.R11 -> assignVirtualRegister(assign.target, RegisterOrPair.R11)
|
||||
RegisterOrPair.R12 -> assignVirtualRegister(assign.target, RegisterOrPair.R12)
|
||||
RegisterOrPair.R13 -> assignVirtualRegister(assign.target, RegisterOrPair.R13)
|
||||
RegisterOrPair.R14 -> assignVirtualRegister(assign.target, RegisterOrPair.R14)
|
||||
RegisterOrPair.R15 -> assignVirtualRegister(assign.target, RegisterOrPair.R15)
|
||||
else -> {
|
||||
val sflag = returnValue.second.statusflag
|
||||
if(sflag!=null)
|
||||
@@ -301,6 +317,17 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda cx16.${register.toString().lowercase()}L")
|
||||
assignRegisterByte(target, CpuRegister.A)
|
||||
}
|
||||
in WordDatatypes -> assignRegisterpairWord(target, register)
|
||||
else -> throw AssemblyError("expected byte or word")
|
||||
}
|
||||
}
|
||||
|
||||
private fun attemptAssignOptimizedBinexpr(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
||||
if(expr.operator in ComparisonOperators) {
|
||||
if(expr.right.constValue(program)?.number == 0.0) {
|
||||
@@ -330,140 +357,288 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
if(!expr.inferType(program).isInteger)
|
||||
return false
|
||||
|
||||
if(expr.operator!="+" && expr.operator!="-")
|
||||
return false
|
||||
|
||||
val dt = expr.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||
val left = expr.left
|
||||
val right = expr.right
|
||||
if(dt in ByteDatatypes) {
|
||||
when (right) {
|
||||
is IdentifierReference -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
val symname = asmgen.asmVariableName(right)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out(" clc | adc $symname")
|
||||
else
|
||||
asmgen.out(" sec | sbc $symname")
|
||||
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)
|
||||
return true
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||
else
|
||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
return true
|
||||
}
|
||||
} else if(dt in WordDatatypes) {
|
||||
when (right) {
|
||||
is AddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
val symbol = asmgen.asmVariableName(right.identifier)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<$symbol
|
||||
pha
|
||||
tya
|
||||
adc #>$symbol
|
||||
tay
|
||||
pla""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<$symbol
|
||||
pha
|
||||
tya
|
||||
sbc #>$symbol
|
||||
tay
|
||||
pla""")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val symname = asmgen.asmVariableName(right)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $symname
|
||||
pha
|
||||
tya
|
||||
adc $symname+1
|
||||
tay
|
||||
pla""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc $symname
|
||||
pha
|
||||
tya
|
||||
sbc $symname+1
|
||||
tay
|
||||
pla""")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<${right.number.toHex()}
|
||||
pha
|
||||
tya
|
||||
adc #>${right.number.toHex()}
|
||||
tay
|
||||
pla""")
|
||||
} else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${right.number.toHex()}
|
||||
pha
|
||||
tya
|
||||
sbc #>${right.number.toHex()}
|
||||
tay
|
||||
pla""")
|
||||
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)
|
||||
return true
|
||||
}
|
||||
is TypecastExpression -> {
|
||||
val castedValue = right.expression
|
||||
if(right.type in WordDatatypes && castedValue.inferType(program).isBytes) {
|
||||
if(castedValue is IdentifierReference) {
|
||||
val castedSymname = asmgen.asmVariableName(castedValue)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $castedSymname
|
||||
bcc +
|
||||
iny
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if(expr.operator == "==" || expr.operator == "!=") {
|
||||
// expression datatype is BOOL (ubyte) but operands can be anything
|
||||
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
|
||||
expr.left.isSimple && expr.right.isSimple) {
|
||||
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)
|
||||
if(expr.operator=="==") {
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
bne +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc $castedSymname
|
||||
bcs +
|
||||
dey
|
||||
} else {
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
} else if(expr.left.inferType(program).isWords && expr.right.inferType(program).isWords &&
|
||||
expr.left.isSimple && expr.right.isSimple) {
|
||||
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)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
if(expr.operator=="==") {
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
lda #0
|
||||
bne ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else if(expr.operator=="+" || expr.operator=="-") {
|
||||
val dt = expr.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||
val left = expr.left
|
||||
val right = expr.right
|
||||
if(dt in ByteDatatypes) {
|
||||
when (right) {
|
||||
is IdentifierReference -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
val symname = asmgen.asmVariableName(right)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out(" clc | adc $symname")
|
||||
else
|
||||
asmgen.out(" sec | sbc $symname")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||
else
|
||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
} else if(dt in WordDatatypes) {
|
||||
when (right) {
|
||||
is AddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
val symbol = asmgen.asmVariableName(right.identifier)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<$symbol
|
||||
pha
|
||||
tya
|
||||
adc #>$symbol
|
||||
tay
|
||||
pla""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<$symbol
|
||||
pha
|
||||
tya
|
||||
sbc #>$symbol
|
||||
tay
|
||||
pla""")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val symname = asmgen.asmVariableName(right)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $symname
|
||||
pha
|
||||
tya
|
||||
adc $symname+1
|
||||
tay
|
||||
pla""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc $symname
|
||||
pha
|
||||
tya
|
||||
sbc $symname+1
|
||||
tay
|
||||
pla""")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<${right.number.toHex()}
|
||||
pha
|
||||
tya
|
||||
adc #>${right.number.toHex()}
|
||||
tay
|
||||
pla""")
|
||||
} else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${right.number.toHex()}
|
||||
pha
|
||||
tya
|
||||
sbc #>${right.number.toHex()}
|
||||
tay
|
||||
pla""")
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
is TypecastExpression -> {
|
||||
val castedValue = right.expression
|
||||
if(right.type in WordDatatypes && castedValue.inferType(program).isBytes) {
|
||||
if(castedValue is IdentifierReference) {
|
||||
val castedSymname = asmgen.asmVariableName(castedValue)
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $castedSymname
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc $castedSymname
|
||||
bcs +
|
||||
dey
|
||||
+""")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun assignLogicalWithSimpleRightOperandByte(target: AsmAssignTarget, left: Expression, operator: String, right: Expression) {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, false)
|
||||
val operand = when(right) {
|
||||
is NumericLiteral -> "#${right.number.toHex()}"
|
||||
is IdentifierReference -> asmgen.asmSymbolName(right)
|
||||
else -> throw AssemblyError("wrong right operand type")
|
||||
}
|
||||
when (operator) {
|
||||
"&", "and" -> asmgen.out(" and $operand")
|
||||
"|", "or" -> asmgen.out(" ora $operand")
|
||||
"^", "xor" -> asmgen.out(" eor $operand")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A)
|
||||
}
|
||||
|
||||
private fun assignLogicalWithSimpleRightOperandWord(target: AsmAssignTarget, left: Expression, operator: String, right: Expression) {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, false)
|
||||
when(right) {
|
||||
is NumericLiteral -> {
|
||||
val number = right.number.toHex()
|
||||
when (operator) {
|
||||
"&", "and" -> asmgen.out(" and #<$number | pha | tya | and #>$number | tay | pla")
|
||||
"|", "or" -> asmgen.out(" ora #<$number | pha | tya | ora #>$number | tay | pla")
|
||||
"^", "xor" -> asmgen.out(" eor #<$number | pha | tya | eor #>$number | tay | pla")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val name = asmgen.asmSymbolName(right)
|
||||
when (operator) {
|
||||
"&", "and" -> asmgen.out(" and $name | pha | tya | and $name+1 | tay | pla")
|
||||
"|", "or" -> asmgen.out(" ora $name | pha | tya | ora $name+1 | tay | pla")
|
||||
"^", "xor" -> asmgen.out(" eor $name | pha | tya | eor $name+1 | tay | pla")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("wrong right operand type")
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
private fun attemptAssignToByteCompareZero(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
||||
when (expr.operator) {
|
||||
"==" -> {
|
||||
@@ -536,8 +711,6 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun fallbackToStackEval(assign: AsmAssignment) {
|
||||
// TODO DON'T STACK-EVAL... perhaps by using a temp var? so that it becomes augmentable assignment expression?
|
||||
// or don't try to solve it here in this one case and rather rewrite the whole stack based value evaluation.
|
||||
// this routine is called for assigning a binaryexpression value:
|
||||
// - if it's a boolean comparison expression and the workaround isn't possible (no origTarget ast node)
|
||||
// - for all other binary expressions.
|
||||
@@ -561,7 +734,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||
asmgen.out(" ldy #${stringVal.value.length}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
@@ -580,14 +753,14 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||
}
|
||||
@@ -604,7 +777,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
// use subroutine
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||
val stringVal = variable.value as StringLiteral
|
||||
asmgen.out(" ldy #${stringVal.value.length}")
|
||||
@@ -618,7 +791,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
val arrayVal = variable.value as ArrayLiteral
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
@@ -627,7 +800,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
val arrayVal = variable.value as ArrayLiteral
|
||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||
return
|
||||
@@ -738,10 +911,10 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
return
|
||||
}
|
||||
|
||||
if(origTypeCastExpression.type == DataType.UBYTE) {
|
||||
if(valueDt in WordDatatypes && origTypeCastExpression.type == DataType.UBYTE) {
|
||||
val parentTc = origTypeCastExpression.parent as? TypecastExpression
|
||||
if(parentTc!=null && parentTc.type==DataType.UWORD) {
|
||||
// typecast something to ubyte and directly back to uword
|
||||
// typecast a word value to ubyte and directly back to uword
|
||||
// generate code for lsb(value) here instead of the ubyte typecast
|
||||
return assignCastViaLsbFunc(value, target)
|
||||
}
|
||||
@@ -2579,7 +2752,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
|
||||
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
|
||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, asmgen)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
@@ -2589,14 +2762,14 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
throw AssemblyError("can't directly assign a FLOAT expression to an integer variable $expr")
|
||||
} else {
|
||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, scope, variableAsmName = asmVarName)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, asmgen)
|
||||
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
|
||||
translateNormalAssignment(assign)
|
||||
|
||||
@@ -236,7 +236,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
else -> {
|
||||
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
|
||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
||||
@@ -305,7 +305,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.A,
|
||||
target.datatype == DataType.BYTE, null,
|
||||
program,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||
@@ -317,7 +316,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.AY,
|
||||
target.datatype == DataType.WORD, null,
|
||||
program,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||
@@ -329,7 +327,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.FAC1,
|
||||
true, null,
|
||||
program,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||
|
||||
@@ -3,6 +3,7 @@ plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id "io.kotest" version "0.3.9"
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -31,6 +32,7 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -42,6 +44,22 @@ sourceSets {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDir "${project.projectDir}/test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,14 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeAst" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.code.core.*
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
|
||||
@@ -33,7 +31,9 @@ internal class AssignmentGen(private val codeGen: CodeGen, private val expressio
|
||||
else
|
||||
fallbackAssign(assignment)
|
||||
} else if(array!=null) {
|
||||
// TODO in-place array element assignment?
|
||||
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
|
||||
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
|
||||
// will be optimized later and have the double assignments removed.
|
||||
fallbackAssign(assignment)
|
||||
} else {
|
||||
fallbackAssign(assignment)
|
||||
@@ -165,6 +165,11 @@ internal class AssignmentGen(private val codeGen: CodeGen, private val expressio
|
||||
throw AssemblyError("non-array var indexing requires bytes index")
|
||||
val idxReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(array.index, idxReg, -1)
|
||||
if(zero) {
|
||||
// there's no STOREZIX instruction
|
||||
resultRegister = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
||||
}
|
||||
code += VmCodeInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, value = variableAddr)
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
|
||||
"ror" -> funcRolRor(Opcode.ROXR, call, resultRegister)
|
||||
"rol2" -> funcRolRor(Opcode.ROL, call, resultRegister)
|
||||
"ror2" -> funcRolRor(Opcode.ROR, call, resultRegister)
|
||||
else -> TODO("builtinfunc ${call.name}")
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
}
|
||||
|
||||
if(options.optimize) {
|
||||
val optimizer = VmPeepholeOptimizer(vmprog, allocations)
|
||||
val optimizer = VmPeepholeOptimizer(vmprog)
|
||||
optimizer.optimize()
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
val code = when(node) {
|
||||
is PtBlock -> translate(node)
|
||||
is PtSub -> translate(node)
|
||||
is PtAsmSub -> translate(node)
|
||||
is PtScopeVarsDecls -> VmCodeChunk() // vars should be looked up via symbol table
|
||||
is PtVariable -> VmCodeChunk() // var should be looked up via symbol table
|
||||
is PtMemMapped -> VmCodeChunk() // memmapped var should be looked up via symbol table
|
||||
@@ -103,7 +104,6 @@ class CodeGen(internal val program: PtProgram,
|
||||
is PtConditionalBranch -> translate(node)
|
||||
is PtInlineAssembly -> VmCodeChunk(VmCodeInlineAsm(node.assembly))
|
||||
is PtIncludeBinary -> VmCodeChunk(VmCodeInlineBinary(node.file, node.offset, node.length))
|
||||
is PtAsmSub -> TODO("asmsub not yet supported on virtual machine target ${node.position}")
|
||||
is PtAddressOf,
|
||||
is PtContainmentCheck,
|
||||
is PtMemoryByte,
|
||||
@@ -275,14 +275,8 @@ class CodeGen(internal val program: PtProgram,
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += translateNode(forLoop.statements)
|
||||
if(step<3) {
|
||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
} else {
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
code += addConstReg(loopvarDt, indexReg, step)
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
}
|
||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
val branchOpcode = if(loopvar.dt in SignedDatatypes) Opcode.BLES else Opcode.BLE
|
||||
code += VmCodeInstruction(branchOpcode, loopvarDt, reg1=indexReg, reg2=endvalueReg, labelSymbol=loopLabel)
|
||||
return code
|
||||
@@ -314,14 +308,8 @@ class CodeGen(internal val program: PtProgram,
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
|
||||
code += VmCodeLabel(loopLabel)
|
||||
code += translateNode(forLoop.statements)
|
||||
if(step<3) {
|
||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
} else {
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
code += addConstReg(loopvarDt, indexReg, step)
|
||||
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
}
|
||||
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
|
||||
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
|
||||
code += if(rangeEndWrapped==0) {
|
||||
VmCodeInstruction(Opcode.BNZ, loopvarDt, reg1 = indexReg, labelSymbol = loopLabel)
|
||||
} else {
|
||||
@@ -379,18 +367,13 @@ class CodeGen(internal val program: PtProgram,
|
||||
}
|
||||
else -> {
|
||||
val valueReg = vmRegisters.nextFree()
|
||||
val operandReg = vmRegisters.nextFree()
|
||||
if(value>0) {
|
||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=value)
|
||||
code += VmCodeInstruction(Opcode.ADDR, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value=value)
|
||||
code += VmCodeInstruction(Opcode.ADDM, dt, reg1=valueReg, value=address.toInt())
|
||||
}
|
||||
else {
|
||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=-value)
|
||||
code += VmCodeInstruction(Opcode.SUBR, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value=-value)
|
||||
code += VmCodeInstruction(Opcode.SUBM, dt, reg1=valueReg, value=address.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,14 +492,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.ASR, dt, reg1=reg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.LSR, dt, reg1=reg)
|
||||
if(pow2==1 && !signed) {
|
||||
code += VmCodeInstruction(Opcode.LSR, dt, reg1=reg) // simple single bit shift
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
else if(pow2>=1 &&!signed) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
||||
@@ -542,14 +521,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.ASRM, dt, value=address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.LSRM, dt, value=address)
|
||||
if(pow2==1 && !signed) {
|
||||
code += VmCodeInstruction(Opcode.LSRM, dt, value=address) // just simple bit shift
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
else if(pow2>=1 && !signed) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
||||
@@ -787,6 +762,17 @@ class CodeGen(internal val program: PtProgram,
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(sub: PtAsmSub): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeComment("ASMSUB: ${sub.scopedName}")
|
||||
code += VmCodeLabel(sub.scopedName)
|
||||
for (child in sub.children) {
|
||||
code += translateNode(child)
|
||||
}
|
||||
code += VmCodeComment("ASMSUB-END '${sub.name}'")
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(block: PtBlock): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeComment("BLOCK '${block.name}' addr=${block.address} lib=${block.library}")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.StSub
|
||||
import prog8.code.StSubroutineParameter
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.vm.Opcode
|
||||
@@ -812,45 +814,52 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
}
|
||||
|
||||
fun translate(fcall: PtFunctionCall, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
|
||||
val subroutine = codeGen.symbolTable.flat.getValue(fcall.functionName) as StSub
|
||||
val code = VmCodeChunk()
|
||||
for ((arg, parameter) in fcall.args.zip(subroutine.parameters)) {
|
||||
val paramDt = codeGen.vmType(parameter.type)
|
||||
if(codeGen.isZero(arg)) {
|
||||
if (paramDt == VmDataType.FLOAT) {
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
||||
} else {
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
||||
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.functionName)) {
|
||||
is StSub -> {
|
||||
val code = VmCodeChunk()
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
val paramDt = codeGen.vmType(parameter.type)
|
||||
if(codeGen.isZero(arg)) {
|
||||
if (paramDt == VmDataType.FLOAT) {
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
||||
} else {
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
|
||||
}
|
||||
} else {
|
||||
if (paramDt == VmDataType.FLOAT) {
|
||||
val argFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(arg, -1, argFpReg)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, fpReg1 = argFpReg, value = mem)
|
||||
} else {
|
||||
val argReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(arg, argReg, -1)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, reg1 = argReg, value = mem)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (paramDt == VmDataType.FLOAT) {
|
||||
val argFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(arg, -1, argFpReg)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, fpReg1 = argFpReg, value = mem)
|
||||
code += VmCodeInstruction(Opcode.CALL, labelSymbol=fcall.functionName)
|
||||
if(fcall.type==DataType.FLOAT) {
|
||||
if (!fcall.void && resultFpRegister != 0) {
|
||||
// Call convention: result value is in fr0, so put it in the required register instead.
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.FLOAT, fpReg1 = resultFpRegister, fpReg2 = 0)
|
||||
}
|
||||
} else {
|
||||
val argReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(arg, argReg, -1)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
code += VmCodeInstruction(Opcode.STOREM, paramDt, reg1 = argReg, value = mem)
|
||||
if (!fcall.void && resultRegister != 0) {
|
||||
// Call convention: result value is in r0, so put it in the required register instead.
|
||||
code += VmCodeInstruction(Opcode.LOADR, codeGen.vmType(fcall.type), reg1 = resultRegister, reg2 = 0)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
is StRomSub -> {
|
||||
throw AssemblyError("virtual machine doesn't yet support calling romsub $fcall")
|
||||
}
|
||||
else -> throw AssemblyError("invalid node type")
|
||||
}
|
||||
code += VmCodeInstruction(Opcode.CALL, labelSymbol=fcall.functionName)
|
||||
if(fcall.type==DataType.FLOAT) {
|
||||
if (!fcall.void && resultFpRegister != 0) {
|
||||
// Call convention: result value is in fr0, so put it in the required register instead.
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.FLOAT, fpReg1 = resultFpRegister, fpReg2 = 0)
|
||||
}
|
||||
} else {
|
||||
if (!fcall.void && resultRegister != 0) {
|
||||
// Call convention: result value is in r0, so put it in the required register instead.
|
||||
code += VmCodeInstruction(Opcode.LOADR, codeGen.vmType(fcall.type), reg1 = resultRegister, reg2 = 0)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,8 +60,8 @@ class VariableAllocator(private val st: SymbolTable, private val program: PtProg
|
||||
DataType.FLOAT -> (variable.initialNumericValue ?: 0.0).toString()
|
||||
in NumericDatatypes -> (variable.initialNumericValue ?: 0).toHex()
|
||||
DataType.STR -> {
|
||||
val encoded = program.encoding.encodeString(variable.initialStringValue!!.first, variable.initialStringValue!!.second)
|
||||
encoded.joinToString(",") { it.toInt().toHex() } + ",0"
|
||||
val encoded = program.encoding.encodeString(variable.initialStringValue!!.first, variable.initialStringValue!!.second) + listOf(0u)
|
||||
encoded.joinToString(",") { it.toInt().toHex() }
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
if(variable.initialArrayValue!=null) {
|
||||
@@ -81,6 +81,25 @@ class VariableAllocator(private val st: SymbolTable, private val program: PtProg
|
||||
}
|
||||
mm.add(Pair(variable.scopedName, "$location $typeStr $value"))
|
||||
}
|
||||
for (variable in st.allMemMappedVariables) {
|
||||
val location = allocations.getValue(variable.scopedName)
|
||||
val typeStr = when(variable.dt) {
|
||||
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
|
||||
DataType.BYTE, DataType.ARRAY_B -> "byte"
|
||||
DataType.UWORD, DataType.ARRAY_UW -> "uword"
|
||||
DataType.WORD, DataType.ARRAY_W -> "word"
|
||||
DataType.FLOAT, DataType.ARRAY_F -> "float"
|
||||
else -> throw InternalCompilerException("weird dt")
|
||||
}
|
||||
val value = when(variable.dt) {
|
||||
DataType.FLOAT -> "0.0"
|
||||
in NumericDatatypes -> "0"
|
||||
DataType.ARRAY_F -> (1..variable.length!!).joinToString(",") { "0.0" }
|
||||
in ArrayDatatypes -> (1..variable.length!!).joinToString(",") { "0" }
|
||||
else -> throw InternalCompilerException("weird dt for mem mapped var")
|
||||
}
|
||||
mm.add(Pair(variable.scopedName, "$location $typeStr $value"))
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@ import prog8.vm.Instruction
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
|
||||
internal class VmOptimizerException(msg: String): Exception(msg)
|
||||
|
||||
|
||||
class VmPeepholeOptimizer(private val vmprog: AssemblyProgram, private val allocations: VariableAllocator) {
|
||||
class VmPeepholeOptimizer(private val vmprog: AssemblyProgram) {
|
||||
fun optimize() {
|
||||
vmprog.getBlocks().forEach { block ->
|
||||
do {
|
||||
@@ -172,6 +170,7 @@ class VmPeepholeOptimizer(private val vmprog: AssemblyProgram, private val alloc
|
||||
// TODO: detect multiple sequential rnd with same reg1, only keep one
|
||||
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
||||
// TODO: detect multiple same ands, ors; only keep first
|
||||
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
|
||||
// ...
|
||||
}
|
||||
return changed
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package prog8tests.vm.helpers
|
||||
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.IMemSizer
|
||||
import prog8.code.core.IStringEncoding
|
||||
|
||||
|
||||
internal object DummyMemsizer : IMemSizer {
|
||||
override fun memorySize(dt: DataType) = 0
|
||||
override fun memorySize(arrayDt: DataType, numElements: Int) = 0
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import prog8.code.ast.PtProgram
|
||||
import prog8.codegen.virtual.*
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.vm.helpers.DummyMemsizer
|
||||
import prog8tests.vm.helpers.DummyStringEncoder
|
||||
|
||||
class TestVmPeepholeOpt: FunSpec({
|
||||
fun makeVmProgram(lines: List<VmCodeLine>): Pair<AssemblyProgram, VariableAllocator> {
|
||||
@@ -26,19 +26,19 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
fun AssemblyProgram.lines(): List<VmCodeLine> = this.getBlocks().flatMap { it.lines }
|
||||
|
||||
test("remove nops") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("dummy")),
|
||||
VmCodeInstruction(Opcode.NOP),
|
||||
VmCodeInstruction(Opcode.NOP)
|
||||
))
|
||||
asm.lines().size shouldBe 3
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
asm.lines().size shouldBe 1
|
||||
}
|
||||
|
||||
test("remove jmp to label below") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label")), // removed
|
||||
VmCodeLabel(listOf("label")),
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label2")), // removed
|
||||
@@ -49,7 +49,7 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
VmCodeLabel(listOf("label3"))
|
||||
))
|
||||
asm.lines().size shouldBe 8
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 5
|
||||
@@ -61,7 +61,7 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("remove double sec/clc") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.SEC),
|
||||
VmCodeInstruction(Opcode.SEC),
|
||||
VmCodeInstruction(Opcode.SEC),
|
||||
@@ -70,7 +70,7 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
VmCodeInstruction(Opcode.CLC)
|
||||
))
|
||||
asm.lines().size shouldBe 6
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 1
|
||||
@@ -78,14 +78,14 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("push followed by pop") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
|
||||
VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
|
||||
VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
|
||||
VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
|
||||
))
|
||||
asm.lines().size shouldBe 4
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 1
|
||||
@@ -95,7 +95,7 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("remove useless div/mul, add/sub") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
|
||||
@@ -108,19 +108,19 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
VmCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
|
||||
))
|
||||
asm.lines().size shouldBe 10
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace add/sub 1 by inc/dec") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
asm.lines().size shouldBe 2
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 2
|
||||
@@ -129,7 +129,7 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("remove useless and/or/xor") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
|
||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
|
||||
@@ -140,21 +140,21 @@ class TestVmPeepholeOpt: FunSpec({
|
||||
VmCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
asm.lines().size shouldBe 8
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace and/or/xor by constant number") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
val(asm, _) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
|
||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
|
||||
VmCodeInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
|
||||
))
|
||||
asm.lines().size shouldBe 4
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
val opt = VmPeepholeOptimizer(asm)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 4
|
||||
@@ -1,15 +1,18 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.UndefinedSymbolError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.maySwapOperandOrder
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AssociativeOperators
|
||||
import prog8.code.core.DataType
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
|
||||
|
||||
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
@@ -6,20 +6,18 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.maySwapOperandOrder
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.Jump
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
|
||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
class ExpressionSimplifier(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
@@ -30,8 +28,9 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val literal = typecast.expression as? NumericLiteral
|
||||
if (literal != null) {
|
||||
val newLiteral = literal.cast(typecast.type)
|
||||
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal)
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral.valueOrZero(), typecast)
|
||||
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
|
||||
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
|
||||
}
|
||||
}
|
||||
|
||||
// remove redundant nested typecasts
|
||||
@@ -73,6 +72,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(compTarget.name!=VMTarget.NAME) {
|
||||
val booleanCondition = ifElse.condition as? BinaryExpression
|
||||
if(booleanCondition!=null && booleanCondition.operator=="&") {
|
||||
// special optimization of WORD & $ff00 -> just and the msb of WORD with $ff
|
||||
val rightNum = booleanCondition.right as? NumericLiteral
|
||||
if(rightNum!=null && rightNum.type==DataType.UWORD && (rightNum.number.toInt() and 0x00ff)==0) {
|
||||
val msb = BuiltinFunctionCall(IdentifierReference(listOf("msb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
|
||||
val bytevalue = NumericLiteral(DataType.UBYTE, (rightNum.number.toInt() shr 8).toDouble(), booleanCondition.right.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(booleanCondition.left, msb, booleanCondition),
|
||||
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -333,6 +348,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallExpr.target.nameInSource == listOf("mkword")) {
|
||||
if(functionCallExpr.args[0].constValue(program)?.number==0.0) {
|
||||
// just cast the lsb to uword
|
||||
val cast = TypecastExpression(functionCallExpr.args[1], DataType.UWORD, true, functionCallExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -468,19 +491,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
in powersOfTwo -> {
|
||||
if (leftDt in IntegerDatatypes) {
|
||||
// divided by a power of two => shift right
|
||||
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
|
||||
// Unsigned number divided by a power of two => shift right
|
||||
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values),
|
||||
// so we leave that as is and let the code generator deal with it.
|
||||
val numshifts = log2(cv).toInt()
|
||||
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
in negativePowersOfTwo -> {
|
||||
if (leftDt in IntegerDatatypes) {
|
||||
// divided by a negative power of two => negate, then shift right
|
||||
val numshifts = log2(-cv).toInt()
|
||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (leftDt == DataType.UBYTE) {
|
||||
@@ -538,9 +556,10 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
in negativePowersOfTwo -> {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a negative power of two => negate, then shift left
|
||||
// times a negative power of two => negate, then shift
|
||||
val numshifts = log2(-cv).toInt()
|
||||
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
val negation = PrefixExpression("-", expr2.left, expr.position)
|
||||
return BinaryExpression(negation, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -570,7 +589,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||
} else if (amount > 8) {
|
||||
}
|
||||
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)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(DataType.UBYTE, 0.0, expr.position)), 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)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
||||
@@ -609,12 +635,19 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
else if(amount==8) {
|
||||
// shift right by 8 bits is just a byte operation: msb(X) as uword
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
|
||||
}
|
||||
else if (amount > 8) {
|
||||
// same as above but with residual shifts.
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// bit-shifting a signed value shouldn't be allowed by the compiler but here we go...
|
||||
if (amount > 16) {
|
||||
expr.right = NumericLiteral.optimalInteger(16, expr.right.position)
|
||||
return null
|
||||
|
||||
@@ -60,8 +60,8 @@ fun Program.inlineSubroutines(): Int {
|
||||
return inliner.applyModifications()
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions() : Int {
|
||||
val opti = ExpressionSimplifier(this)
|
||||
fun Program.simplifyExpressions(target: ICompilationTarget) : Int {
|
||||
val opti = ExpressionSimplifier(this, target)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class Inliner(val program: Program): AstWalker() {
|
||||
} else
|
||||
false
|
||||
}
|
||||
is Jump, is GoSub -> true
|
||||
is Jump -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -170,15 +170,6 @@ class Inliner(val program: Program): AstWalker() {
|
||||
return super.before(program)
|
||||
}
|
||||
|
||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
val sub = gosub.identifier.targetStatement(program) as? Subroutine
|
||||
return if(sub==null)
|
||||
noModifications
|
||||
else
|
||||
possibleInlineFcallStmt(sub, gosub, parent)
|
||||
|
||||
}
|
||||
|
||||
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])))
|
||||
|
||||
@@ -73,31 +73,6 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
if(compTarget.name!=VMTarget.NAME) {
|
||||
// see if we can optimize any complex argument expressions to be just a simple variable
|
||||
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
|
||||
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
|
||||
val arg = functionCallStatement.args[0]
|
||||
if(!arg.isSimple && arg !is IFunctionCall) {
|
||||
val name = getTempRegisterName(arg.inferType(program))
|
||||
val tempvar = IdentifierReference(name, functionCallStatement.position)
|
||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -244,25 +219,6 @@ class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
// NOTE: do NOT remove a jump to the next statement, because this will lead to wrong code when this occurs at the end of a subroutine
|
||||
// if we want to optimize this away, it can be done later at code generation time.
|
||||
|
||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
|
||||
val subroutineParams = gosub.identifier.targetSubroutine(program)?.parameters
|
||||
if(subroutineParams!=null && subroutineParams.isEmpty()) {
|
||||
val returnstmt = gosub.nextSibling() as? Return
|
||||
if(returnstmt!=null && returnstmt.value==null) {
|
||||
return listOf(
|
||||
IAstModification.Remove(returnstmt, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(gosub, Jump(null, gosub.identifier, null, gosub.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
@@ -429,31 +385,22 @@ class StatementOptimizer(private val program: Program,
|
||||
if(compTarget.name==VMTarget.NAME)
|
||||
return noModifications
|
||||
|
||||
fun returnViaIntermediaryVar(value: Expression): Iterable<IAstModification>? {
|
||||
val subr = returnStmt.definingSubroutine!!
|
||||
val returnDt = subr.returntypes.single()
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary variable, then return that
|
||||
val (returnVarName, _) = program.getTempVar(returnDt)
|
||||
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, 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)
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// TODO decision when to use intermediary variable to calculate returnvalue seems a bit arbitrary...
|
||||
val returnvalue = returnStmt.value
|
||||
if (returnvalue!=null) {
|
||||
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
|
||||
val mod = returnViaIntermediaryVar(returnvalue)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ dependencies {
|
||||
implementation project(':codeGenCpu6502')
|
||||
implementation project(':codeGenVirtual')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
@@ -69,7 +68,6 @@ sourceSets {
|
||||
test {
|
||||
java {
|
||||
srcDir "${project.projectDir}/test"
|
||||
srcDir "${project(':compilerAst').projectDir}/test/helpers"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,5 @@
|
||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||
<orderEntry type="module" module-name="codeGenVirtual" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -315,7 +315,7 @@ hline_zero2
|
||||
; for efficiency of internal algorithms here is the internal plot routine
|
||||
; that takes the plotx coordinate in a separate variable instead of the XY register pair:
|
||||
|
||||
uword internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
||||
uword @zp internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
||||
|
||||
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||
%asm {{
|
||||
|
||||
@@ -416,6 +416,9 @@ _loop
|
||||
beq _stop
|
||||
cmp #7 ; screencode letters A-F are 1-6
|
||||
bcc _add_letter
|
||||
and #127
|
||||
cmp #97
|
||||
bcs _try_iso ; maybe letter is iso:'a'-iso:'f' (97-102)
|
||||
cmp #'g'
|
||||
bcs _stop
|
||||
cmp #'a'
|
||||
@@ -451,6 +454,11 @@ _add_letter
|
||||
sta P8ZP_SCRATCH_B1
|
||||
pla
|
||||
jmp _calc
|
||||
_try_iso
|
||||
cmp #103
|
||||
bcs _stop
|
||||
and #63
|
||||
bne _add_letter
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,20 @@
|
||||
%import textio
|
||||
|
||||
; Bitmap pixel graphics module for the CommanderX16
|
||||
; wraps the graphics functions that are in ROM.
|
||||
; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
|
||||
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
|
||||
; Wraps the graphics functions that are in ROM.
|
||||
; Only lo-res 320x240 256 color mode for now.
|
||||
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
|
||||
; For other resolutions or other color modes, use the "gfx2" module instead. (which is Cx16-specific)
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
|
||||
|
||||
graphics {
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 200
|
||||
const ubyte HEIGHT = 240
|
||||
|
||||
|
||||
ubyte stroke_color = 1
|
||||
ubyte background_color = 0
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
@@ -27,10 +32,18 @@ graphics {
|
||||
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
stroke_color = pixelcolor
|
||||
background_color = bgcolor
|
||||
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
|
||||
cx16.GRAPH_clear()
|
||||
}
|
||||
|
||||
sub colors(ubyte stroke, ubyte fill) {
|
||||
; this routine is only available on the cx16, other targets can't change colors on the fly
|
||||
cx16.GRAPH_set_colors(stroke, fill, background_color)
|
||||
stroke_color = stroke
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
cx16.GRAPH_draw_line(x1, y1, x2, y2)
|
||||
}
|
||||
@@ -71,31 +84,31 @@ graphics {
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter + yy
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter + xx
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
yy++
|
||||
if decisionOver2<=0 {
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
|
||||
@@ -90,6 +90,10 @@ psg {
|
||||
; cx16.r1L = the voice number
|
||||
; cx16.r2L = attack value
|
||||
|
||||
pushw(cx16.r0)
|
||||
push(cx16.r1L)
|
||||
push(cx16.r2L)
|
||||
pushw(cx16.r9)
|
||||
; calculate new volumes
|
||||
for cx16.r1L in 0 to 15 {
|
||||
when envelope_states[cx16.r1L] {
|
||||
@@ -138,6 +142,10 @@ psg {
|
||||
cx16.VERA_DATA0 = cx16.VERA_DATA1 & %11000000 | msb(envelope_volumes[cx16.r1L])
|
||||
}
|
||||
cx16.pop_vera_context()
|
||||
popw(cx16.r9)
|
||||
pop(cx16.r2L)
|
||||
pop(cx16.r1L)
|
||||
popw(cx16.r0)
|
||||
}
|
||||
|
||||
ubyte[16] envelope_states
|
||||
|
||||
@@ -98,7 +98,7 @@ cx16 {
|
||||
&uword ISTOP = $0328
|
||||
&uword IGETIN = $032a
|
||||
&uword ICLALL = $032c
|
||||
&uword KEYHDL = $032e ; keyboard scan code handler
|
||||
&uword KEYHDL = $032e ; keyboard scan code handler see examples/cx16/keyboardhandler.p8
|
||||
&uword ILOAD = $0330
|
||||
&uword ISAVE = $0332
|
||||
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||
@@ -306,7 +306,7 @@ romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, ubyte getCurrent @Pc) clobbers(A, X, Y) -> ubyte @Pc
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff6e = jsrfar()
|
||||
romsub $ff6e = jsrfar() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
@@ -735,49 +735,53 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push_vera_context() clobbers(A) {
|
||||
asmsub push_vera_context() clobbers(A) {
|
||||
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
|
||||
%asm {{
|
||||
; note cannot store this on cpu hardware stack because this gets called as a subroutine
|
||||
lda cx16.VERA_ADDR_L
|
||||
pha
|
||||
sta _vera_storage
|
||||
lda cx16.VERA_ADDR_M
|
||||
pha
|
||||
sta _vera_storage+1
|
||||
lda cx16.VERA_ADDR_H
|
||||
pha
|
||||
sta _vera_storage+2
|
||||
lda cx16.VERA_CTRL
|
||||
pha
|
||||
sta _vera_storage+3
|
||||
eor #1
|
||||
sta cx16.VERA_CTRL
|
||||
lda cx16.VERA_ADDR_L
|
||||
pha
|
||||
sta _vera_storage+4
|
||||
lda cx16.VERA_ADDR_M
|
||||
pha
|
||||
sta _vera_storage+5
|
||||
lda cx16.VERA_ADDR_H
|
||||
pha
|
||||
sta _vera_storage+6
|
||||
lda cx16.VERA_CTRL
|
||||
pha
|
||||
sta _vera_storage+7
|
||||
rts
|
||||
_vera_storage: .byte 0,0,0,0,0,0,0,0
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop_vera_context() clobbers(A) {
|
||||
asmsub pop_vera_context() clobbers(A) {
|
||||
; -- use this at the end of your IRQ handler if it uses Vera registers, to restore the state
|
||||
%asm {{
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+7
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+6
|
||||
sta cx16.VERA_ADDR_H
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+5
|
||||
sta cx16.VERA_ADDR_M
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+4
|
||||
sta cx16.VERA_ADDR_L
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+3
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+2
|
||||
sta cx16.VERA_ADDR_H
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
pla
|
||||
lda cx16.push_vera_context._vera_storage+0
|
||||
sta cx16.VERA_ADDR_L
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -864,7 +868,9 @@ sys {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
stz $01 ; bank the kernal in
|
||||
stz $01 ; bank the kernal in
|
||||
lda #$80
|
||||
sta cx16.VERA_CTRL ; reset Vera (kernal doesn't do this?)
|
||||
jmp (cx16.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ diskio {
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(13, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void c64.OPEN() ; open 13,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
@@ -23,8 +24,13 @@ diskio {
|
||||
}
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
ubyte status = c64.READST()
|
||||
while not status {
|
||||
status = c64.READST()
|
||||
if status!=0 {
|
||||
status = 1
|
||||
goto io_error
|
||||
}
|
||||
|
||||
while status==0 {
|
||||
ubyte low = c64.CHRIN()
|
||||
ubyte high = c64.CHRIN()
|
||||
txt.print_uw(mkword(high, low))
|
||||
@@ -43,9 +49,9 @@ diskio {
|
||||
if c64.STOP2()
|
||||
break
|
||||
}
|
||||
status = c64.READST()
|
||||
|
||||
io_error:
|
||||
status = c64.READST()
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(13)
|
||||
|
||||
@@ -59,6 +65,42 @@ io_error:
|
||||
return true
|
||||
}
|
||||
|
||||
sub diskname(ubyte drivenumber) -> uword {
|
||||
; -- Returns pointer to disk name string or 0 if failure.
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(13, drivenumber, 0)
|
||||
ubyte okay = false
|
||||
void c64.OPEN() ; open 13,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(13) ; use #13 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
repeat 6 {
|
||||
void c64.CHRIN() ; skip the 6 prologue bytes
|
||||
}
|
||||
if c64.READST()!=0
|
||||
goto io_error
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
cx16.r0 = &list_filename
|
||||
repeat {
|
||||
@(cx16.r0) = c64.CHRIN()
|
||||
if @(cx16.r0)==0
|
||||
break
|
||||
cx16.r0++
|
||||
}
|
||||
okay = true
|
||||
|
||||
io_error:
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(13)
|
||||
if okay
|
||||
return &list_filename
|
||||
return 0
|
||||
}
|
||||
|
||||
; internal variables for the iterative file lister / loader
|
||||
bool list_skip_disk_name
|
||||
@@ -67,26 +109,25 @@ io_error:
|
||||
bool iteration_in_progress = false
|
||||
ubyte @zp first_byte
|
||||
bool have_first_byte
|
||||
str list_filename = "?" * 32
|
||||
|
||||
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.
|
||||
const uword names_buf_size = 800
|
||||
uword names_buffer = memory("filenames", names_buf_size, 0)
|
||||
uword buffer_start = names_buffer
|
||||
; -- 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)
|
||||
uword buffer_start = filenames_buffer
|
||||
ubyte files_found = 0
|
||||
if lf_start_list(drivenumber, pattern_ptr) {
|
||||
while lf_next_entry() {
|
||||
@(name_ptrs) = lsb(names_buffer)
|
||||
@(name_ptrs) = lsb(filenames_buffer)
|
||||
name_ptrs++
|
||||
@(name_ptrs) = msb(names_buffer)
|
||||
@(name_ptrs) = msb(filenames_buffer)
|
||||
name_ptrs++
|
||||
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
|
||||
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
||||
files_found++
|
||||
if names_buffer - buffer_start > names_buf_size-18
|
||||
if filenames_buffer - buffer_start > filenames_buf_size-18
|
||||
break
|
||||
if files_found == max_names
|
||||
break
|
||||
@@ -208,14 +249,16 @@ close_end:
|
||||
c64.SETLFS(11, drivenumber, 0)
|
||||
void c64.OPEN() ; open 11,8,0,"filename"
|
||||
if_cc {
|
||||
iteration_in_progress = true
|
||||
have_first_byte = false
|
||||
void c64.CHKIN(11) ; use #11 as input channel
|
||||
if_cc {
|
||||
first_byte = c64.CHRIN() ; read first byte to test for file not found
|
||||
if not c64.READST() {
|
||||
have_first_byte = true
|
||||
return true
|
||||
if c64.READST()==0 {
|
||||
iteration_in_progress = true
|
||||
have_first_byte = false
|
||||
void c64.CHKIN(11) ; use #11 as input channel
|
||||
if_cc {
|
||||
first_byte = c64.CHRIN() ; read first byte to test for file not found
|
||||
if not c64.READST() {
|
||||
have_first_byte = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,8 +425,8 @@ _end rts
|
||||
|
||||
sub status(ubyte drivenumber) -> uword {
|
||||
; -- retrieve the disk drive's current status message
|
||||
uword messageptr = &filename
|
||||
c64.SETNAM(0, filename)
|
||||
uword messageptr = &list_filename
|
||||
c64.SETNAM(0, list_filename)
|
||||
c64.SETLFS(15, drivenumber, 15)
|
||||
void c64.OPEN() ; open 15,8,15
|
||||
if_cs
|
||||
@@ -393,18 +436,21 @@ _end rts
|
||||
goto io_error
|
||||
|
||||
while not c64.READST() {
|
||||
@(messageptr) = c64.CHRIN()
|
||||
first_byte = c64.CHRIN()
|
||||
if first_byte=='\r' or first_byte=='\n'
|
||||
break
|
||||
@(messageptr) = first_byte
|
||||
messageptr++
|
||||
}
|
||||
|
||||
@(messageptr) = 0
|
||||
|
||||
done:
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(15)
|
||||
return filename
|
||||
return list_filename
|
||||
|
||||
io_error:
|
||||
filename = "?disk error"
|
||||
list_filename = "?disk error"
|
||||
goto done
|
||||
}
|
||||
|
||||
@@ -506,10 +552,10 @@ io_error:
|
||||
|
||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
filename[0] = 's'
|
||||
filename[1] = ':'
|
||||
ubyte flen = string.copy(filenameptr, &filename+2)
|
||||
c64.SETNAM(flen+2, filename)
|
||||
list_filename[0] = 's'
|
||||
list_filename[1] = ':'
|
||||
ubyte flen = string.copy(filenameptr, &list_filename+2)
|
||||
c64.SETNAM(flen+2, list_filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLRCHN()
|
||||
@@ -518,17 +564,24 @@ io_error:
|
||||
|
||||
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
||||
; -- rename a file on the drive
|
||||
filename[0] = 'r'
|
||||
filename[1] = ':'
|
||||
ubyte flen_new = string.copy(newfileptr, &filename+2)
|
||||
filename[flen_new+2] = '='
|
||||
ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
|
||||
c64.SETNAM(3+flen_new+flen_old, filename)
|
||||
list_filename[0] = 'r'
|
||||
list_filename[1] = ':'
|
||||
ubyte flen_new = string.copy(newfileptr, &list_filename+2)
|
||||
list_filename[flen_new+2] = '='
|
||||
ubyte flen_old = string.copy(oldfileptr, &list_filename+3+flen_new)
|
||||
c64.SETNAM(3+flen_new+flen_old, list_filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
}
|
||||
|
||||
str filename = "0:??????????????????????????????????????"
|
||||
sub send_command(ubyte drivenumber, uword commandptr) {
|
||||
; -- send a dos command to the drive
|
||||
c64.SETNAM(string.length(commandptr), commandptr)
|
||||
c64.SETLFS(15, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(15)
|
||||
}
|
||||
}
|
||||
@@ -665,13 +665,10 @@ greaterequalzero_sb .proc
|
||||
|
||||
greaterequalzero_sw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._false
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
bpl equalzero_b._true
|
||||
bmi equalzero_b._false
|
||||
.pend
|
||||
|
||||
|
||||
memcopy16_up .proc
|
||||
; -- copy memory UP from (P8ZP_SCRATCH_W1) to (P8ZP_SCRATCH_W2) of length X/Y (16-bit, X=lo, Y=hi)
|
||||
; clobbers register A,X,Y
|
||||
|
||||
@@ -129,7 +129,7 @@ _startloop dey
|
||||
|
||||
asmsub find(uword string @R0, ubyte character @A) -> ubyte @A, ubyte @Pc {
|
||||
; Locates the first position of the given character in the string,
|
||||
; returns Carry set if found + index in A, or Carry clear if not found.
|
||||
; returns Carry set if found + index in A, or A=0 + Carry clear if not found.
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||
sta P8ZP_SCRATCH_B1
|
||||
@@ -144,7 +144,8 @@ _startloop dey
|
||||
beq _found
|
||||
iny
|
||||
bne -
|
||||
_notfound clc
|
||||
_notfound lda #0
|
||||
clc
|
||||
rts
|
||||
_found tya
|
||||
sec
|
||||
@@ -223,6 +224,26 @@ _done rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub startswith(str st, str prefix) -> bool {
|
||||
ubyte prefix_len = length(prefix)
|
||||
ubyte str_len = length(st)
|
||||
if prefix_len > str_len
|
||||
return false
|
||||
cx16.r9L = st[prefix_len]
|
||||
st[prefix_len] = 0
|
||||
cx16.r9H = compare(st, prefix) as ubyte
|
||||
st[prefix_len] = cx16.r9L
|
||||
return cx16.r9H==0
|
||||
}
|
||||
|
||||
sub endswith(str st, str suffix) -> bool {
|
||||
ubyte suffix_len = length(suffix)
|
||||
ubyte str_len = length(st)
|
||||
if suffix_len > str_len
|
||||
return false
|
||||
return compare(st + str_len - suffix_len, suffix) == 0
|
||||
}
|
||||
|
||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
; pattern matching of a string.
|
||||
@@ -293,5 +314,4 @@ fail clc ; yes, no match found, return with c=0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -251,4 +251,13 @@ sub bin2uword(str string) -> uword {
|
||||
}
|
||||
}
|
||||
|
||||
sub any2uword(str string) -> uword {
|
||||
; -- convert any number string (any prefix allowed) to uword.
|
||||
when string[0] {
|
||||
'$' -> return hex2uword(string)
|
||||
'%' -> return bin2uword(string)
|
||||
else -> return str2uword(string)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -84,7 +84,7 @@ sub log2(float value) -> float {
|
||||
sub sqrt(float value) -> float {
|
||||
%asm {{
|
||||
loadm.f fr0,{floats.sqrt.value}
|
||||
fsqrt.f fr0,fr0
|
||||
sqrt.f fr0,fr0
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -113,4 +113,24 @@ string {
|
||||
ix++
|
||||
}
|
||||
}
|
||||
|
||||
sub startswith(str st, str prefix) -> bool {
|
||||
ubyte prefix_len = length(prefix)
|
||||
ubyte str_len = length(st)
|
||||
if prefix_len > str_len
|
||||
return false
|
||||
cx16.r9L = st[prefix_len]
|
||||
st[prefix_len] = 0
|
||||
cx16.r9H = compare(st, prefix) as ubyte
|
||||
st[prefix_len] = cx16.r9L
|
||||
return cx16.r9H==0
|
||||
}
|
||||
|
||||
sub endswith(str st, str suffix) -> bool {
|
||||
ubyte suffix_len = length(suffix)
|
||||
ubyte str_len = length(st)
|
||||
if suffix_len > str_len
|
||||
return false
|
||||
return compare(st + str_len - suffix_len, suffix) == 0
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
8.3
|
||||
8.4
|
||||
@@ -10,10 +10,7 @@ import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardWatchEventKinds
|
||||
import java.nio.file.*
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -148,16 +145,26 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
|
||||
fun determineRecompilationNeeded(event: WatchKey): Boolean {
|
||||
if(event.isValid) {
|
||||
for (changed in event.pollEvents()) {
|
||||
if (changed.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
|
||||
val changedPath = changed.context() as Path
|
||||
if (allImportedFiles.any { it.fileName == changedPath.fileName }) {
|
||||
println(" change detected: $changedPath")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var recompile=false
|
||||
while(!recompile) {
|
||||
val event = watchservice.take()
|
||||
for (changed in event.pollEvents()) {
|
||||
val changedPath = changed.context() as Path
|
||||
if(allImportedFiles.any { it.fileName == changedPath.fileName }) {
|
||||
println(" change detected: $changedPath")
|
||||
recompile = true
|
||||
}
|
||||
}
|
||||
Thread.sleep(50) // avoid multiple events on same file
|
||||
recompile = determineRecompilationNeeded(event)
|
||||
event.reset()
|
||||
}
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
println("Analyzing code...")
|
||||
program.preprocessAst(errors, compilerOptions.compTarget)
|
||||
program.preprocessAst(errors, compilerOptions)
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
|
||||
@@ -358,7 +358,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
remover.applyModifications()
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = program.simplifyExpressions()
|
||||
val optsDone1 = program.simplifyExpressions(compTarget)
|
||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||
val optsDone4 = program.inlineSubroutines()
|
||||
|
||||
@@ -6,11 +6,12 @@ import com.github.michaelbull.result.getOrElse
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.determineGosubArguments
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
import java.io.File
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
@@ -43,7 +44,6 @@ class IntermediateAstMaker(val program: Program) {
|
||||
is Directive -> transform(statement)
|
||||
is ForLoop -> transform(statement)
|
||||
is FunctionCallStatement -> transform(statement)
|
||||
is GoSub -> transform(statement)
|
||||
is IfElse -> transform(statement)
|
||||
is InlineAssembly -> transform(statement)
|
||||
is Jump -> transform(statement)
|
||||
@@ -85,12 +85,6 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcAssign: Assignment): PtNode {
|
||||
if(srcAssign.origin==AssignmentOrigin.PARAMETERASSIGN) {
|
||||
// assignments that are setting the parameters for a function call,
|
||||
// will be gathered at the GoSub itself later.
|
||||
return PtNop(srcAssign.position)
|
||||
}
|
||||
|
||||
val assign = PtAssignment(srcAssign.position)
|
||||
assign.add(transform(srcAssign.target))
|
||||
assign.add(transformExpression(srcAssign.value))
|
||||
@@ -229,28 +223,6 @@ class IntermediateAstMaker(val program: Program) {
|
||||
return call
|
||||
}
|
||||
|
||||
private fun transform(gosub: GoSub): PtFunctionCall {
|
||||
// Gather the Goto and any preceding parameter assignments back into a single Function call node.
|
||||
// (the reason it was split up in the first place, is because the Compiler Ast optimizers
|
||||
// can then work on any complex expressions that are used as arguments.)
|
||||
val arguments = determineGosubArguments(gosub)
|
||||
|
||||
val parameters = gosub.identifier.targetSubroutine(program)!!.parameters
|
||||
if(arguments.size != parameters.size)
|
||||
throw FatalAstException("mismatched number of parameter assignments for function call")
|
||||
|
||||
val target = transform(gosub.identifier)
|
||||
val call = PtFunctionCall(target.targetName, true, DataType.UNDEFINED, gosub.position)
|
||||
|
||||
// put arguments in correct order for the parameters
|
||||
parameters.forEach {
|
||||
val argument = arguments.getValue(it.name)
|
||||
call.add(transformExpression(argument))
|
||||
}
|
||||
|
||||
return call
|
||||
}
|
||||
|
||||
private fun transform(srcIf: IfElse): PtIfElse {
|
||||
val ifelse = PtIfElse(srcIf.position)
|
||||
ifelse.add(transformExpression(srcIf.condition))
|
||||
@@ -313,6 +285,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
srcSub.asmAddress,
|
||||
srcSub.asmClobbers,
|
||||
params,
|
||||
srcSub.returntypes,
|
||||
srcSub.asmReturnvaluesRegisters,
|
||||
srcSub.inline,
|
||||
srcSub.position)
|
||||
|
||||
@@ -202,16 +202,6 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(gosub: GoSub) {
|
||||
val targetStatement = checkFunctionOrLabelExists(gosub.identifier, gosub)
|
||||
if(targetStatement!=null) {
|
||||
if(targetStatement is BuiltinFunctionPlaceholder)
|
||||
errors.err("can't gosub to a builtin function", gosub.position)
|
||||
}
|
||||
|
||||
super.visit(gosub)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
val addr = block.address
|
||||
if(addr!=null && addr>65535u) {
|
||||
@@ -313,7 +303,7 @@ internal class AstChecker(private val program: Program,
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or the assembler equivalent in case of %asm)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,8 +317,8 @@ internal class AstChecker(private val program: Program,
|
||||
err("number of return registers is not the isSameAs as number of return values")
|
||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||
if(param.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
|
||||
err("parameter '${param.first.name}' should be (u)byte")
|
||||
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE && param.first.type != DataType.BOOL)
|
||||
err("parameter '${param.first.name}' should be (u)byte or bool")
|
||||
}
|
||||
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
||||
@@ -342,17 +332,17 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
||||
if(pair.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
|
||||
err("return value #${index + 1} should be (u)byte")
|
||||
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE && pair.first != DataType.BOOL)
|
||||
err("return type #${index + 1} should be (u)byte")
|
||||
}
|
||||
else if(pair.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
||||
err("return value #${index + 1} should be (u)word/address")
|
||||
err("return type #${index + 1} should be (u)word/address")
|
||||
}
|
||||
else if(pair.second.statusflag!=null) {
|
||||
if (pair.first != DataType.UBYTE)
|
||||
err("return value #${index + 1} should be ubyte")
|
||||
err("return type #${index + 1} should be ubyte")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,8 +789,11 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
fun isPassByReferenceElement(e: Expression): Boolean {
|
||||
if(e is IdentifierReference) {
|
||||
val decl = e.targetVarDecl(program)!!
|
||||
return decl.datatype in PassByReferenceDatatypes
|
||||
val decl = e.targetVarDecl(program)
|
||||
return if(decl!=null)
|
||||
decl.datatype in PassByReferenceDatatypes
|
||||
else
|
||||
true // is probably a symbol that needs addr-of
|
||||
}
|
||||
return e is StringLiteral
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import prog8.ast.expressions.CharLiteral
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
@@ -92,8 +94,8 @@ internal fun Program.verifyFunctionArgTypes(errors: IErrorReporter) {
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.preprocessAst(errors: IErrorReporter, target: ICompilationTarget) {
|
||||
val transforms = AstPreprocessor(this, errors, target)
|
||||
internal fun Program.preprocessAst(errors: IErrorReporter, options: CompilationOptions) {
|
||||
val transforms = AstPreprocessor(this, errors, options)
|
||||
transforms.visit(this)
|
||||
var mods = transforms.applyModifications()
|
||||
while(mods>0)
|
||||
@@ -166,3 +168,17 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
|
||||
val instructions =
|
||||
if(compTarget.name == VMTarget.NAME)
|
||||
listOf(" return", "\treturn", " jump", "\tjump", " jumpi", "\tjumpi")
|
||||
else
|
||||
listOf(" rti", "\trti", " rts", "\trts", " jmp", "\tjmp", " bra", "\tbra")
|
||||
return statements
|
||||
.asSequence()
|
||||
.filterIsInstance<InlineAssembly>()
|
||||
.any {
|
||||
instructions.any { instr->instr in it.assembly }
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
||||
}
|
||||
|
||||
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(options.compTarget.name== VMTarget.NAME)
|
||||
if(options.compTarget.name==VMTarget.NAME)
|
||||
return noModifications // vm codegen deals correctly with all cases
|
||||
|
||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||
|
||||
@@ -8,39 +8,52 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
|
||||
|
||||
class AstPreprocessor(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
|
||||
class AstPreprocessor(val program: Program,
|
||||
val errors: IErrorReporter,
|
||||
val options: CompilationOptions) : AstWalker() {
|
||||
|
||||
override fun before(program: Program): Iterable<IAstModification> {
|
||||
if(compTarget.name!=Cx16Target.NAME) {
|
||||
// reset the address of the virtual registers to be inside the evaluation stack.
|
||||
// (we don't do this on CommanderX16 itself as the registers have a fixed location in Zeropage there)
|
||||
val cx16block = program.allBlocks.single { it.name=="cx16" }
|
||||
val memVars = cx16block.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.associateBy { it.name }
|
||||
|
||||
val estack = compTarget.machine.ESTACK_HI
|
||||
for(regnum in 0u..15u) {
|
||||
val rX = memVars.getValue("r$regnum")
|
||||
val rXL = memVars.getValue("r${regnum}L")
|
||||
val rXH = memVars.getValue("r${regnum}H")
|
||||
val rXs = memVars.getValue("r${regnum}s")
|
||||
val rXsL = memVars.getValue("r${regnum}sL")
|
||||
val rXsH = memVars.getValue("r${regnum}sH")
|
||||
setAddress(rX, estack + 2u*regnum)
|
||||
setAddress(rXL, estack + 2u*regnum)
|
||||
setAddress(rXH, estack + 2u*regnum +1u)
|
||||
setAddress(rXs, estack + 2u*regnum)
|
||||
setAddress(rXsL, estack + 2u*regnum)
|
||||
setAddress(rXsH, estack + 2u*regnum + 1u)
|
||||
if(options.compTarget.name==C64Target.NAME) {
|
||||
if(options.zeropage==ZeropageType.KERNALSAFE || options.zeropage==ZeropageType.FULL) {
|
||||
// there is enough space in the zero page to put the cx16 virtual registers there.
|
||||
// unfortunately, can't be the same address as CommanderX16.
|
||||
relocateCx16VirtualRegisters(program, 0x0004u)
|
||||
}
|
||||
}
|
||||
else if(options.compTarget.name!=Cx16Target.NAME) {
|
||||
relocateCx16VirtualRegisters(program, options.compTarget.machine.ESTACK_HI)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun relocateCx16VirtualRegisters(program: Program, baseAddress: UInt) {
|
||||
// reset the address of the virtual registers to be inside the evaluation stack.
|
||||
// (we don't do this on CommanderX16 itself as the registers have a fixed location in Zeropage there)
|
||||
val cx16block = program.allBlocks.single { it.name == "cx16" }
|
||||
val memVars = cx16block.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.associateBy { it.name }
|
||||
|
||||
for (regnum in 0u..15u) {
|
||||
val rX = memVars.getValue("r$regnum")
|
||||
val rXL = memVars.getValue("r${regnum}L")
|
||||
val rXH = memVars.getValue("r${regnum}H")
|
||||
val rXs = memVars.getValue("r${regnum}s")
|
||||
val rXsL = memVars.getValue("r${regnum}sL")
|
||||
val rXsH = memVars.getValue("r${regnum}sH")
|
||||
setAddress(rX, baseAddress + 2u * regnum)
|
||||
setAddress(rXL, baseAddress + 2u * regnum)
|
||||
setAddress(rXH, baseAddress + 2u * regnum + 1u)
|
||||
setAddress(rXs, baseAddress + 2u * regnum)
|
||||
setAddress(rXsL, baseAddress + 2u * regnum)
|
||||
setAddress(rXsH, baseAddress + 2u * regnum + 1u)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAddress(vardecl: VarDecl, address: UInt) {
|
||||
val oldAddr = vardecl.value as NumericLiteral
|
||||
vardecl.value = NumericLiteral(oldAddr.type, address.toDouble(), oldAddr.position)
|
||||
@@ -48,13 +61,13 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
|
||||
|
||||
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(char.encoding== Encoding.DEFAULT)
|
||||
char.encoding = compTarget.defaultEncoding
|
||||
char.encoding = options.compTarget.defaultEncoding
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(string.encoding==Encoding.DEFAULT)
|
||||
string.encoding = compTarget.defaultEncoding
|
||||
string.encoding = options.compTarget.defaultEncoding
|
||||
return super.before(string, parent)
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
if (!subroutine.isAsmSubroutine) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(subroutine.amountOfRtsInAsm() == 0
|
||||
(!subroutine.hasRtsInAsm(options.compTarget)
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine
|
||||
&& subroutine.statements.last() !is Return)) {
|
||||
@@ -161,10 +161,11 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
}
|
||||
|
||||
if (!subroutine.inline || !options.optimize) {
|
||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && subroutine.amountOfRtsInAsm() == 0) {
|
||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) {
|
||||
// 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 += IAstModification.InsertLast(InlineAssembly(" rts\n", Position.DUMMY), subroutine)
|
||||
val instruction = if(options.compTarget.name==VMTarget.NAME) " return\n" else " rts\n"
|
||||
mods += IAstModification.InsertLast(InlineAssembly(instruction, Position.DUMMY), subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,88 +216,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
(binExpr.right as? NumericLiteral)?.number!=0.0)
|
||||
throw InternalCompilerException("0==X should have been swapped to if X==0")
|
||||
|
||||
// simplify the conditional expression, introduce simple assignments if required.
|
||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||
val simplify = simplifyConditionalExpression(binExpr)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(simplify.rightVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.rightVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.leftVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
private class CondExprSimplificationResult(
|
||||
val leftVarAssignment: Assignment?,
|
||||
val leftOperandReplacement: Expression?,
|
||||
val rightVarAssignment: Assignment?,
|
||||
val rightOperandReplacement: Expression?
|
||||
)
|
||||
|
||||
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
|
||||
|
||||
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
|
||||
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
|
||||
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
|
||||
if(options.compTarget.name==VMTarget.NAME) // don't apply this optimization for Vm target
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
var rightAssignment: Assignment? = null
|
||||
var rightOperandReplacement: Expression? = null
|
||||
|
||||
val separateLeftExpr = !expr.left.isSimple
|
||||
&& expr.left !is IFunctionCall
|
||||
&& expr.left !is ContainmentCheck
|
||||
val separateRightExpr = !expr.right.isSimple
|
||||
&& expr.right !is IFunctionCall
|
||||
&& expr.right !is ContainmentCheck
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
|
||||
if(!leftDt.isInteger || !rightDt.isInteger) {
|
||||
// we can't reasonably simplify non-integer expressions
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
}
|
||||
|
||||
if(separateLeftExpr) {
|
||||
val name = getTempRegisterName(leftDt)
|
||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left,
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
if(separateRightExpr) {
|
||||
val (tempVarName, _) = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
|
||||
rightOperandReplacement = IdentifierReference(tempVarName, expr.position)
|
||||
rightAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
|
||||
expr.right,
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
return CondExprSimplificationResult(
|
||||
leftAssignment, leftOperandReplacement,
|
||||
rightAssignment, rightOperandReplacement
|
||||
)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
@@ -119,12 +119,6 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
if(gosub.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $gosub")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(label: Label, parent: Node): Iterable<IAstModification> {
|
||||
if(label.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $label")
|
||||
|
||||
@@ -7,9 +7,6 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.cpu6502.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.cpu6502.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
|
||||
internal class StatementReorderer(val program: Program,
|
||||
@@ -26,7 +23,6 @@ internal class StatementReorderer(val program: Program,
|
||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||
// - replace subroutine calls (statement) by just assigning the arguments to the parameters and then a GoSub to the routine.
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
@@ -387,155 +383,4 @@ internal class StatementReorderer(val program: Program,
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)
|
||||
?: throw FatalAstException("no target for $functionCallStatement")
|
||||
checkUnusedReturnValues(functionCallStatement, function, errors)
|
||||
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun tryReplaceCallWithGosub(
|
||||
functionCallStatement: FunctionCallStatement,
|
||||
parent: Node,
|
||||
program: Program,
|
||||
options: CompilationOptions
|
||||
): Iterable<IAstModification> {
|
||||
val callee = functionCallStatement.target.targetStatement(program)!!
|
||||
if(callee is Subroutine) {
|
||||
if(callee.inline)
|
||||
return emptyList()
|
||||
return if(callee.isAsmSubroutine) {
|
||||
if(options.compTarget.name==VMTarget.NAME)
|
||||
emptyList()
|
||||
else
|
||||
tryReplaceCallAsmSubWithGosub(functionCallStatement, parent, callee)
|
||||
}
|
||||
else
|
||||
tryReplaceCallNormalSubWithGosub(functionCallStatement, parent, callee, program)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun tryReplaceCallNormalSubWithGosub(call: FunctionCallStatement, parent: Node, callee: Subroutine, program: Program): Iterable<IAstModification> {
|
||||
val noModifications = emptyList<IAstModification>()
|
||||
|
||||
if(callee.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
return listOf(IAstModification.ReplaceNode(call, GoSub(call.target, call.position), parent))
|
||||
}
|
||||
|
||||
if(callee.parameters.size==1) {
|
||||
if(callee.parameters[0].type in IntegerDatatypes) {
|
||||
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else if(callee.parameters.size==2) {
|
||||
if(callee.parameters[0].type in ByteDatatypes && callee.parameters[1].type in ByteDatatypes) {
|
||||
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
val assignParams =
|
||||
callee.parameters.zip(call.args).map {
|
||||
var argumentValue = it.second
|
||||
val paramIdentifier = IdentifierReference(callee.scopedName + it.first.name, argumentValue.position)
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
if(argumentValue is IdentifierReference)
|
||||
argumentValue = AddressOf(argumentValue, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, AssignmentOrigin.PARAMETERASSIGN, argumentValue.position)
|
||||
}
|
||||
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
|
||||
scope.statements += GoSub(call.target, call.position)
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
|
||||
private fun tryReplaceCallAsmSubWithGosub(
|
||||
call: FunctionCallStatement,
|
||||
parent: Node,
|
||||
callee: Subroutine
|
||||
): Iterable<IAstModification> {
|
||||
val noModifications = emptyList<IAstModification>()
|
||||
|
||||
if(callee.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(callee.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
scope.statements += GoSub(call.target, call.position)
|
||||
if(callee.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
} else if(!asmsub6502ArgsHaveRegisterClobberRisk(call.args, callee.asmParameterRegisters)) {
|
||||
// No register clobber risk, let the asmgen assign values to the registers directly.
|
||||
// this is more efficient than first evaluating them to the stack.
|
||||
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
|
||||
return noModifications
|
||||
} else {
|
||||
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
|
||||
return makeGosubWithArgsViaCpuStack(call, call.position, parent, callee)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeGosubWithArgsViaCpuStack(
|
||||
call: IFunctionCall,
|
||||
position: Position,
|
||||
parent: Node,
|
||||
callee: Subroutine
|
||||
): Iterable<IAstModification> {
|
||||
|
||||
fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
|
||||
mutableListOf(IdentifierReference(targetName, position)),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
|
||||
val pushvalue = when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> value
|
||||
in PassByReferenceDatatypes -> value
|
||||
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
|
||||
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
|
||||
else -> throw FatalAstException("invalid dt $dt $value")
|
||||
}
|
||||
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
|
||||
mutableListOf(pushvalue),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
val argOrder = asmsub6502ArgsEvalOrder(callee)
|
||||
val scope = AnonymousScope(mutableListOf(), position)
|
||||
if(callee.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), position), mutableListOf(), true, position)
|
||||
}
|
||||
argOrder.reversed().forEach {
|
||||
val arg = call.args[it]
|
||||
val param = callee.parameters[it]
|
||||
scope.statements += pushCall(arg, param.type, arg.position)
|
||||
}
|
||||
// ... and pop them off again into the registers.
|
||||
argOrder.forEach {
|
||||
val param = callee.parameters[it]
|
||||
val targetName = callee.scopedName + param.name
|
||||
scope.statements += popCall(targetName, param.type, position)
|
||||
}
|
||||
scope.statements += GoSub(call.target, position)
|
||||
if(callee.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), position), mutableListOf(), true, position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call as Node, scope, parent))
|
||||
}
|
||||
@@ -38,11 +38,12 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
val parameters = subroutine.parameters.map { StSubroutineParameter(it.name, it.type) }
|
||||
if(subroutine.asmAddress!=null) {
|
||||
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.position)
|
||||
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.returntypes, subroutine.position)
|
||||
scopestack.peek().add(node)
|
||||
// st.origAstLinks[subroutine] = node
|
||||
} else {
|
||||
val node = StSub(subroutine.name, parameters, subroutine.position)
|
||||
val returnType = if(subroutine.returntypes.isEmpty()) null else subroutine.returntypes.first()
|
||||
val node = StSub(subroutine.name, parameters, returnType, subroutine.position)
|
||||
scopestack.peek().add(node)
|
||||
scopestack.push(node)
|
||||
super.visit(subroutine)
|
||||
|
||||
@@ -6,7 +6,8 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
@@ -218,9 +219,5 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
if(mismatch>=0) {
|
||||
val actual = argtypes[mismatch]
|
||||
val expected = consideredParamTypes[mismatch]
|
||||
if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number !in setOf(0.0, 1.0))
|
||||
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
|
||||
return if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
|
||||
null // specifying a 1 or 0 as a BOOL is okay
|
||||
else
|
||||
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
|
||||
}
|
||||
if(target.isAsmSubroutine) {
|
||||
if(target.asmReturnvaluesRegisters.size>1) {
|
||||
|
||||
@@ -197,10 +197,7 @@ class TestCallgraph: FunSpec({
|
||||
callgraph.checkRecursiveCalls(errors)
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 4
|
||||
errors.warnings[0] shouldContain "contains recursive subroutine calls"
|
||||
errors.warnings[1] shouldContain "start at"
|
||||
errors.warnings[2] shouldContain "recurse1 at"
|
||||
errors.warnings[3] shouldContain "recurse2 at"
|
||||
errors.warnings[0] shouldContain "contains recursive subroutines"
|
||||
}
|
||||
|
||||
test("no recursion warning if reference isn't a call") {
|
||||
|
||||
@@ -94,6 +94,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"amiga",
|
||||
"bdmusic",
|
||||
"bobs",
|
||||
"circles",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
@@ -126,6 +127,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
listOf(
|
||||
"animals",
|
||||
"balls",
|
||||
"bsieve",
|
||||
"cube3d",
|
||||
"cube3d-float",
|
||||
"cube3d-gfx",
|
||||
|
||||
@@ -5,16 +5,37 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.astprocessing.hasRtsInAsm
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
class TestSubroutines: FunSpec({
|
||||
|
||||
test("string arg for byte param proper errormessage and subroutineptr in array too") {
|
||||
val text="""
|
||||
main {
|
||||
sub func(ubyte bb) {
|
||||
bb++
|
||||
}
|
||||
|
||||
sub start() {
|
||||
func("abc")
|
||||
uword[] commands = ["abc", func]
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "type mismatch, was: STR expected: UBYTE"
|
||||
errors.errors[1] shouldContain "initialisation value has incompatible type"
|
||||
}
|
||||
|
||||
test("stringParameter") {
|
||||
val text = """
|
||||
main {
|
||||
@@ -103,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.amountOfRtsInAsm() shouldBe 1
|
||||
asmfunc.hasRtsInAsm(C64Target()) shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("str param should have been changed to uword") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
@@ -267,6 +288,6 @@ class TestSubroutines: FunSpec({
|
||||
|
||||
stmts.last() shouldBe instanceOf<Subroutine>()
|
||||
stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough
|
||||
stmts.dropLast(2).last() shouldBe instanceOf<GoSub>()
|
||||
stmts.dropLast(2).last() shouldBe instanceOf<IFunctionCall>()
|
||||
}
|
||||
})
|
||||
@@ -95,22 +95,23 @@ main {
|
||||
ubyte ub3
|
||||
ub3 = 1
|
||||
ubyte @shared bvalue
|
||||
bvalue = (((ub1^ub2)^ub3)^1)
|
||||
bvalue = ub1
|
||||
bvalue ^= ub2
|
||||
bvalue ^= ub3
|
||||
bvalue ^= 1
|
||||
bvalue = (((ub1^ub2)^ub3)^(ftrue(99)!=0))
|
||||
bvalue = ((ub1&ub2)&(ftrue(99)!=0))
|
||||
return
|
||||
*/
|
||||
stmts.size shouldBe 11
|
||||
val assignValue1 = (stmts[7] as Assignment).value as BinaryExpression
|
||||
val assignValue2 = (stmts[8] as Assignment).value as BinaryExpression
|
||||
val assignValue3 = (stmts[9] as Assignment).value as BinaryExpression
|
||||
assignValue1.operator shouldBe "^"
|
||||
stmts.size shouldBe 14
|
||||
val assignValue1 = (stmts[7] as Assignment).value as IdentifierReference
|
||||
val assignValue2 = (stmts[11] as Assignment).value as BinaryExpression
|
||||
val assignValue3 = (stmts[12] as Assignment).value as BinaryExpression
|
||||
assignValue1.nameInSource shouldBe listOf("ub1")
|
||||
assignValue2.operator shouldBe "^"
|
||||
assignValue3.operator shouldBe "&"
|
||||
val right1 = assignValue1.right as NumericLiteral
|
||||
val right2 = assignValue2.right as BinaryExpression
|
||||
val right3 = assignValue3.right as BinaryExpression
|
||||
right1.number shouldBe 1.0
|
||||
right2.operator shouldBe "!="
|
||||
right3.operator shouldBe "!="
|
||||
right2.left shouldBe instanceOf<IFunctionCall>()
|
||||
@@ -701,11 +702,12 @@ main {
|
||||
fl = bb as float
|
||||
bb = fl as byte
|
||||
uw = fl as uword
|
||||
uw = 8888 + (bb as ubyte)
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
result.program.entrypoint.statements.size shouldBe 13
|
||||
result.program.entrypoint.statements.size shouldBe 15
|
||||
}
|
||||
|
||||
test("invalid typecasts of numbers") {
|
||||
@@ -826,7 +828,7 @@ main {
|
||||
"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
val statements = result.program.entrypoint.statements
|
||||
statements.size shouldBe 27
|
||||
statements.size shouldBeGreaterThan 10
|
||||
}
|
||||
|
||||
test("cast to unsigned in conditional") {
|
||||
|
||||
@@ -34,6 +34,9 @@ class TestAbstractZeropage: FunSpec({
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,20 +120,22 @@ class TestC64Zeropage: FunSpec({
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
zp1.availableBytes() shouldBe 18
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 85
|
||||
zp2.availableBytes() shouldBe 88
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
|
||||
zp3.availableBytes() shouldBe 125
|
||||
zp3.availableBytes() shouldBe 97
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
zp4.availableBytes() shouldBe 239
|
||||
zp4.availableBytes() shouldBe 207
|
||||
zp4.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 238
|
||||
zp4.availableBytes() shouldBe 206
|
||||
zp4.allocate(listOf("test2"), DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 237
|
||||
zp4.availableBytes() shouldBe 205
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
zp1.availableBytes() shouldBe 239
|
||||
zp1.availableBytes() shouldBe 207
|
||||
4u shouldNotBeIn zp1.free
|
||||
35u shouldNotBeIn zp1.free
|
||||
50u shouldBeIn zp1.free
|
||||
100u shouldBeIn zp1.free
|
||||
49u shouldBeIn zp1.free
|
||||
@@ -139,7 +144,9 @@ class TestC64Zeropage: FunSpec({
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 139
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldNotBeIn zp2.free
|
||||
35u shouldNotBeIn zp2.free
|
||||
50u shouldNotBeIn zp2.free
|
||||
100u shouldNotBeIn zp2.free
|
||||
49u shouldBeIn zp2.free
|
||||
@@ -147,6 +154,10 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldBeIn zp3.free
|
||||
35u shouldNotBeIn zp3.free
|
||||
}
|
||||
|
||||
test("testBasicsafeAllocation") {
|
||||
@@ -173,7 +184,7 @@ class TestC64Zeropage: FunSpec({
|
||||
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 239
|
||||
zp.availableBytes() shouldBe 207
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
|
||||
|
||||
@@ -240,11 +240,6 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(gosub: GoSub) {
|
||||
output("gosub ")
|
||||
gosub.identifier.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(ifElse: IfElse) {
|
||||
output("if ")
|
||||
ifElse.condition.accept(this)
|
||||
|
||||
@@ -197,6 +197,14 @@ interface INameScope: IStatementContainer, INamedStatement {
|
||||
else
|
||||
statementScope = (statementScope as Node).definingScope
|
||||
}
|
||||
|
||||
// still not found, maybe it is a symbol in another module
|
||||
for(module in (this as Node).definingModule.program.modules) {
|
||||
val stmt = module.searchSymbol(name)
|
||||
if(stmt!=null)
|
||||
return stmt
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ package prog8.ast
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.ZeropageWish
|
||||
@@ -47,48 +47,6 @@ fun Program.getTempVar(dt: DataType, altNames: Boolean=false): Pair<List<String>
|
||||
return Pair(tmpvarName, decl)
|
||||
}
|
||||
|
||||
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
|
||||
return when {
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
|
||||
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
|
||||
dt istype DataType.BOOL -> listOf("cx16", "r9L")
|
||||
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
|
||||
dt istype DataType.UWORD -> listOf("cx16", "r9")
|
||||
dt istype DataType.WORD -> listOf("cx16", "r9s")
|
||||
dt.isPassByReference -> listOf("cx16", "r9")
|
||||
else -> throw FatalAstException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
|
||||
fun determineGosubArguments(gosub: GoSub): Map<String, Expression> {
|
||||
val parent = gosub.parent as IStatementContainer
|
||||
val gosubIdx = parent.statements.indexOf(gosub)
|
||||
val previousNodes = parent.statements.subList(0, gosubIdx).reversed()
|
||||
|
||||
val arguments = mutableMapOf<String, Expression>()
|
||||
for (node in previousNodes) {
|
||||
if(node !is Assignment || node.origin!=AssignmentOrigin.PARAMETERASSIGN)
|
||||
break
|
||||
arguments[node.target.identifier!!.nameInSource.last()] = node.value
|
||||
}
|
||||
|
||||
// instead of just assigning to the parameters, another way is to use push()/pop()
|
||||
if(previousNodes.isNotEmpty()) {
|
||||
val first = previousNodes[0] as? IFunctionCall
|
||||
if(first!=null && (first.target.nameInSource.singleOrNull() in arrayOf("pop", "popw"))) {
|
||||
val numPops = previousNodes.indexOfFirst { (it as? IFunctionCall)?.target?.nameInSource?.singleOrNull() !in arrayOf("pop", "popw") }
|
||||
val pops = previousNodes.subList(0, numPops)
|
||||
val pushes = previousNodes.subList(numPops, numPops+numPops).reversed()
|
||||
for ((push, pop) in pushes.zip(pops)) {
|
||||
val name = ((pop as IFunctionCall).args.single() as IdentifierReference).nameInSource.last()
|
||||
val arg = (push as IFunctionCall).args.single()
|
||||
arguments[name] = arg
|
||||
}
|
||||
}
|
||||
}
|
||||
return arguments
|
||||
}
|
||||
|
||||
fun maySwapOperandOrder(binexpr: BinaryExpression): Boolean {
|
||||
fun ok(expr: Expression): Boolean {
|
||||
return when(expr) {
|
||||
|
||||
@@ -757,9 +757,11 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, select the "biggegst" datatype based on the elements in the array.
|
||||
// otherwise, select the "biggest" datatype based on the elements in the array.
|
||||
require(value.isNotEmpty()) { "can't determine type of empty array" }
|
||||
val datatypesInArray = value.map { it.inferType(program) }
|
||||
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
|
||||
if(datatypesInArray.any{ it.isUnknown })
|
||||
return InferredTypes.InferredType.unknown()
|
||||
val dts = datatypesInArray.map { it.getOr(DataType.UNDEFINED) }
|
||||
return when {
|
||||
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
|
||||
|
||||
@@ -584,24 +584,6 @@ class Jump(var address: UInt?,
|
||||
"Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
||||
}
|
||||
|
||||
// a GoSub is ONLY created internally for calling subroutines, there's no syntax for it in the language
|
||||
class GoSub(val identifier: IdentifierReference, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
identifier.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun copy() = GoSub(identifier.copy(), position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString() =
|
||||
"GoSub($identifier; pos=$position)"
|
||||
}
|
||||
|
||||
class FunctionCallStatement(override var target: IdentifierReference,
|
||||
override var args: MutableList<Expression>,
|
||||
val void: Boolean,
|
||||
@@ -761,13 +743,6 @@ class Subroutine(override val name: String,
|
||||
return KeepAresult(false, saveAonReturn)
|
||||
}
|
||||
|
||||
fun amountOfRtsInAsm(): Int = statements
|
||||
.asSequence()
|
||||
.filter { it is InlineAssembly }
|
||||
.map { (it as InlineAssembly).assembly }
|
||||
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it }
|
||||
|
||||
|
||||
// code to provide the ability to reference asmsub parameters via qualified name:
|
||||
private val asmParamsDecls = mutableMapOf<String, VarDecl>()
|
||||
|
||||
@@ -1065,7 +1040,6 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
|
||||
// Calls to builtin functions will be replaced with this node just before handing the Ast to the codegen.
|
||||
// this is meant to eventually (?) be able to not have any FunctionCallStatement nodes to worry about anymore
|
||||
// in the codegen, because they have been converted into GoSub (for instance) or this node.
|
||||
// However, if/when the codegen is moved over to use the new CodeAst (PtProgram etc. etc.) this is all moot.
|
||||
class BuiltinFunctionCallStatement(override var target: IdentifierReference,
|
||||
override var args: MutableList<Expression>,
|
||||
|
||||
@@ -114,7 +114,6 @@ abstract class AstWalker {
|
||||
open fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(gosub: GoSub, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(label: Label, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@@ -157,7 +156,6 @@ abstract class AstWalker {
|
||||
open fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(label: Label, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@@ -307,12 +305,6 @@ abstract class AstWalker {
|
||||
track(after(jump, parent), jump, parent)
|
||||
}
|
||||
|
||||
fun visit(gosub: GoSub, parent: Node) {
|
||||
track(before(gosub, parent), gosub, parent)
|
||||
gosub.identifier.accept(this, gosub)
|
||||
track(after(gosub, parent), gosub, parent)
|
||||
}
|
||||
|
||||
fun visit(ifElse: IfElse, parent: Node) {
|
||||
track(before(ifElse, parent), ifElse, parent)
|
||||
ifElse.condition.accept(this, ifElse)
|
||||
|
||||
@@ -70,10 +70,6 @@ interface IAstVisitor {
|
||||
jump.identifier?.accept(this)
|
||||
}
|
||||
|
||||
fun visit(gosub: GoSub) {
|
||||
gosub.identifier.accept(this)
|
||||
}
|
||||
|
||||
fun visit(ifElse: IfElse) {
|
||||
ifElse.condition.accept(this)
|
||||
ifElse.truepart.accept(this)
|
||||
|
||||
@@ -33,15 +33,12 @@ class CallGraph(private val program: Program, private val allowMissingIdentifier
|
||||
}
|
||||
|
||||
private val usedBlocks: Set<Block> by lazy {
|
||||
val blocksFromSubroutines = usedSubroutines.map { it.definingBlock }
|
||||
val blocksFromLibraries = program.allBlocks.filter { it.isInLibrary }
|
||||
val used = mutableSetOf<Block>()
|
||||
|
||||
allIdentifiersAndTargets.forEach {
|
||||
if(it.key.definingBlock in blocksFromSubroutines) {
|
||||
val target = it.value.definingBlock
|
||||
used.add(target)
|
||||
}
|
||||
val target = it.value.definingBlock
|
||||
used.add(target)
|
||||
}
|
||||
|
||||
used + blocksFromLibraries + program.entrypoint.definingBlock
|
||||
@@ -100,17 +97,6 @@ class CallGraph(private val program: Program, private val allowMissingIdentifier
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(gosub: GoSub) {
|
||||
val otherSub = gosub.identifier.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
gosub.definingSubroutine?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + gosub
|
||||
}
|
||||
}
|
||||
super.visit(gosub)
|
||||
}
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
val target = identifier.targetStatement(program)
|
||||
if(allowMissingIdentifierTargetVarDecls) {
|
||||
@@ -129,54 +115,35 @@ class CallGraph(private val program: Program, private val allowMissingIdentifier
|
||||
}
|
||||
|
||||
fun checkRecursiveCalls(errors: IErrorReporter) {
|
||||
val cycles = recursionCycles()
|
||||
if(cycles.any()) {
|
||||
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", cycles[0][0].position)
|
||||
val printed = mutableSetOf<Subroutine>()
|
||||
for(chain in cycles) {
|
||||
if(chain[0] !in printed) {
|
||||
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.warn("Cycle in (a subroutine call in) $chainStr", chain[0].position)
|
||||
printed.add(chain[0])
|
||||
}
|
||||
val recursiveSubroutines = recursionCycles()
|
||||
if(recursiveSubroutines.any()) {
|
||||
errors.warn("Program contains recursive subroutines. These only works in very specific limited scenarios!", recursiveSubroutines.first().position)
|
||||
for(subroutine in recursiveSubroutines) {
|
||||
errors.warn("recursive subroutine '${subroutine.name}'", subroutine.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recursionCycles(): List<List<Subroutine>> {
|
||||
val chains = mutableListOf<MutableList<Subroutine>>()
|
||||
private fun recursionCycles(): Set<Subroutine> {
|
||||
val cycles = mutableSetOf<Subroutine>()
|
||||
|
||||
for(caller in calls.keys) {
|
||||
val visited = calls.keys.associateWith { false }.toMutableMap()
|
||||
val recStack = calls.keys.associateWith { false }.toMutableMap()
|
||||
val chain = mutableListOf<Subroutine>()
|
||||
if(hasCycle(caller, visited, recStack, chain))
|
||||
chains.add(chain)
|
||||
if(hasRecursionCycle(caller))
|
||||
cycles.add(caller)
|
||||
}
|
||||
return chains
|
||||
return cycles
|
||||
}
|
||||
|
||||
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
|
||||
// mark current node as visited and add to recursion stack
|
||||
if(recStack[sub]==true)
|
||||
return true
|
||||
if(visited[sub]==true)
|
||||
return false
|
||||
|
||||
// mark visited and add to recursion stack
|
||||
visited[sub] = true
|
||||
recStack[sub] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
for(called in calls.getValue(sub)) {
|
||||
if(hasCycle(called, visited, recStack, chain)) {
|
||||
chain.add(called)
|
||||
return true
|
||||
private fun hasRecursionCycle(sub: Subroutine): Boolean {
|
||||
val callCloud = calls.getValue(sub).toMutableSet()
|
||||
var previousCloudSize = -1
|
||||
while(callCloud.size > previousCloudSize && sub !in callCloud) {
|
||||
previousCloudSize = callCloud.size
|
||||
for(element in callCloud.toList()) {
|
||||
callCloud.addAll(calls.getValue(element))
|
||||
}
|
||||
}
|
||||
|
||||
// pop from recursion stack
|
||||
recStack[sub] = false
|
||||
return false
|
||||
return sub in callCloud
|
||||
}
|
||||
|
||||
fun unused(module: Module) = module !in usedModules
|
||||
|
||||
@@ -138,6 +138,7 @@ Provides several routines that deal with disk drive I/O, such as:
|
||||
- display disk drive status
|
||||
- load and save data from and to the disk
|
||||
- delete and rename files on the disk
|
||||
- send arbitrary CbmDos command to disk drive
|
||||
|
||||
On the Commander X16 it tries to use that machine's fast kernal loading routines if possible.
|
||||
|
||||
@@ -172,7 +173,7 @@ Provides string manipulation routines.
|
||||
|
||||
``find(string, char) -> ubyte index + carry bit``
|
||||
Locates the first position of the given character in the string, returns carry bit set if found
|
||||
and the index in the string. Or carry bit clear if the character was not found.
|
||||
and the index in the string. Or 0+carry bit clear if the character was not found.
|
||||
|
||||
``compare(string1, string2) -> ubyte result``
|
||||
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
||||
@@ -190,6 +191,16 @@ Provides string manipulation routines.
|
||||
``upper(string)``
|
||||
Uppercases the petscii-string in place.
|
||||
|
||||
``startswith(string, prefix) -> bool``
|
||||
Returns true if string starts with prefix, otherwise false
|
||||
|
||||
``endswith(string, suffix) -> bool``
|
||||
Returns true if string ends with suffix, otherwise false
|
||||
|
||||
``pattern_match(string, pattern) -> ubyte`` (not on Virtual target)
|
||||
Returns 1 (true) if the string matches the pattern, 0 (false) if not.
|
||||
'?' in the pattern matches any one character. '*' in the pattern matches any substring.
|
||||
|
||||
|
||||
floats
|
||||
------
|
||||
|
||||
@@ -236,6 +236,11 @@ Unsigned integers are in the range 0-255 for unsigned byte types, and 0-65535 fo
|
||||
The signed integers integers are in the range -128..127 for bytes,
|
||||
and -32768..32767 for words.
|
||||
|
||||
.. caution::
|
||||
Doing math on signed integers can result in code that is a lot larger and slower than
|
||||
when using unsigned integers. Make sure you really need the signed numbers, otherwise
|
||||
stick to unsigned integers for efficiency.
|
||||
|
||||
|
||||
Boolean values
|
||||
^^^^^^^^^^^^^^
|
||||
@@ -495,7 +500,9 @@ Breaking out of a loop prematurely is possible with the ``break`` statement.
|
||||
languages if this is *not* the case! A for loop from ubyte 10 to ubyte 2, for example, will iterate through
|
||||
all values 10, 11, 12, 13, .... 254, 255, 0 (wrapped), 1, 2. In other languages the entire loop will
|
||||
be skipped in such cases. But prog8 omits the overhead of an extra loop range check and/or branch for every for loop
|
||||
by assuming the normal ranges.
|
||||
because the most common case is that it is not needed.
|
||||
You should add an explicit range check yourself if the ending value can be less than the start value and
|
||||
a full wrap-around loop is not what you want!
|
||||
|
||||
|
||||
Conditional Execution
|
||||
@@ -897,14 +904,17 @@ memory(name, size, alignment)
|
||||
|
||||
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
|
||||
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
|
||||
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte).
|
||||
Notice that bank $00 is used by the Kernal and should not be used by user code.
|
||||
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte), but you can specify
|
||||
any address in system ram (why this can be useful is explained at the end of this paragraph)
|
||||
The third argument can be used to designate the memory address
|
||||
of an argument for the routine; it will be loaded into the A register and will
|
||||
receive the result value returned by the routine in the A register. If you leave this at zero,
|
||||
no argument passing will be done.
|
||||
If the routine requires different arguments or return values, ``callfar`` cannot be used
|
||||
and you'll have to set up a call to ``jsrfar`` yourself to process this.
|
||||
Note: the address can be a variable or other expression, which allows you to use ``callfar`` with bank 0 to do an indirect JSR to a subroutine
|
||||
whose address can vary (jump table, etc. ``goto`` can do an indirect JMP to a variable address): ``callfar(0, &routine, &argument)``
|
||||
This is not very efficient though, so maybe you should write a small piece of inline assembly for this instead.
|
||||
|
||||
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
|
||||
Calls an assembly routine in another rom-bank on the CommanderX16
|
||||
@@ -921,7 +931,7 @@ callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for
|
||||
syscall(callnr), syscall1(callnr, arg), syscall2(callnr, arg1, arg2), syscall3(callnr, arg1, arg2, arg3)
|
||||
Functions for doing a system call on targets that support this. Currently no actual target
|
||||
uses this though except, possibly, the experimental code generation target!
|
||||
The regular 6502 based compiler targets just use a gosub to asmsub kernal routines at
|
||||
The regular 6502 based compiler targets just use a subroutine call to asmsub kernal routines at
|
||||
specific memory locations. So these builtin function calls are not useful yet except for
|
||||
experimentation in new code generation targets.
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ Directives
|
||||
- type ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
|
||||
- type ``none`` : no launcher logic is added at all
|
||||
|
||||
|
||||
.. data:: %zeropage <style>
|
||||
|
||||
Level: module.
|
||||
@@ -80,6 +79,16 @@ Directives
|
||||
|
||||
Also read :ref:`zeropage`.
|
||||
|
||||
.. note::
|
||||
``kernalsafe`` and ``full`` on the C64 leave enough room in the zeropage to reallocate the
|
||||
16 virtual registers cx16.r0...cx16.r15 from the Commander X16 into the zeropage as well
|
||||
(but not on the same locations). They are relocated automatically by the compiler.
|
||||
The other options need those locations for other things so those virtual registers have
|
||||
to be put into memory elsewhere (outside of the zeropage). Trying to use them as zero page
|
||||
variables or pointers etc. will be a lot slower in those cases!
|
||||
On the CommanderX16 the registers are always in zeropage. On other targets, for now, they
|
||||
are always outside of the zeropage.
|
||||
|
||||
.. data:: %zpreserved <fromaddress>,<toaddress>
|
||||
|
||||
Level: module.
|
||||
@@ -753,14 +762,14 @@ You can still ``break`` out of such a loop if you want though.
|
||||
Conditional Execution and Jumps
|
||||
-------------------------------
|
||||
|
||||
Unconditional jump
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Unconditional jump: goto
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To jump to another part of the program, you use a ``goto`` statement with an addres or the name
|
||||
of a label or subroutine::
|
||||
|
||||
goto $c000 ; address
|
||||
goto name ; label or subroutine
|
||||
goto $c000 ; address
|
||||
goto name ; label or subroutine
|
||||
|
||||
uword address = $4000
|
||||
goto address ; jump via address variable
|
||||
@@ -770,6 +779,8 @@ to another piece of code that eventually returns).
|
||||
|
||||
If you jump to an address variable (uword), it is doing an 'indirect' jump: the jump will be done
|
||||
to the address that's currently in the variable.
|
||||
Note: to do an indirect *JSR* to a routine with a varying address, you can use the ``callfar`` builtin function
|
||||
(which is not very efficient) or you have to write a small piece of inline assembly.
|
||||
|
||||
|
||||
Conditional execution
|
||||
|
||||
+23
-29
@@ -16,41 +16,36 @@ Need help with
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Compiler:
|
||||
|
||||
- on non-cx16 targets: have an option that if zeropage=FULL, moves the cx16 virtual registers to ZP (same location as on x16?)
|
||||
needs the dynamic base address for the symbols in syslib.p8
|
||||
also needs a trick to allocate them in ZP like Cx16Zeropage already does
|
||||
- vm Instruction needs to know what the read-registers/memory are, and what the write-register/memory is.
|
||||
this info is needed for more advanced optimizations and later code generation steps.
|
||||
- vm: implement remaining sin/cos functions in math.p8
|
||||
- vm: find a solution for the cx16.r0..r15 that "overlap" (r0, r0L, r0H etc) but in the vm each get their own separate variable location now
|
||||
- vm: somehow deal with asmsubs otherwise the vm IR can't fully encode all of prog8
|
||||
- vm: don't store symbol names in instructions to make optimizing the IR easier? but what about jumps to labels. And it's no longer readable by humans.
|
||||
- vm: how to remove all unused subroutines? (in the 6502 assembly codegen, we let 64tass solve this for us)
|
||||
- vm: implement remaining sin/cos functions in math.p8 and merge tables
|
||||
- vm: implement memory mapped variables properly in VariableAllocator
|
||||
- vm: find a solution for the cx16.r0..r15 that "overlap" (r0, r0L, r0H etc) but in the vm each get their own separate variable location now. Maybe this gets solved by the previous item?
|
||||
- vm: encode romsub & romsub call in VM IR (can just crash in virtualmachine itself because program is not in the simulated memory) ExpressionGen.kt
|
||||
- vm: how to remove all unused subroutines? (the 6502 assembly codegen relies on 64tass solve this for us)
|
||||
- vm: rather than being able to jump to any 'address' (IPTR), use 'blocks' that have entry and exit points -> even better dead code elimination possible too
|
||||
- vm: add ore optimizations in VmPeepholeOptimizer
|
||||
- move the vm unit tests to codeGenVirtual module and remove virtualmachine dependency in the compiler module
|
||||
- vm: add more optimizations in VmPeepholeOptimizer
|
||||
- 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.....
|
||||
How is it for the vm target? -> just 2 special cases in CodeGen.
|
||||
- when the vm is stable and *if* its language can get promoted to prog8 IL, the variable allocation should be changed.
|
||||
It's now done before the vm code generation, but the IL should probably not depend on the allocations already performed.
|
||||
So the CodeGen doesn't do VariableAlloc *before* the codegen, but as a last step.
|
||||
- generate WASM from the new ast (or from vm code?) to run prog8 on a browser canvas?
|
||||
So the CodeGen doesn't do VariableAlloc *before* the codegen, but as a last step instead.
|
||||
- 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.
|
||||
- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation, but how does it know?
|
||||
- simplifyConditionalExpression() sometimes introduces needless assignment to r9 tempvar (what scenarios?)
|
||||
- generate WASM from the new ast (or from vm code?) 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?
|
||||
(but we lose the optimizing aspect of the assembler where it strips out unused code.
|
||||
There's not really a dynamic switch possible as all assembly lib code is static and uses one or the other)
|
||||
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?)
|
||||
- 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
|
||||
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?
|
||||
|
||||
|
||||
Libraries:
|
||||
@@ -59,7 +54,7 @@ Libraries:
|
||||
- fix the problems in atari target, and flesh out its libraries.
|
||||
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
|
||||
- optimize several inner loops in gfx2 even further?
|
||||
- add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)?
|
||||
- add modes 3 and perhaps even 2 to gfx2 (16 color and 4 color)?
|
||||
- add a flood fill routine to gfx2?
|
||||
|
||||
|
||||
@@ -71,7 +66,7 @@ Expressions:
|
||||
- rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code that uses a fixed number of predetermined value 'variables'?
|
||||
"Three address code" was mentioned. https://en.wikipedia.org/wiki/Three-address_code
|
||||
these variables have to be unique for each subroutine because they could otherwise be interfered with from irq routines etc.
|
||||
The VM IL solves this already (by using unlimited registers) but still lacks a translation to 6502.
|
||||
The VM IL solves this already (by using unlimited registers) but that still lacks a translation to 6502.
|
||||
- this removes the need for the BinExprSplitter? (which is problematic and very limited now)
|
||||
and perhaps the assignment splitting in BeforeAsmAstChanger too
|
||||
|
||||
@@ -79,11 +74,10 @@ Optimizations:
|
||||
|
||||
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. When 6502-codegen is no longer done from
|
||||
the old CompilerAst, those checks should probably be removed, or be made permanent
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served
|
||||
- AssignmentAsmGen.assignExpression() -> improve code gen for assigning boolean comparison expressions
|
||||
Check what the vm target does here, maybe just do this as part of the vm -> 6502 codegen.
|
||||
- when a for loop's loopvariable isn't referenced in the body, and the iterations are known, replace the loop by a repeatloop
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
- when a loopvariable of a forloop isn't referenced in the body, and the iterations are known, replace the loop by a repeatloop
|
||||
but we have no efficient way right now to see if the body references a variable.
|
||||
- optimize function argument expressions better (use temporary variables to replace non-simple expressions?)
|
||||
|
||||
|
||||
STRUCTS again?
|
||||
@@ -92,7 +86,7 @@ STRUCTS again?
|
||||
What if we were to re-introduce Structs in prog8? Some thoughts:
|
||||
|
||||
- can contain only numeric types (byte,word,float) - no nested structs, no reference types (strings, arrays) inside structs
|
||||
- is just some syntactic sugar for a scoped set of variables -> ast transform to do exactly this before codegen
|
||||
- is just some syntactic sugar for a scoped set of variables -> ast transform to do exactly this before codegen. Codegen doesn't know about struct.
|
||||
- no arrays of struct -- because too slow on 6502 to access those, rather use struct of arrays instead.
|
||||
can we make this a compiler/codegen only issue? i.e. syntax is just as if it was an array of structs?
|
||||
or make it explicit in the syntax so that it is clear what the memory layout of it is.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
%import textio
|
||||
|
||||
main {
|
||||
|
||||
sub crc16(uword data, uword length) -> uword {
|
||||
uword crc = 0
|
||||
repeat length {
|
||||
crc ^= @(data) << $0008
|
||||
repeat 8 {
|
||||
if crc & $8000
|
||||
crc = (crc<<1)^$1021
|
||||
else
|
||||
crc<<=1
|
||||
}
|
||||
data++
|
||||
}
|
||||
return crc
|
||||
}
|
||||
|
||||
sub start() {
|
||||
txt.print("calculating...")
|
||||
c64.SETTIM(0,0,0)
|
||||
uword crc = crc16($e000, $2000)
|
||||
txt.print_uwhex(crc, true) ; should be $ffd0
|
||||
txt.nl()
|
||||
txt.print_uw(c64.RDTIM16())
|
||||
txt.print(" jiffies")
|
||||
sys.wait(100)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
%import textio
|
||||
|
||||
main {
|
||||
sub crc32(uword data, uword length) {
|
||||
; because prog8 doesn't have 32 bits integers, we have to split up the calucation over 2 words.
|
||||
; result in cx16.r0 (high word) and cx1.r1 (low word).
|
||||
cx16.r0 = 0
|
||||
cx16.r1 = 0
|
||||
repeat length {
|
||||
cx16.r0 ^= @(data) << $0008
|
||||
repeat 8 {
|
||||
if cx16.r0 & $8000 {
|
||||
sys.clear_carry()
|
||||
rol(cx16.r1)
|
||||
rol(cx16.r0)
|
||||
cx16.r0 ^= $04c1
|
||||
cx16.r1 ^= $1db7
|
||||
}
|
||||
else {
|
||||
sys.clear_carry()
|
||||
rol(cx16.r1)
|
||||
rol(cx16.r0)
|
||||
}
|
||||
}
|
||||
data++
|
||||
}
|
||||
cx16.r0 ^= $ffff
|
||||
cx16.r1 ^= $ffff
|
||||
}
|
||||
|
||||
sub start() {
|
||||
txt.print("calculating...")
|
||||
c64.SETTIM(0,0,0)
|
||||
crc32($e000, $2000)
|
||||
txt.print_uwhex(cx16.r0, true)
|
||||
txt.print_uwhex(cx16.r1, false)
|
||||
txt.nl()
|
||||
txt.print_uw(c64.RDTIM16())
|
||||
txt.print(" jiffies")
|
||||
sys.wait(100)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
%import textio
|
||||
|
||||
main {
|
||||
|
||||
sub crc8(uword data, uword length) -> ubyte {
|
||||
ubyte crc = 0
|
||||
repeat length {
|
||||
crc ^= @(data)
|
||||
repeat 8 {
|
||||
if crc & $80
|
||||
crc = (crc<<1)^$1d
|
||||
else
|
||||
crc<<=1
|
||||
}
|
||||
data++
|
||||
}
|
||||
return crc
|
||||
}
|
||||
|
||||
sub start() {
|
||||
txt.print("calculating...")
|
||||
c64.SETTIM(0,0,0)
|
||||
ubyte crc = crc8($e000, $2000)
|
||||
txt.print_ubhex(crc, true) ; should be $a2
|
||||
txt.nl()
|
||||
txt.print_uw(c64.RDTIM16())
|
||||
txt.print(" jiffies")
|
||||
sys.wait(100)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
%import textio
|
||||
%import floats
|
||||
|
||||
main {
|
||||
const ubyte N_ITER = 10
|
||||
const ubyte SIZE = 32
|
||||
|
||||
float[SIZE] array = 0.0
|
||||
|
||||
sub testpow(float x, uword y) -> float {
|
||||
float tmp = x
|
||||
if y==0
|
||||
return 1
|
||||
repeat y-1 {
|
||||
tmp *= x
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
sub start() {
|
||||
txt.print("calculating...")
|
||||
c64.SETTIM(0,0,0)
|
||||
|
||||
float res
|
||||
uword i
|
||||
ubyte j
|
||||
for i in 0 to N_ITER-1 {
|
||||
for j in 0 to SIZE-1 {
|
||||
array[j] += testpow(2.5/(i+1.0), j)
|
||||
}
|
||||
}
|
||||
for j in 0 to SIZE-1 {
|
||||
res += array[j]
|
||||
}
|
||||
|
||||
floats.print_f(res)
|
||||
txt.nl()
|
||||
txt.print_uw(c64.RDTIM16())
|
||||
txt.print(" jiffies")
|
||||
sys.wait(100)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Contains several benchmarks from https://gglabs.us/node/2293 ported to prog8.
|
||||
@@ -0,0 +1,51 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
; The "Byte Sieve" test. https://en.wikipedia.org/wiki/Byte_Sieve
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
c64.SETTIM(0, 0, 0)
|
||||
|
||||
const ubyte ITERS = 10
|
||||
uword count
|
||||
uword i
|
||||
uword prime
|
||||
uword k
|
||||
const uword SIZEPL = 8191
|
||||
uword @zp flags_ptr = memory("flags", SIZEPL, $100)
|
||||
|
||||
txt.print("calculating...\n")
|
||||
|
||||
repeat ITERS {
|
||||
sys.memset(flags_ptr, SIZEPL, 1)
|
||||
count = 0
|
||||
for i in 0 to SIZEPL-1 {
|
||||
if @(flags_ptr+i) {
|
||||
prime = i + i + 3
|
||||
k = i + prime
|
||||
while k <= SIZEPL-1 {
|
||||
@(flags_ptr + k) = false
|
||||
k += prime
|
||||
}
|
||||
; txt.print_uw(prime)
|
||||
; txt.spc()
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txt.print_uw(count)
|
||||
txt.print(" primes\n")
|
||||
|
||||
float time = c64.RDTIM16() / 60.0
|
||||
floats.print_f(time)
|
||||
txt.print(" sec total = ")
|
||||
floats.print_f(time/ITERS)
|
||||
txt.print(" sec per iteration\n")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
%import graphics
|
||||
|
||||
; note: this program is tuned for the CX16, but with some minor modifications can run on other systems too.
|
||||
|
||||
main {
|
||||
const ubyte MAX_NUM_CIRCLES = 80
|
||||
const ubyte GROWTH_RATE = 2
|
||||
uword[MAX_NUM_CIRCLES] circle_x
|
||||
uword[MAX_NUM_CIRCLES] circle_y
|
||||
ubyte[MAX_NUM_CIRCLES] circle_radius
|
||||
ubyte num_circles = 0
|
||||
ubyte background_color
|
||||
|
||||
sub start() {
|
||||
graphics.enable_bitmap_mode()
|
||||
|
||||
repeat {
|
||||
background_color = rnd()
|
||||
graphics.clear_screen(0, background_color)
|
||||
num_circles = 0
|
||||
draw_circles()
|
||||
}
|
||||
}
|
||||
|
||||
sub draw_circles() {
|
||||
uword @zp x
|
||||
uword @zp y
|
||||
ubyte @zp radius
|
||||
|
||||
while num_circles<MAX_NUM_CIRCLES {
|
||||
x = rndw() % graphics.WIDTH
|
||||
y = rndw() % graphics.HEIGHT
|
||||
radius = GROWTH_RATE * 2 ; use a bit of a buffer between circles.
|
||||
if not_colliding() {
|
||||
radius -= GROWTH_RATE
|
||||
ubyte color = rnd()
|
||||
while color==background_color
|
||||
color = rnd()
|
||||
graphics.colors(color, 0)
|
||||
while not_edge() and not_colliding() {
|
||||
graphics.disc(x, y as ubyte, radius)
|
||||
sys.waitvsync()
|
||||
radius += GROWTH_RATE
|
||||
}
|
||||
circle_x[num_circles] = x
|
||||
circle_y[num_circles] = y
|
||||
circle_radius[num_circles] = radius - GROWTH_RATE
|
||||
num_circles++
|
||||
}
|
||||
}
|
||||
|
||||
sub not_colliding() -> bool {
|
||||
if num_circles==0
|
||||
return true
|
||||
ubyte @zp c
|
||||
for c in 0 to num_circles-1 {
|
||||
if distance(c) < (radius as uword) + circle_radius[c]
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub distance(ubyte cix) -> uword {
|
||||
word dx = x as word - circle_x[cix]
|
||||
word dy = y as word - circle_y[cix]
|
||||
uword sqx = dx*dx as uword
|
||||
uword sqy = dy*dy as uword
|
||||
return sqrt16(sqx + sqy)
|
||||
}
|
||||
|
||||
; sub distance(ubyte cix) -> uword {
|
||||
; float dx = x as float - circle_x[cix]
|
||||
; float dy = y as float - circle_y[cix]
|
||||
; return floats.sqrt(dx*dx + dy*dy) as uword
|
||||
; }
|
||||
|
||||
sub not_edge() -> bool {
|
||||
if x as word - radius < 0
|
||||
return false
|
||||
if x + radius >= graphics.WIDTH
|
||||
return false
|
||||
if y as word - radius < 0
|
||||
return false
|
||||
if y + radius >= graphics.HEIGHT
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,12 +228,12 @@ const ubyte totalNumberOfFaces = 22
|
||||
const ubyte totalNumberOfPoints = 34
|
||||
str shipName = "cobra-mk3"
|
||||
; vertices
|
||||
word[] xcoor = [ 32,-32,0,-120,120,-88,88,128,-128,0,-32,32,-36,-8,8,36,36,8,-8,-36,-1,-1,-80,-80,-88,80,88,80,1,1,1,1,-1,-1 ]
|
||||
word[] ycoor = [ 0,0,26,-3,-3,16,16,-8,-8,26,-24,-24,8,12,12,8,-12,-16,-16,-12,-1,-1,-6,6,0,6,0,-6,-1,-1,1,1,1,1 ]
|
||||
word[] zcoor = [ 76,76,24,-8,-8,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,76,90,-40,-40,-40,-40,-40,-40,76,90,76,90,76,90 ]
|
||||
word[totalNumberOfPoints] xcoor = [ 32,-32,0,-120,120,-88,88,128,-128,0,-32,32,-36,-8,8,36,36,8,-8,-36,-1,-1,-80,-80,-88,80,88,80,1,1,1,1,-1,-1 ]
|
||||
word[totalNumberOfPoints] ycoor = [ 0,0,26,-3,-3,16,16,-8,-8,26,-24,-24,8,12,12,8,-12,-16,-16,-12,-1,-1,-6,6,0,6,0,-6,-1,-1,1,1,1,1 ]
|
||||
word[totalNumberOfPoints] zcoor = [ 76,76,24,-8,-8,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,76,90,-40,-40,-40,-40,-40,-40,76,90,76,90,76,90 ]
|
||||
; edges and faces
|
||||
ubyte[] edgesFrom = [ 0,1,0,10,1,0,2,0,4,0,4,7,2,1,1,3,8,3,2,5,6,5,6,16,15,14,14,18,13,12,12,26,25,25,22,23,22,20,28,21,20,28,29,30,31,30,32,20,21,20,20 ]
|
||||
ubyte[] edgesTo = [ 1,2,2,11,10,11,6,6,6,4,7,11,5,5,3,5,10,8,9,9,9,8,7,17,16,15,17,19,18,13,19,27,26,27,23,24,24,28,29,29,21,30,31,31,33,32,33,32,33,33,29 ]
|
||||
ubyte[totalNumberOfEdges] edgesFrom = [ 0,1,0,10,1,0,2,0,4,0,4,7,2,1,1,3,8,3,2,5,6,5,6,16,15,14,14,18,13,12,12,26,25,25,22,23,22,20,28,21,20,28,29,30,31,30,32,20,21,20,20 ]
|
||||
ubyte[totalNumberOfEdges] edgesTo = [ 1,2,2,11,10,11,6,6,6,4,7,11,5,5,3,5,10,8,9,9,9,8,7,17,16,15,17,19,18,13,19,27,26,27,23,24,24,28,29,29,21,30,31,31,33,32,33,32,33,33,29 ]
|
||||
ubyte[] facesPoints = [
|
||||
0,1,2 ,255,
|
||||
11,10,1,0 ,255,
|
||||
|
||||
@@ -6,7 +6,7 @@ main {
|
||||
|
||||
sub start() {
|
||||
|
||||
txt.print("ps2 scancode handler test - press keys! esc to quit!\n")
|
||||
txt.print("ps2 custom key handler test - press keys! esc to quit!\n")
|
||||
|
||||
sys.set_irqd()
|
||||
uword old_keyhdl = cx16.KEYHDL
|
||||
@@ -22,7 +22,7 @@ main {
|
||||
sys.clear_irqd()
|
||||
}
|
||||
|
||||
sub keyboard_scancode_handler(ubyte prefix, ubyte scancode, bool updown) {
|
||||
sub keyboard_scancode_handler(ubyte prefix, ubyte scancode, bool updown) -> bool {
|
||||
txt.print_ubhex(prefix, true)
|
||||
txt.chrout(':')
|
||||
txt.print_ubhex(scancode, true)
|
||||
@@ -36,7 +36,7 @@ main {
|
||||
; escape was pressed! exit back to basic
|
||||
main.start.escape_pressed = true
|
||||
}
|
||||
return
|
||||
return true ; true = consume key event, false = continue processing it
|
||||
|
||||
asm_shim:
|
||||
%asm {{
|
||||
@@ -49,8 +49,15 @@ asm_shim:
|
||||
+ stx prefix
|
||||
sta scancode
|
||||
jsr keyboard_scancode_handler
|
||||
beq +
|
||||
plx
|
||||
pla
|
||||
lda #0 ;By setting A=0 we will consume this key event
|
||||
tax
|
||||
plp
|
||||
rts
|
||||
+ plx
|
||||
pla ; leave A untouched, continue processing
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
|
||||
+11
-12
@@ -1,22 +1,21 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
ubyte value1 = %1110
|
||||
ubyte value2 = %1111
|
||||
|
||||
bool[2] @shared barr = [true, false]
|
||||
uword @zp flags_ptr = memory("flags", 200, 0)
|
||||
txt.print("calculating...\n")
|
||||
txt.print_uwhex(flags_ptr, true)
|
||||
txt.nl()
|
||||
|
||||
; if value1 and value2 ; TODO value1 isn't converted to bool in 6502 preprocess
|
||||
; txt.print("ok")
|
||||
; else
|
||||
; txt.print("fail!")
|
||||
; txt.nl()
|
||||
repeat 10 {
|
||||
txt.print("new iter\n")
|
||||
txt.print_ub(@($06))
|
||||
sys.memset(flags_ptr, 200, 0)
|
||||
}
|
||||
|
||||
if value1 and value2!=255 ; TODO value1 isn't converted to bool in 6502 preprocess
|
||||
txt.print("ok")
|
||||
else
|
||||
txt.print("fail!")
|
||||
txt.print("done\n")
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,10 @@ main {
|
||||
for yy in 0 to 239 {
|
||||
for xx in 0 to 319 {
|
||||
ubyte pixel = sys.gfx_getpixel(xx, yy)
|
||||
if pixel>4
|
||||
if pixel>4 {
|
||||
pixel-=4
|
||||
sys.gfx_plot(xx, yy, pixel)
|
||||
sys.gfx_plot(xx, yy, pixel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,11 @@ class Assembler {
|
||||
println("warning: ignoring incbin command: $rest")
|
||||
continue
|
||||
}
|
||||
val opcode = Opcode.valueOf(instr.uppercase())
|
||||
val opcode = try {
|
||||
Opcode.valueOf(instr.uppercase())
|
||||
} catch (ax: IllegalArgumentException) {
|
||||
throw IllegalArgumentException("invalid vmasm instruction: $instr", ax)
|
||||
}
|
||||
var type: VmDataType? = convertType(typestr)
|
||||
val formats = instructionFormats.getValue(opcode)
|
||||
val format: InstructionFormat
|
||||
@@ -204,7 +208,7 @@ class Assembler {
|
||||
if(format.fpValue)
|
||||
floatValue = value!!
|
||||
|
||||
program.add(Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, value = intValue, fpValue = floatValue))
|
||||
program.add(Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, intValue, floatValue))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ add reg1, value - reg1 += value (unsigned + signed)
|
||||
addm reg1, address - memory at address += reg1 (unsigned + signed)
|
||||
subr reg1, reg2 - reg1 -= reg2 (unsigned + signed)
|
||||
sub reg1, value - reg1 -= value (unsigned + signed)
|
||||
subm reg1, address - memory at address -= reg2 (unsigned + signed)
|
||||
subm reg1, address - memory at address -= reg1 (unsigned + signed)
|
||||
mulr reg1, reg2 - unsigned multiply reg1 *= reg2 note: byte*byte->byte, no type extension to word!
|
||||
mul reg1, value - unsigned multiply reg1 *= value note: byte*byte->byte, no type extension to word!
|
||||
mulm reg1, address - memory at address *= reg2 note: byte*byte->byte, no type extension to word!
|
||||
@@ -128,31 +128,31 @@ All have type b or w.
|
||||
|
||||
andr reg1, reg2 - reg1 = reg1 bitwise and reg2
|
||||
and reg1, value - reg1 = reg1 bitwise and value
|
||||
andm reg1 address - memory = memory bitwise and reg1
|
||||
orr reg1, reg2 - reg1 = reg1 bitwise or reg2
|
||||
or reg1, value - reg1 = reg1 bitwise or value
|
||||
orm reg1, address - memory = memory bitwise or reg1
|
||||
xorr reg1, reg2 - reg1 = reg1 bitwise xor reg2
|
||||
xor reg1, value - reg1 = reg1 bitwise xor value
|
||||
xorm reg1, address - memory = memory bitwise xor reg1
|
||||
inv reg1 - reg1 = bitwise invert of reg1 (all bits flipped)
|
||||
lsrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits + set Carry to shifted bit
|
||||
invm address - memory = bitwise invert of that memory (all bits flipped)
|
||||
asrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits (signed) + set Carry to shifted bit
|
||||
lsrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits + set Carry to shifted bit
|
||||
lsln reg1, reg2 - reg1 = multi-shift reg1 left by reg2 bits + set Carry to shifted bit
|
||||
lsr reg1 - shift reg1 right by 1 bits + set Carry to shifted bit
|
||||
asrnm reg1, address - multi-shift memory right by reg1 bits (signed) + set Carry to shifted bit
|
||||
lsrnm reg1, address - multi-shift memoryright by reg1 bits + set Carry to shifted bit
|
||||
lslnm reg1, address - multi-shift memory left by reg1 bits + set Carry to shifted bit
|
||||
asr reg1 - shift reg1 right by 1 bits (signed) + set Carry to shifted bit
|
||||
lsr reg1 - shift reg1 right by 1 bits + set Carry to shifted bit
|
||||
lsl reg1 - shift reg1 left by 1 bits + set Carry to shifted bit
|
||||
lsrm address - shift memory right by 1 bits + set Carry to shifted bit
|
||||
asrm address - shift memory right by 1 bits (signed) + set Carry to shifted bit
|
||||
lslm address - shift memory left by 1 bits + set Carry to shifted bit
|
||||
ror reg1 - rotate reg1 right by 1 bits, not using carry + set Carry to shifted bit
|
||||
roxr reg1 - rotate reg1 right by 1 bits, using carry + set Carry to shifted bit
|
||||
rol reg1 - rotate reg1 left by 1 bits, not using carry + set Carry to shifted bit
|
||||
roxl reg1 - rotate reg1 left by 1 bits, using carry, + set Carry to shifted bit
|
||||
andm reg1 address - memory = memory bitwise and reg1
|
||||
orm reg1, address - memory = memory bitwise or reg1
|
||||
xorm reg1, address - memory = memory bitwise xor reg1
|
||||
invm address - memory = bitwise invert of that memory (all bits flipped)
|
||||
lsrnm reg1, address - multi-shift memoryright by reg1 bits + set Carry to shifted bit
|
||||
asrnm reg1, address - multi-shift memory right by reg1 bits (signed) + set Carry to shifted bit
|
||||
lslnm reg1, address - multi-shift memory left by reg1 bits + set Carry to shifted bit
|
||||
lsrm address - shift memory right by 1 bits + set Carry to shifted bit
|
||||
asrm address - shift memory right by 1 bits (signed) + set Carry to shifted bit
|
||||
lslm address - shift memory left by 1 bits + set Carry to shifted bit
|
||||
rorm address - rotate memory right by 1 bits, not using carry + set Carry to shifted bit
|
||||
roxrm address - rotate memory right by 1 bits, using carry + set Carry to shifted bit
|
||||
rolm address - rotate memory left by 1 bits, not using carry + set Carry to shifted bit
|
||||
@@ -172,6 +172,15 @@ ftosw reg1, fpreg1 - reg1 = fpreg1 as signed word
|
||||
fpow fpreg1, fpreg2 - fpreg1 = fpreg1 to the power of fpreg2
|
||||
fabs fpreg1, fpreg2 - fpreg1 = abs(fpreg2)
|
||||
fcomp reg1, fpreg1, fpreg2 - reg1 = result of comparison of fpreg1 and fpreg2: 0=equal, 1=fpreg1 is greater, -1=fpreg1 is smaller
|
||||
fsin fpreg1, fpreg2 - fpreg1 = sin(fpreg2)
|
||||
fcos fpreg1, fpreg2 - fpreg1 = cos(fpreg2)
|
||||
ftan fpreg1, fpreg2 - fpreg1 = tan(fpreg2)
|
||||
fatan fpreg1, fpreg2 - fpreg1 = atan(fpreg2)
|
||||
fln fpreg1, fpreg2 - fpreg1 = ln(fpreg2) ; natural logarithm
|
||||
flog fpreg1, fpreg2 - fpreg1 = log(fpreg2) ; base 2 logarithm
|
||||
fround fpreg1, fpreg2 - fpreg1 = round(fpreg2)
|
||||
ffloor fpreg1, fpreg2 - fpreg1 = floor(fpreg2)
|
||||
fceil fpreg1, fpreg2 - fpreg1 = ceil(fpreg2)
|
||||
|
||||
|
||||
MISC
|
||||
@@ -316,7 +325,6 @@ enum class Opcode {
|
||||
FATAN,
|
||||
FLN,
|
||||
FLOG,
|
||||
FSQRT,
|
||||
FROUND,
|
||||
FFLOOR,
|
||||
FCEIL,
|
||||
@@ -382,6 +390,11 @@ data class Instruction(
|
||||
val fpValue: Float?=null,
|
||||
val labelSymbol: List<String>?=null // symbolic label name as alternative to value (so only for Branch/jump/call Instructions!)
|
||||
) {
|
||||
// reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT)
|
||||
// This knowledge is useful in IL assembly optimizers to see how registers are used.
|
||||
val reg1direction: OperandDirection
|
||||
val fpReg1direction: OperandDirection
|
||||
|
||||
init {
|
||||
val formats = instructionFormats.getValue(opcode)
|
||||
if(type==null && !formats.containsKey(null))
|
||||
@@ -410,6 +423,9 @@ data class Instruction(
|
||||
throw IllegalArgumentException("$opcode: integer point instruction can't use floating point registers")
|
||||
}
|
||||
|
||||
reg1direction = format.reg1direction
|
||||
fpReg1direction = format.fpReg1direction
|
||||
|
||||
if(opcode in setOf(Opcode.BEQ, Opcode.BNE, Opcode.BLT, Opcode.BLTS,
|
||||
Opcode.BGT, Opcode.BGTS, Opcode.BLE, Opcode.BLES,
|
||||
Opcode.BGE, Opcode.BGES,
|
||||
@@ -464,184 +480,203 @@ data class Instruction(
|
||||
}
|
||||
}
|
||||
|
||||
enum class OperandDirection {
|
||||
INPUT,
|
||||
OUTPUT,
|
||||
INOUT
|
||||
}
|
||||
|
||||
data class InstructionFormat(val datatype: VmDataType?,
|
||||
val reg1: Boolean, val reg2: Boolean,
|
||||
val fpReg1: Boolean, val fpReg2: Boolean,
|
||||
val value: Boolean,
|
||||
val fpValue: Boolean) {
|
||||
val reg1: Boolean, val reg1direction: OperandDirection, // reg1 can be IN/OUT/INOUT
|
||||
val reg2: Boolean, // always only IN
|
||||
val fpReg1: Boolean, val fpReg1direction: OperandDirection, // fpreg1 can be IN/OUT/INOUT
|
||||
val fpReg2: Boolean, // always only IN
|
||||
val value: Boolean, // always only IN
|
||||
val fpValue: Boolean // always only IN
|
||||
) {
|
||||
companion object {
|
||||
fun from(spec: String): Map<VmDataType?, InstructionFormat> {
|
||||
val result = mutableMapOf<VmDataType?, InstructionFormat>()
|
||||
for(part in spec.split('|').map{ it.trim() }) {
|
||||
var reg1 = false
|
||||
var reg2 = false
|
||||
var fpreg1 = false
|
||||
var fpreg2 = false
|
||||
var value = false
|
||||
var fpvalue = false
|
||||
var reg1 = false // read/write/modify possible
|
||||
var reg1Direction = OperandDirection.INPUT
|
||||
var reg2 = false // strictly read-only
|
||||
var fpreg1 = false // read/write/modify possible
|
||||
var fpreg1Direction = OperandDirection.INPUT
|
||||
var fpreg2 = false // strictly read-only
|
||||
var value = false // strictly read-only
|
||||
var fpvalue = false // strictly read-only
|
||||
val splits = part.splitToSequence(',').iterator()
|
||||
val typespec = splits.next()
|
||||
while(splits.hasNext()) {
|
||||
when(splits.next()) {
|
||||
"r1" -> reg1=true
|
||||
"r2" -> reg2=true
|
||||
"fr1" -> fpreg1=true
|
||||
"fr2" -> fpreg2=true
|
||||
"v" -> value = true
|
||||
"fv" -> fpvalue = true
|
||||
"<r1" -> { reg1=true; reg1Direction=OperandDirection.INPUT }
|
||||
">r1" -> { reg1=true; reg1Direction=OperandDirection.OUTPUT }
|
||||
"<>r1" -> { reg1=true; reg1Direction=OperandDirection.INOUT }
|
||||
"<r2" -> reg2 = true
|
||||
"<fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.INPUT }
|
||||
">fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.OUTPUT }
|
||||
"<>fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.INOUT }
|
||||
"<fr2" -> fpreg2=true
|
||||
"<v" -> value = true
|
||||
"<fv" -> fpvalue = true
|
||||
else -> throw IllegalArgumentException(spec)
|
||||
}
|
||||
}
|
||||
if(typespec=="N")
|
||||
result[null] = InstructionFormat(null, reg1=reg1, reg2=reg2, fpReg1=fpreg1, fpReg2=fpreg2, value=value, fpValue=fpvalue)
|
||||
result[null] = InstructionFormat(null, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue)
|
||||
if('B' in typespec)
|
||||
result[VmDataType.BYTE] = InstructionFormat(VmDataType.BYTE, reg1=reg1, reg2=reg2, fpReg1=fpreg1, fpReg2=fpreg2, value=value, fpValue=fpvalue)
|
||||
result[VmDataType.BYTE] = InstructionFormat(VmDataType.BYTE, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue)
|
||||
if('W' in typespec)
|
||||
result[VmDataType.WORD] = InstructionFormat(VmDataType.WORD, reg1=reg1, reg2=reg2, fpReg1=fpreg1, fpReg2=fpreg2, value=value, fpValue=fpvalue)
|
||||
result[VmDataType.WORD] = InstructionFormat(VmDataType.WORD, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue)
|
||||
if('F' in typespec)
|
||||
result[VmDataType.FLOAT] = InstructionFormat(VmDataType.FLOAT, reg1=reg1, reg2=reg2, fpReg1=fpreg1, fpReg2=fpreg2, value=value, fpValue=fpvalue)
|
||||
result[VmDataType.FLOAT] = InstructionFormat(VmDataType.FLOAT, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
<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?
|
||||
*/
|
||||
@Suppress("BooleanLiteralArgument")
|
||||
val instructionFormats = mutableMapOf(
|
||||
Opcode.NOP to InstructionFormat.from("N"),
|
||||
Opcode.LOAD to InstructionFormat.from("BW,r1,v | F,fr1,fv"),
|
||||
Opcode.LOADM to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.LOADI to InstructionFormat.from("BW,r1,r2 | F,fr1,r1"),
|
||||
Opcode.LOADX to InstructionFormat.from("BW,r1,r2,v | F,fr1,r1,v"),
|
||||
Opcode.LOADIX to InstructionFormat.from("BW,r1,r2,v | F,fr1,r1,v"),
|
||||
Opcode.LOADR to InstructionFormat.from("BW,r1,r2 | F,fr1,fr2"),
|
||||
Opcode.STOREM to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.STOREI to InstructionFormat.from("BW,r1,r2 | F,fr1,r1"),
|
||||
Opcode.STOREX to InstructionFormat.from("BW,r1,r2,v | F,fr1,r1,v"),
|
||||
Opcode.STOREIX to InstructionFormat.from("BW,r1,r2,v | F,fr1,r1,v"),
|
||||
Opcode.STOREZM to InstructionFormat.from("BW,v | F,v"),
|
||||
Opcode.STOREZI to InstructionFormat.from("BW,r1 | F,r1"),
|
||||
Opcode.STOREZX to InstructionFormat.from("BW,r1,v | F,r1,v"),
|
||||
Opcode.JUMP to InstructionFormat.from("N,v"),
|
||||
Opcode.CALL to InstructionFormat.from("N,v"),
|
||||
Opcode.SYSCALL to InstructionFormat.from("N,v"),
|
||||
Opcode.LOAD to InstructionFormat.from("BW,>r1,<v | F,>fr1,<fv"),
|
||||
Opcode.LOADM to InstructionFormat.from("BW,>r1,<v | F,>fr1,<v"),
|
||||
Opcode.LOADI to InstructionFormat.from("BW,>r1,<r2 | F,>fr1,<r1"),
|
||||
Opcode.LOADX to InstructionFormat.from("BW,>r1,<r2,<v | F,>fr1,<r1,<v"),
|
||||
Opcode.LOADIX to InstructionFormat.from("BW,>r1,<r2,<v | F,>fr1,<r1,<v"),
|
||||
Opcode.LOADR to InstructionFormat.from("BW,>r1,<r2 | F,>fr1,<fr2"),
|
||||
Opcode.STOREM to InstructionFormat.from("BW,<r1,<v | F,<fr1,<v"),
|
||||
Opcode.STOREI to InstructionFormat.from("BW,<r1,<r2 | F,<fr1,<r1"),
|
||||
Opcode.STOREX to InstructionFormat.from("BW,<r1,<r2,<v | F,<fr1,<r1,<v"),
|
||||
Opcode.STOREIX to InstructionFormat.from("BW,<r1,<r2,<v | F,<fr1,<r1,<v"),
|
||||
Opcode.STOREZM to InstructionFormat.from("BW,<v | F,<v"),
|
||||
Opcode.STOREZI to InstructionFormat.from("BW,<r1 | F,<r1"),
|
||||
Opcode.STOREZX to InstructionFormat.from("BW,<r1,<v | F,<r1,<v"),
|
||||
Opcode.JUMP to InstructionFormat.from("N,<v"),
|
||||
Opcode.CALL to InstructionFormat.from("N,<v"),
|
||||
Opcode.SYSCALL to InstructionFormat.from("N,<v"),
|
||||
Opcode.RETURN to InstructionFormat.from("N"),
|
||||
Opcode.BSTCC to InstructionFormat.from("N,v"),
|
||||
Opcode.BSTCS to InstructionFormat.from("N,v"),
|
||||
Opcode.BSTEQ to InstructionFormat.from("N,v"),
|
||||
Opcode.BSTNE to InstructionFormat.from("N,v"),
|
||||
Opcode.BSTNEG to InstructionFormat.from("N,v"),
|
||||
Opcode.BSTPOS to InstructionFormat.from("N,v"),
|
||||
Opcode.BZ to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.BNZ to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.BEQ to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BNE to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BLT to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BLTS to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BGT to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BGTS to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BLE to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BLES to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BGE to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.BGES to InstructionFormat.from("BW,r1,r2,v"),
|
||||
Opcode.SEQ to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SNE to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SLT to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SLTS to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SGT to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SGTS to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SLE to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SLES to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SGE to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.SGES to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.INC to InstructionFormat.from("BW,r1"),
|
||||
Opcode.INCM to InstructionFormat.from("BW,v"),
|
||||
Opcode.DEC to InstructionFormat.from("BW,r1"),
|
||||
Opcode.DECM to InstructionFormat.from("BW,v"),
|
||||
Opcode.NEG to InstructionFormat.from("BW,r1 | F,fr1"),
|
||||
Opcode.NEGM to InstructionFormat.from("BW,v | F,v"),
|
||||
Opcode.ADDR to InstructionFormat.from("BW,r1,r2 | F,fr1,fr2"),
|
||||
Opcode.ADD to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.ADDM to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.SUBR to InstructionFormat.from("BW,r1,r2 | F,fr1,fr2"),
|
||||
Opcode.SUB to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.SUBM to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.MULR to InstructionFormat.from("BW,r1,r2 | F,fr1,fr2"),
|
||||
Opcode.MUL to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.MULM to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.DIVR to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.DIV to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.DIVM to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.DIVSR to InstructionFormat.from("BW,r1,r2 | F,fr1,fr2"),
|
||||
Opcode.DIVS to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.DIVSM to InstructionFormat.from("BW,r1,v | F,fr1,v"),
|
||||
Opcode.SQRT to InstructionFormat.from("BW,r1,r2 | F,fr1,fr2"),
|
||||
Opcode.SGN to InstructionFormat.from("BW,r1,r2 | F,fr1,fr2"),
|
||||
Opcode.RND to InstructionFormat.from("BW,r1 | F,fr1"),
|
||||
Opcode.MODR to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.MOD to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.CMP to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.EXT to InstructionFormat.from("BW,r1"),
|
||||
Opcode.EXTS to InstructionFormat.from("BW,r1"),
|
||||
Opcode.ANDR to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.AND to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.ANDM to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.ORR to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.OR to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.ORM to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.XORR to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.XOR to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.XORM to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.INV to InstructionFormat.from("BW,r1"),
|
||||
Opcode.INVM to InstructionFormat.from("BW,v"),
|
||||
Opcode.ASRN to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.ASRNM to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.LSRN to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.LSRNM to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.LSLN to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.LSLNM to InstructionFormat.from("BW,r1,v"),
|
||||
Opcode.ASR to InstructionFormat.from("BW,r1"),
|
||||
Opcode.ASRM to InstructionFormat.from("BW,v"),
|
||||
Opcode.LSR to InstructionFormat.from("BW,r1"),
|
||||
Opcode.LSRM to InstructionFormat.from("BW,v"),
|
||||
Opcode.LSL to InstructionFormat.from("BW,r1"),
|
||||
Opcode.LSLM to InstructionFormat.from("BW,v"),
|
||||
Opcode.ROR to InstructionFormat.from("BW,r1"),
|
||||
Opcode.RORM to InstructionFormat.from("BW,v"),
|
||||
Opcode.ROXR to InstructionFormat.from("BW,r1"),
|
||||
Opcode.ROXRM to InstructionFormat.from("BW,v"),
|
||||
Opcode.ROL to InstructionFormat.from("BW,r1"),
|
||||
Opcode.ROLM to InstructionFormat.from("BW,v"),
|
||||
Opcode.ROXL to InstructionFormat.from("BW,r1"),
|
||||
Opcode.ROXLM to InstructionFormat.from("BW,v"),
|
||||
Opcode.BSTCC to InstructionFormat.from("N,<v"),
|
||||
Opcode.BSTCS to InstructionFormat.from("N,<v"),
|
||||
Opcode.BSTEQ to InstructionFormat.from("N,<v"),
|
||||
Opcode.BSTNE to InstructionFormat.from("N,<v"),
|
||||
Opcode.BSTNEG to InstructionFormat.from("N,<v"),
|
||||
Opcode.BSTPOS to InstructionFormat.from("N,<v"),
|
||||
Opcode.BZ to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.BNZ to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.BEQ to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BNE to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BLT to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BLTS to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BGT to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BGTS to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BLE to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BLES to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BGE to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.BGES to InstructionFormat.from("BW,<r1,<r2,<v"),
|
||||
Opcode.SEQ to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SNE to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SLT to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SLTS to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SGT to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SGTS to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SLE to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SLES to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SGE to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.SGES to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.INC to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.INCM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.DEC to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.DECM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.NEG to InstructionFormat.from("BW,<>r1 | F,<>fr1"),
|
||||
Opcode.NEGM to InstructionFormat.from("BW,<v | F,<v"),
|
||||
Opcode.ADDR to InstructionFormat.from("BW,<>r1,<r2 | F,<>fr1,<fr2"),
|
||||
Opcode.ADD to InstructionFormat.from("BW,<>r1,<v | F,<>fr1,<v"),
|
||||
Opcode.ADDM to InstructionFormat.from("BW,<r1,<v | F,<fr1,<v"),
|
||||
Opcode.SUBR to InstructionFormat.from("BW,<>r1,<r2 | F,<>fr1,<fr2"),
|
||||
Opcode.SUB to InstructionFormat.from("BW,<>r1,<v | F,<>fr1,<v"),
|
||||
Opcode.SUBM to InstructionFormat.from("BW,<r1,<v | F,<fr1,<v"),
|
||||
Opcode.MULR to InstructionFormat.from("BW,<>r1,<r2 | F,<>fr1,<fr2"),
|
||||
Opcode.MUL to InstructionFormat.from("BW,<>r1,<v | F,<>fr1,<v"),
|
||||
Opcode.MULM to InstructionFormat.from("BW,<r1,<v | F,<fr1,<v"),
|
||||
Opcode.DIVR to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.DIV to InstructionFormat.from("BW,<>r1,<v"),
|
||||
Opcode.DIVM to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.DIVSR to InstructionFormat.from("BW,<>r1,<r2 | F,<>fr1,<fr2"),
|
||||
Opcode.DIVS to InstructionFormat.from("BW,<>r1,<v | F,<>fr1,<v"),
|
||||
Opcode.DIVSM to InstructionFormat.from("BW,<r1,<v | F,<fr1,<v"),
|
||||
Opcode.SQRT to InstructionFormat.from("BW,>r1,<r2 | F,>fr1,<fr2"),
|
||||
Opcode.SGN to InstructionFormat.from("BW,>r1,<r2 | F,>fr1,<fr2"),
|
||||
Opcode.RND to InstructionFormat.from("BW,>r1 | F,>fr1"),
|
||||
Opcode.MODR to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.MOD to InstructionFormat.from("BW,<>r1,<v"),
|
||||
Opcode.CMP to InstructionFormat.from("BW,<r1,<r2"),
|
||||
Opcode.EXT to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.EXTS to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.ANDR to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.AND to InstructionFormat.from("BW,<>r1,<v"),
|
||||
Opcode.ANDM to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.ORR to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.OR to InstructionFormat.from("BW,<>r1,<v"),
|
||||
Opcode.ORM to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.XORR to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.XOR to InstructionFormat.from("BW,<>r1,<v"),
|
||||
Opcode.XORM to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.INV to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.INVM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.ASRN to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.ASRNM to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.LSRN to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.LSRNM to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.LSLN to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.LSLNM to InstructionFormat.from("BW,<r1,<v"),
|
||||
Opcode.ASR to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.ASRM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.LSR to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.LSRM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.LSL to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.LSLM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.ROR to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.RORM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.ROXR to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.ROXRM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.ROL to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.ROLM to InstructionFormat.from("BW,<v"),
|
||||
Opcode.ROXL to InstructionFormat.from("BW,<>r1"),
|
||||
Opcode.ROXLM to InstructionFormat.from("BW,<v"),
|
||||
|
||||
Opcode.FFROMUB to InstructionFormat.from("F,fr1,r1"),
|
||||
Opcode.FFROMSB to InstructionFormat.from("F,fr1,r1"),
|
||||
Opcode.FFROMUW to InstructionFormat.from("F,fr1,r1"),
|
||||
Opcode.FFROMSW to InstructionFormat.from("F,fr1,r1"),
|
||||
Opcode.FTOUB to InstructionFormat.from("F,r1,fr1"),
|
||||
Opcode.FTOSB to InstructionFormat.from("F,r1,fr1"),
|
||||
Opcode.FTOUW to InstructionFormat.from("F,r1,fr1"),
|
||||
Opcode.FTOSW to InstructionFormat.from("F,r1,fr1"),
|
||||
Opcode.FPOW to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FABS to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FSIN to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FCOS to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FTAN to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FATAN to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FLN to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FLOG to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FSQRT to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FROUND to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FFLOOR to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FCEIL to InstructionFormat.from("F,fr1,fr2"),
|
||||
Opcode.FCOMP to InstructionFormat.from("F,r1,fr1,fr2"),
|
||||
Opcode.FFROMUB to InstructionFormat.from("F,>fr1,<r1"),
|
||||
Opcode.FFROMSB to InstructionFormat.from("F,>fr1,<r1"),
|
||||
Opcode.FFROMUW to InstructionFormat.from("F,>fr1,<r1"),
|
||||
Opcode.FFROMSW to InstructionFormat.from("F,>fr1,<r1"),
|
||||
Opcode.FTOUB to InstructionFormat.from("F,>r1,<fr1"),
|
||||
Opcode.FTOSB to InstructionFormat.from("F,>r1,<fr1"),
|
||||
Opcode.FTOUW to InstructionFormat.from("F,>r1,<fr1"),
|
||||
Opcode.FTOSW to InstructionFormat.from("F,>r1,<fr1"),
|
||||
Opcode.FPOW to InstructionFormat.from("F,<>fr1,<fr2"),
|
||||
Opcode.FABS to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FCOMP to InstructionFormat.from("F,>r1,<fr1,<fr2"),
|
||||
Opcode.FSIN to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FCOS to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FTAN to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FATAN to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FLN to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FLOG to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FROUND to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FFLOOR to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
Opcode.FCEIL to InstructionFormat.from("F,>fr1,<fr2"),
|
||||
|
||||
Opcode.MSIG to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.PUSH to InstructionFormat.from("BW,r1"),
|
||||
Opcode.POP to InstructionFormat.from("BW,r1"),
|
||||
Opcode.CONCAT to InstructionFormat.from("BW,r1,r2"),
|
||||
Opcode.MSIG to InstructionFormat.from("BW,>r1,<r2"),
|
||||
Opcode.PUSH to InstructionFormat.from("BW,<r1"),
|
||||
Opcode.POP to InstructionFormat.from("BW,>r1"),
|
||||
Opcode.CONCAT to InstructionFormat.from("BW,<>r1,<r2"),
|
||||
Opcode.CLC to InstructionFormat.from("N"),
|
||||
Opcode.SEC to InstructionFormat.from("N"),
|
||||
Opcode.BREAKPOINT to InstructionFormat.from("N"),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.vm
|
||||
|
||||
import prog8.code.core.AssemblyError
|
||||
import kotlin.math.min
|
||||
|
||||
/*
|
||||
@@ -268,7 +269,7 @@ object SysCalls {
|
||||
val comparison = first.compareTo(second)
|
||||
vm.registers.setSB(0, comparison.toByte())
|
||||
}
|
||||
else -> TODO("syscall ${call.name}")
|
||||
else -> throw AssemblyError("missing syscall ${call.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,6 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
|
||||
Opcode.FATAN -> InsFATAN(ins)
|
||||
Opcode.FLN -> InsFLN(ins)
|
||||
Opcode.FLOG -> InsFLOG(ins)
|
||||
Opcode.FSQRT -> InsFSQRT(ins)
|
||||
Opcode.FROUND -> InsFROUND(ins)
|
||||
Opcode.FFLOOR -> InsFFLOOR(ins)
|
||||
Opcode.FCEIL -> InsFCEIL(ins)
|
||||
@@ -1283,7 +1282,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
|
||||
private fun InsEXT(i: Instruction) {
|
||||
when(i.type!!){
|
||||
VmDataType.BYTE -> registers.setUW(i.reg1!!, registers.getUB(i.reg1).toUShort())
|
||||
VmDataType.WORD -> TODO("ext.w not yet supported, requires 32 bits registers")
|
||||
VmDataType.WORD -> throw IllegalArgumentException("ext.w not yet supported, requires 32 bits registers")
|
||||
VmDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
pc++
|
||||
@@ -1292,7 +1291,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
|
||||
private fun InsEXTS(i: Instruction) {
|
||||
when(i.type!!){
|
||||
VmDataType.BYTE -> registers.setSW(i.reg1!!, registers.getSB(i.reg1).toShort())
|
||||
VmDataType.WORD -> TODO("exts.w not yet supported, requires 32 bits registers")
|
||||
VmDataType.WORD -> throw IllegalArgumentException("exts.w not yet supported, requires 32 bits registers")
|
||||
VmDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
pc++
|
||||
@@ -1761,7 +1760,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
|
||||
val newValue = value.toInt() ushr 8
|
||||
registers.setUB(i.reg1!!, newValue.toUByte())
|
||||
}
|
||||
VmDataType.WORD -> TODO("msig.w not yet supported, requires 32-bits registers")
|
||||
VmDataType.WORD -> throw IllegalArgumentException("msig.w not yet supported, requires 32-bits registers")
|
||||
VmDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
pc++
|
||||
@@ -1774,7 +1773,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
|
||||
val msb = registers.getUB(i.reg2!!)
|
||||
registers.setUW(i.reg1, ((msb.toInt() shl 8) or lsb.toInt()).toUShort())
|
||||
}
|
||||
VmDataType.WORD -> TODO("concat.w not yet supported, requires 32-bits registers")
|
||||
VmDataType.WORD -> throw IllegalArgumentException("concat.w not yet supported, requires 32-bits registers")
|
||||
VmDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
pc++
|
||||
@@ -1887,12 +1886,6 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
|
||||
pc++
|
||||
}
|
||||
|
||||
private fun InsFSQRT(i: Instruction) {
|
||||
val value = registers.getFloat(i.fpReg2!!)
|
||||
registers.setFloat(i.fpReg1!!, sqrt(value))
|
||||
pc++
|
||||
}
|
||||
|
||||
private fun InsFCOMP(i: Instruction) {
|
||||
val left = registers.getFloat(i.fpReg1!!)
|
||||
val right = registers.getFloat(i.fpReg2!!)
|
||||
|
||||
@@ -2,10 +2,7 @@ import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.vm.Instruction
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
import prog8.vm.instructionFormats
|
||||
import prog8.vm.*
|
||||
|
||||
|
||||
class TestInstructions: FunSpec({
|
||||
@@ -18,6 +15,8 @@ class TestInstructions: FunSpec({
|
||||
ins.reg2 shouldBe null
|
||||
ins.value shouldBe null
|
||||
ins.labelSymbol shouldBe null
|
||||
ins.reg1direction shouldBe OperandDirection.INPUT
|
||||
ins.fpReg1direction shouldBe OperandDirection.INPUT
|
||||
ins.toString() shouldBe "nop"
|
||||
}
|
||||
|
||||
@@ -29,6 +28,8 @@ class TestInstructions: FunSpec({
|
||||
ins.reg2 shouldBe null
|
||||
ins.value shouldBe 9999
|
||||
ins.labelSymbol shouldBe null
|
||||
ins.reg1direction shouldBe OperandDirection.INPUT
|
||||
ins.fpReg1direction shouldBe OperandDirection.INPUT
|
||||
ins.toString() shouldBe "bz.b r42,9999"
|
||||
}
|
||||
|
||||
@@ -40,9 +41,36 @@ class TestInstructions: FunSpec({
|
||||
ins.reg2 shouldBe null
|
||||
ins.value shouldBe null
|
||||
ins.labelSymbol shouldBe listOf("a","b","c")
|
||||
ins.reg1direction shouldBe OperandDirection.INPUT
|
||||
ins.fpReg1direction shouldBe OperandDirection.INPUT
|
||||
ins.toString() shouldBe "bz.w r11,_a.b.c"
|
||||
}
|
||||
|
||||
test("with output registers") {
|
||||
val ins = Instruction(Opcode.ADDR, VmDataType.WORD, reg1=11, reg2=22)
|
||||
ins.opcode shouldBe Opcode.ADDR
|
||||
ins.type shouldBe VmDataType.WORD
|
||||
ins.reg1 shouldBe 11
|
||||
ins.reg2 shouldBe 22
|
||||
ins.value shouldBe null
|
||||
ins.labelSymbol shouldBe null
|
||||
ins.reg1direction shouldBe OperandDirection.INOUT
|
||||
ins.fpReg1direction shouldBe OperandDirection.INPUT
|
||||
ins.toString() shouldBe "addr.w r11,r22"
|
||||
|
||||
val ins2 = Instruction(Opcode.SQRT, VmDataType.BYTE, reg1=11, reg2=22)
|
||||
ins2.opcode shouldBe Opcode.SQRT
|
||||
ins2.type shouldBe VmDataType.BYTE
|
||||
ins2.reg1 shouldBe 11
|
||||
ins2.reg2 shouldBe 22
|
||||
ins2.value shouldBe null
|
||||
ins2.labelSymbol shouldBe null
|
||||
ins2.reg1direction shouldBe OperandDirection.OUTPUT
|
||||
ins2.fpReg1direction shouldBe OperandDirection.INPUT
|
||||
ins2.toString() shouldBe "sqrt.b r11,r22"
|
||||
}
|
||||
|
||||
|
||||
test("missing type should fail") {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
Instruction(Opcode.BZ, reg1=42, value=9999)
|
||||
|
||||
Reference in new issue
Block a user