Compare commits

..
53 Commits
Author SHA1 Message Date
Irmen de Jong aa949165c7 diskio.f_open_w() error handling back to what it was before
Otherwise it eats the status message. Added comment that you have to check this manually to be sure if the call succeeded or not!
2023-11-12 21:14:06 +01:00
Irmen de Jong d22359b6e7 removed cx16.FB_cursor_position2() because it was only for use in the graphics module 2023-11-12 16:40:13 +01:00
Irmen de Jong d73709653d remove unused interned strings in the resulting code (for example from removed if/else blocks) 2023-11-12 05:28:24 +01:00
Irmen de Jong 405926e811 oops 2023-11-11 14:31:48 +01:00
Irmen de Jong 36758f41a4 fixed diskio.f_open_w() error handling, finally added f_seek_w() to be able to seek in files for writing. 2023-11-11 14:26:40 +01:00
Irmen de Jong 7ebc9c79cf added string.append()
cleanup redundant diskio prefixes
2023-11-10 23:53:59 +01:00
Irmen de Jong e0668b55b9 fix gfx2 safe_disc coloring 2023-11-10 01:08:13 +01:00
Irmen de Jong 76c09da961 make pokemon() be like poke, but also return the old value in the memory location. 2023-11-09 22:48:44 +01:00
Irmen de Jong 7e3b8c2c59 fix compiler crash on certain subroutine inlining attempts. 2023-11-09 21:16:12 +01:00
Irmen de Jong ecca854c7c Added cx16.edkeyvec and cx16.set_chrin_keyhandler(). mention the Github actions builds. 2023-11-09 01:03:31 +01:00
Irmen de Jong 3b0d7ea960 better const-evaluation of addressOf a memory mapped variable 2023-11-08 22:04:41 +01:00
Irmen de Jong f70fa42eac more accurate palette conversion 2023-11-08 01:33:55 +01:00
markjreed 5698de6cf4 feat: requirements.txt for convertsprite.py (#112) 2023-11-08 01:32:41 +01:00
Irmen de Jong c5a333a904 CX16: diskio.f_write() now uses fast MCIOUT block writes, including hiram bank boundary wrap-over 2023-11-08 01:12:49 +01:00
markjreed ff324955dd Feature/read cursor position (#111)
* feat: add ability to read cursor position on CBM machines

* feat: implement plot()/column() for atari target; add get_cursor(), get_column(), row(), and get_row()

* feat: implement wait_key() for Commodore targets; add get_cursor(), get_column(), row(), get_row()

* feat: really implement waitkey() on CBM targets

* fix: make waitkey void for compatibility with atari
2023-11-07 22:19:16 +01:00
Irmen de Jong 70436f5dca cx16.vpeek() use VERA_DATA0 instead of 1, to not cause ADDRSEL to be != 0 (interferes with kernal) 2023-11-07 22:09:53 +01:00
Irmen de Jong 31177a2b1b added sys.disable_caseswitch() and sys.enable_caseswitch() 2023-11-07 00:27:34 +01:00
Irmen de Jong 4de012fc49 added notes to textio about PETSCII vs Screencode encoding. 2023-11-06 23:18:24 +01:00
Irmen de Jong ee2888e744 verafx.mult/muls now return upper 16 bits of the result in r0 2023-11-06 21:55:58 +01:00
Irmen de Jong efe4df92dc optimize when with const value (remove other choices from code) 2023-11-06 00:08:07 +01:00
Irmen de Jong 723ab54f97 optimized all circle routines a little more. Added gfx2/monogfx safe_circle and safe_disc. Warning for when on const value. 2023-11-05 21:29:59 +01:00
Irmen de Jong d9389afc66 fix compiler crash on certain constant expressions 2023-11-05 13:59:08 +01:00
Irmen de Jong e7178ee496 optimized comparison with word variables 2023-11-05 00:20:12 +01:00
Irmen de Jong d5f35bb3fb added gfx2.init_mode() 2023-11-04 14:53:08 +01:00
Irmen de Jong 72f1a779f2 optimize monogfx.fill() and gfx2.fill(), also don't read outside screen area 2023-11-04 14:30:51 +01:00
Irmen de Jong 3277544295 optimize assigning word array value to byte variable 2023-11-04 00:33:50 +01:00
Irmen de Jong 98d2c64d5d fix assembly error for uword[3] @zp @split word_addrs 2023-11-03 00:39:43 +01:00
Irmen de Jong f68b46fc60 add a %zpallowed option to specify the range of zeropage register that can be used 2023-11-03 00:19:25 +01:00
Irmen de Jong d54ab856e7 fix parameter passing bug introduced recently (byte not converted to word) 2023-11-02 00:31:35 +01:00
Irmen de Jong 16b24fadea gfx2 future mode, upgrate to Kotlin 1.9.20 2023-11-01 23:18:44 +01:00
Irmen de Jong b3803cbdf1 more opportunities to use LDA(zp) instead of LDA(zp),Y on 65c02 2023-10-31 21:26:55 +01:00
Irmen de Jong 2ceaa25181 optimized code for (infrequently used) logical operations on word array 2023-10-29 23:41:34 +01:00
Irmen de Jong 513611c5a6 IR: using EXT more 2023-10-29 02:57:21 +01:00
Irmen de Jong 7ec4ba40ad optimize asmsub arg evaluation order and stack usage 2023-10-28 17:29:00 +02:00
Irmen de Jong 92374e122b IR: optimize concat with msb 0 into ext 2023-10-28 12:53:41 +02:00
Irmen de Jong 94f12732ab add math.diff() and math.diffw() 2023-10-27 22:36:43 +02:00
Irmen de Jong 0904712a00 remove last trace of getTempVar (arry index expression)
tiny optimization
2023-10-27 21:41:52 +02:00
Irmen de Jong 32becdbced add monogfx lib to virtual target 2023-10-24 00:16:25 +02:00
Irmen de Jong 34aa21f7d9 improve function call arg type casting 2023-10-22 22:33:35 +02:00
Irmen de Jong cc81dd7d3e remove useless close calls from diskio load 2023-10-22 17:24:05 +02:00
Irmen de Jong 335213b55f tweaks 2023-10-21 02:16:58 +02:00
Irmen de Jong 13ab4166c0 new kotest library version 2023-10-19 21:57:06 +02:00
Irmen de Jong 3dc5a0e7f8 some arrays can be in BSS 2023-10-18 23:59:37 +02:00
Irmen de Jong e15c5cde53 tiny fill() optimization 2023-10-18 23:11:16 +02:00
Irmen de Jong d88c09b098 fix signed byte to word casting issue uw = 8888 + (bb as ubyte) 2023-10-17 22:54:33 +02:00
Irmen de Jong 893b383bdf fix signed byte to word sign extension in assignment 2023-10-17 03:08:37 +02:00
Irmen de Jong dd7c9d62e6 remove assigment splitter, it now caused code bloat instead of more efficient code 2023-10-16 02:07:22 +02:00
Irmen de Jong 97c5c90eff fix codegen for var1>>=var2 and var1<<=var2 when var2 is zero 2023-10-16 00:04:21 +02:00
Irmen de Jong 1fb94e7a7b monogfx and gfx2: flood fill uses optimized horizontal line drawing 2023-10-15 23:19:11 +02:00
Irmen de Jong daca87c6d0 added -breakinstr compiler option 2023-10-15 21:55:09 +02:00
Irmen de Jong 203ec5fa46 implement taking address of array var with variable index 2023-10-15 20:24:48 +02:00
Irmen de Jong 9ea69c07b8 optimize word array reads with indexvar 2023-10-14 07:30:54 +02:00
Irmen de Jong 68539d6cc9 micro tweaks adpcm.p8 2023-10-13 00:55:56 +02:00
118 changed files with 3159 additions and 1347 deletions

No files matched your search

+1 -1
View File
@@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0-release-358" />
<option name="version" value="1.9.20" />
</component>
</project>
+6 -6
View File
@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.6.2" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.7.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
</CLASSES>
+13 -13
View File
@@ -1,29 +1,29 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.6.2" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.7.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.6.2/kotest-runner-junit5-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.6.2/kotest-framework-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.7.2/kotest-runner-junit5-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.7.2/kotest-framework-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.6.2/kotest-framework-engine-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.157/classgraph-4.8.157.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.7.2/kotest-framework-engine-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.6.2/kotest-framework-discovery-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.7.2/kotest-framework-discovery-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.6.2/kotest-extensions-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.6.2/kotest-framework-concurrency-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.7.2/kotest-extensions-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.7.2/kotest-framework-concurrency-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
@@ -121,8 +121,8 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
@@ -8,6 +8,7 @@ class CompilationOptions(val output: OutputType,
val launcher: CbmPrgLauncherType,
val zeropage: ZeropageType,
val zpReserved: List<UIntRange>,
val zpAllowed: List<UIntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget,
@@ -21,10 +22,15 @@ class CompilationOptions(val output: OutputType,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,
var splitWordArrays: Boolean = false,
var breakpointCpuInstruction: Boolean = false,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
) {
init {
compTarget.machine.initializeMemoryAreas(this)
}
companion object {
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
}
}
@@ -76,6 +76,13 @@ enum class RegisterOrPair {
companion object {
val names by lazy { values().map { it.toString()} }
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
return when(cpu) {
CpuRegister.A -> A
CpuRegister.X -> X
CpuRegister.Y -> Y
}
}
}
fun asCpuRegister(): CpuRegister = when(this) {
@@ -152,6 +159,7 @@ val ElementToArrayTypes = mapOf(
DataType.FLOAT to DataType.ARRAY_F,
DataType.BOOL to DataType.ARRAY_BOOL
)
val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
@@ -159,6 +167,10 @@ val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val CpuRegisters = setOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
)
enum class OutputType {
@@ -42,6 +42,13 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
}
}
fun retainAllowed() {
synchronized(this) {
for(allowed in options.zpAllowed)
free.retainAll { it in allowed }
}
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
fun hasWordAvailable(): Boolean {
@@ -48,6 +48,7 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -69,6 +69,7 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -75,6 +75,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
allocateCx16VirtualRegisters()
}
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -47,8 +47,8 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
}
@@ -50,6 +50,7 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
+1 -1
View File
@@ -30,7 +30,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
}
sourceSets {
@@ -503,24 +503,18 @@ class AsmGen6502Internal (
return
}
val indexVar = expr.index as? PtIdentifier
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.index.position}")
val indexName = asmVariableName(indexVar)
if(expr.splitWords) {
when (register) {
CpuRegister.A -> out(" lda $indexName")
CpuRegister.X -> out(" ldx $indexName")
CpuRegister.Y -> out(" ldy $indexName")
}
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
return
}
when (elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in ByteDatatypes -> {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
}
in WordDatatypes -> {
out(" lda $indexName | asl a")
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
out(" asl a")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
@@ -529,12 +523,13 @@ class AsmGen6502Internal (
}
DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
out("""
lda $indexName
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc $indexName"""
adc P8ZP_SCRATCH_REG"""
)
when (register) {
CpuRegister.A -> {}
@@ -568,7 +563,7 @@ class AsmGen6502Internal (
when(reg) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes)
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes, true)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
@@ -582,6 +577,34 @@ class AsmGen6502Internal (
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
when (target.datatype) {
in ByteDatatypes -> {
if (value.asConstInteger()==0) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${target.asmVarname}")
else
out("stz ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
val address = target.memory!!.address.asConstInteger()
if(address!=null) {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${address.toHex()}")
else
out(" stz ${address.toHex()}")
return
}
}
TargetStorageKind.REGISTER -> {
val zero = PtNumber(DataType.UBYTE, 0.0, value.position)
zero.parent = value
assignExpressionToRegister(zero, target.register!!, false)
return
}
else -> { }
}
}
assignExpressionToRegister(value, RegisterOrPair.A)
assignRegister(RegisterOrPair.A, target)
}
@@ -1026,6 +1049,15 @@ $repeatLabel""")
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
breakpointLabels.add(label)
out(label)
if(options.breakpointCpuInstruction) {
val instruction =
when(options.compTarget.machine.cpu) {
CpuType.CPU6502 -> "brk"
CpuType.CPU65c02 -> "stp"
else -> throw AssemblyError("invalid cpu type")
}
out(" $instruction")
}
}
internal fun signExtendAYlsb(valueDt: DataType) {
@@ -1,9 +1,8 @@
package prog8.codegen.cpu6502
import prog8.code.ast.*
import prog8.code.ast.PtAsmSub
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
@@ -11,50 +10,19 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (X last), except A,
// 3) single CPU registers (order Y,X,A),
// 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
regsWithoutA.forEach {
if(it.value.first.registerOrPair != RegisterOrPair.X)
order += it.index
}
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
rest.forEach { order += it.index }
regA.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
fun asmsub6502ArgsHaveRegisterClobberRisk(
args: List<PtExpression>,
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
): Boolean {
fun isClobberRisk(expr: PtExpression): Boolean {
when (expr) {
is PtArrayIndexer -> {
return params.any {
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is PtBuiltinFunctionCall -> {
if (expr.name == "lsb" || expr.name == "msb")
return isClobberRisk(expr.args[0])
if (expr.name == "mkword")
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple()
}
else -> return !expr.isSimple()
}
}
return args.size>1 && args.any { isClobberRisk(it) }
}
@@ -49,7 +49,16 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"peekw" -> funcPeekW(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokemon" -> { /* meme function */ }
"pokemon" -> {
val memread = PtMemoryByte(fcall.position)
memread.add(fcall.args[0])
memread.parent = fcall
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A, false)
asmgen.out(" pha")
val memtarget = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.position, memory=memread)
asmgen.assignExpressionTo(fcall.args[1], memtarget)
asmgen.out(" pla")
}
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
@@ -110,8 +119,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val var3name = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
val remainderTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[3].position, var3name)
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false)
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false)
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false, false)
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false, false)
}
private fun funcDivmodW(fcall: PtBuiltinFunctionCall) {
@@ -273,11 +282,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when(fcall.args[0].type) {
DataType.UBYTE -> {
asmgen.out(" ldy #0 | jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
}
DataType.UWORD -> {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
@@ -686,7 +695,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
assignAsmGen.assignConstantByte(target, 0)
} else {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false)
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
}
@@ -701,7 +710,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
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, fcall.position, scope, asmgen), CpuRegister.A, true)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
}
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
@@ -714,7 +723,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
in SplitWordArrayTypes -> TODO("split word any/all")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
}
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
@@ -723,7 +732,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when (dt) {
DataType.BYTE -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false, true)
}
DataType.WORD -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
@@ -871,7 +880,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed)
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed, true)
}
in WordDatatypes -> {
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
@@ -887,55 +896,59 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcMin(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
val signed = fcall.type in SignedDatatypes
if(fcall.type in ByteDatatypes) {
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
asmgen.out("""
lda P8ZP_SCRATCH_B1
+""")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
} else if(fcall.type in WordDatatypes) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if(signed) {
when (fcall.type) {
in ByteDatatypes -> {
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bpl +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jmp ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
+""")
lda P8ZP_SCRATCH_B1
+""")
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.A, targetReg)
}
in WordDatatypes -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if(signed) {
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2
tya
sbc P8ZP_SCRATCH_W2+1
bvc +
eor #$80
+ bpl +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
+""")
} else {
asmgen.out("""
lda P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W2+1
bcc ++
bne +
lda P8ZP_SCRATCH_W1
cmp P8ZP_SCRATCH_W2
bcc ++
+ lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jmp ++
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
+""")
}
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
}
else -> {
throw AssemblyError("min float not supported")
}
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
} else {
throw AssemblyError("min float not supported")
}
}
@@ -1111,7 +1124,25 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid reg")
}
} else {
when(resultRegister) {
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
// just read the lsb byte out of the word array
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable)+"_lsb" else asmgen.asmVariableName(arg.variable)
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.Y)
asmgen.out(" lda $arrayVar,y")
}
RegisterOrPair.Y -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.X)
asmgen.out(" lda $arrayVar,x")
}
RegisterOrPair.X -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.Y)
asmgen.out(" ldx $arrayVar,y")
}
else -> throw AssemblyError("invalid reg")
}
} else when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
@@ -69,37 +69,59 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
registerArgsViaCpuStackEvaluation(call, sub)
} else {
asmsub6502ArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
}
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
return when(arg) {
is PtBuiltinFunctionCall -> {
if (arg.name == "lsb" || arg.name == "msb")
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
return !arg.isSimple()
}
is PtAddressOf -> false
is PtIdentifier -> false
is PtMachineRegister -> false
is PtMemoryByte -> false
is PtNumber -> false
else -> true
}
}
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
val registersUsed = mutableListOf<RegisterOrStatusflag>();
if(callee.parameters.isEmpty())
return
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}
fun usedY() = registersUsed.any {it.registerOrPair==RegisterOrPair.Y || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.XY}
// use the cpu hardware stack as intermediate storage for the arguments.
val argOrder = asmsub6502ArgsEvalOrder(callee)
argOrder.reversed().forEach {
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
}
argOrder.forEach {
val param = callee.parameters[it]
asmgen.popCpuStack(callee, param.second, param.first)
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
optimalEvalOrder.forEach {
val param = sub.parameters[it]
val arg = call.args[it]
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
else if(registersUsed.any {it.statusflag!=null}) {
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
}
else {
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(usedX()) asmgen.restoreRegisterStack(CpuRegister.X, true)
used
}
} else {
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
}
}
}
}
@@ -112,7 +134,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
asmgen.assignExpressionToVariable(value, varName, parameter.type)
}
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
// pass argument via a register parameter
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
throw AssemblyError("argument type incompatible")
@@ -156,6 +178,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
return RegisterOrStatusflag(null, statusflag)
}
else {
// via register or register pair
@@ -188,6 +211,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
}
return RegisterOrStatusflag(register, null)
}
}
@@ -209,3 +233,5 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
return false
}
}
@@ -497,7 +497,15 @@ internal class ProgramAndVarsGen(
for ((scopedName, zpvar) in zpVariables) {
if (scopedName.startsWith("cx16.r"))
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
if(variable.dt in SplitWordArrayTypes) {
val lsbAddr = zpvar.address
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
asmgen.out("${scopedName.substringAfterLast('.')}_msb \t= $msbAddr \t; zp ${zpvar.dt} (msbs)")
} else {
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
}
}
}
@@ -23,13 +23,13 @@ internal class AnyExprAsmGen(
return assignByteBinExpr(expr, assign)
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
require(expr.operator in ComparisonOperators)
TODO("words operands comparison -> byte")
throw AssemblyError("words operands comparison -> byte, should have been handled by assignOptimizedComparisonWords()")
}
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
require(expr.operator in ComparisonOperators)
return assignFloatBinExpr(expr, assign)
}
TODO("weird expr operand types")
throw AssemblyError("weird expr operand types: ${expr.left.type} and {${expr.right.type}")
}
in WordDatatypes -> {
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
@@ -68,14 +68,23 @@ internal class AssignmentAsmGen(private val program: PtProgram,
throw AssemblyError("non-array var indexing requires bytes dt")
if(value.type != DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index")
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
if(asmgen.isZpVar(value.variable)) {
asmgen.out(" lda ($arrayVarName),y")
if(asmgen.isTargetCpu(CpuType.CPU65c02) && (value.index as? PtNumber)?.number==0.0) {
if (asmgen.isZpVar(value.variable)) {
asmgen.out(" lda ($arrayVarName)")
} else {
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
asmgen.out(" lda (P8ZP_SCRATCH_W1)")
}
} else {
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
asmgen.out(" lda (P8ZP_SCRATCH_W1),y")
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
if (asmgen.isZpVar(value.variable)) {
asmgen.out(" lda ($arrayVarName),y")
} else {
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
asmgen.out(" lda (P8ZP_SCRATCH_W1),y")
}
}
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes)
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes, false)
return
}
@@ -88,8 +97,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
asmgen.out(" lda ${arrayVarName}_lsb,y | tax | lda ${arrayVarName}_msb,y | tay | txa")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
asmgen.out(" lda ${arrayVarName}_lsb,y | ldx ${arrayVarName}_msb,y")
assignRegisterpairWord(assign.target, RegisterOrPair.AX)
}
return
}
@@ -100,7 +109,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
when (elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue")
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes)
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes, false)
}
in WordDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | ldy $arrayVarName+$indexValue+1")
@@ -118,12 +127,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
in ByteDatatypes -> {
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
asmgen.out(" lda $arrayVarName,y")
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes)
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes, true)
}
in WordDatatypes -> {
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
asmgen.out(" lda $arrayVarName,y | tax | lda $arrayVarName+1,y | tay | txa")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
asmgen.out(" lda $arrayVarName,y | ldx $arrayVarName+1,y")
assignRegisterpairWord(assign.target, RegisterOrPair.AX)
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.A)
@@ -145,7 +154,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
fun assignViaExprEval(expression: PtExpression) {
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, true)
}
val value = assign.source.memory!!
@@ -160,7 +169,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
is PtBinaryExpression -> {
val addrExpr = value.address as PtBinaryExpression
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, true)
} else {
assignViaExprEval(value.address)
}
@@ -214,9 +223,9 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else -> {
// do NOT restore X register before assigning the result values first
when (returnValue.first.registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A, returnValue.second in SignedDatatypes)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X, returnValue.second in SignedDatatypes)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y, returnValue.second in SignedDatatypes)
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A, returnValue.second in SignedDatatypes, true)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X, returnValue.second in SignedDatatypes, true)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y, returnValue.second in SignedDatatypes, true)
RegisterOrPair.AX -> assignVirtualRegister(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignVirtualRegister(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignVirtualRegister(assign.target, RegisterOrPair.XY)
@@ -252,7 +261,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(assign.target.register==null) {
// still need to assign the result to the target variable/etc.
when(returnDt) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A, returnDt in SignedDatatypes) // function's byte result is in A
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A, returnDt in SignedDatatypes, false) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
when (assign.target.datatype) {
@@ -297,7 +306,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
assignRegisterByte(assign.target, CpuRegister.A, signed)
assignRegisterByte(assign.target, CpuRegister.A, signed, false)
} else {
assignExpressionToRegister(value.value, RegisterOrPair.AY, signed)
when(value.operator) {
@@ -367,7 +376,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtContainmentCheck -> {
containmentCheckIntoA(value)
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, true)
}
is PtBinaryExpression -> {
if(!attemptAssignOptimizedBinexpr(value, assign)) {
@@ -406,7 +415,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda cx16.${register.toString().lowercase()}L")
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, false)
}
in WordDatatypes -> assignRegisterpairWord(target, register)
else -> throw AssemblyError("expected byte or word")
@@ -484,7 +493,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(target.register==RegisterOrPair.A)
asmgen.out(" cmp #0") // fix the status register
else
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
return true
}
DataType.UWORD -> {
@@ -504,7 +513,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
asmgen.out(" pla | jsr math.divmod_ub_asm")
assignRegisterByte(target, CpuRegister.Y, false)
assignRegisterByte(target, CpuRegister.Y, false, true)
return true
}
DataType.BYTE -> {
@@ -512,7 +521,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, true)
asmgen.out(" pla | jsr math.divmod_b_asm")
assignRegisterByte(target, CpuRegister.Y, true)
assignRegisterByte(target, CpuRegister.Y, true, true)
return true
}
DataType.UWORD -> {
@@ -540,7 +549,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, expr.type in SignedDatatypes)
asmgen.out(" pla | jsr math.multiply_bytes")
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
return true
}
in WordDatatypes -> {
@@ -578,7 +587,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr math.mul_byte_${value}")
else
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
return true
}
in WordDatatypes -> {
@@ -638,7 +647,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr math.lsr_ubyte_A")
else
asmgen.out(" jsr math.asl_byte_A")
assignRegisterByte(target, CpuRegister.A, signed)
assignRegisterByte(target, CpuRegister.A, signed, true)
return true
} else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
@@ -673,7 +682,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
}
assignRegisterByte(target, CpuRegister.A, signed)
assignRegisterByte(target, CpuRegister.A, signed, true)
return true
}
else -> {
@@ -682,7 +691,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
} else {
asmgen.out(" lda #0")
}
assignRegisterByte(target, CpuRegister.A, signed)
assignRegisterByte(target, CpuRegister.A, signed, true)
return true
}
}
@@ -755,7 +764,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" clc | adc $symname")
else
asmgen.out(" sec | sbc $symname")
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
return true
}
is PtNumber -> {
@@ -764,7 +773,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" clc | adc #${right.number.toHex()}")
else
asmgen.out(" sec | sbc #${right.number.toHex()}")
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
return true
}
else -> {
@@ -795,7 +804,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else
asmgen.out(" sec | sbc $arrayvarname,y")
}
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
} else {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", right.type)
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
@@ -803,7 +812,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
else
asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
}
return true
}
@@ -913,10 +922,34 @@ internal class AssignmentAsmGen(private val program: PtProgram,
is PtTypeCast -> {
val castedValue = right.value
if(right.type in WordDatatypes && castedValue.type in ByteDatatypes && castedValue is PtIdentifier) {
val castedSymname = asmgen.asmVariableName(castedValue)
assignExpressionToRegister(left, RegisterOrPair.AY, dt == DataType.WORD)
if (expr.operator == "+")
asmgen.out(
if(right.type in SignedDatatypes) {
// we need to sign extend, do this via temporary word variable
asmgen.assignExpressionToVariable(right, "P8ZP_SCRATCH_W1", DataType.WORD)
assignExpressionToRegister(left, RegisterOrPair.AY, dt == DataType.WORD)
if(expr.operator=="+") {
asmgen.out("""
clc
adc P8ZP_SCRATCH_W1
tax
tya
adc P8ZP_SCRATCH_W1+1
tay
txa""")
} else if(expr.operator=="-") {
asmgen.out("""
sec
sbc P8ZP_SCRATCH_W1
tax
tya
sbc P8ZP_SCRATCH_W1+1
tay
txa""")
}
} else {
assignExpressionToRegister(left, RegisterOrPair.AY, dt == DataType.WORD)
val castedSymname = asmgen.asmVariableName(castedValue)
if (expr.operator == "+")
asmgen.out(
"""
clc
adc $castedSymname
@@ -924,8 +957,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
iny
+"""
)
else
asmgen.out(
else
asmgen.out(
"""
sec
sbc $castedSymname
@@ -933,6 +966,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
dey
+"""
)
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
@@ -971,7 +1005,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
return true
}
else if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
@@ -1010,7 +1044,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else -> return false
}
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, true)
return true
}
@@ -1380,10 +1414,24 @@ internal class AssignmentAsmGen(private val program: PtProgram,
private fun assignOptimizedComparisonWords(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
val signed = expr.left.type == DataType.WORD || expr.right.type == DataType.WORD
when(expr.operator) {
"==" -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
if(expr.left is PtIdentifier) {
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
asmgen.out("""
cmp $varName
bne +
cpy $varName+1
bne +
lda #1
bne ++
+ lda #0
+""")
} else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
bne +
cpy P8ZP_SCRATCH_W1+1
@@ -1392,10 +1440,24 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
}
"!=" -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
if(expr.left is PtIdentifier) {
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
asmgen.out("""
cmp $varName
bne +
cpy $varName+1
bne +
lda #0
beq ++
+ lda #1
+""")
} else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
bne +
cpy P8ZP_SCRATCH_W1+1
@@ -1404,11 +1466,39 @@ internal class AssignmentAsmGen(private val program: PtProgram,
beq ++
+ lda #1
+""")
}
}
"<" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
if(expr.right is PtIdentifier) {
val varName = asmgen.asmVariableName(expr.right as PtIdentifier)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.AY)
if(signed)
asmgen.out("""
cmp $varName
tya
sbc $varName+1
bvc +
eor #${'$'}80
+ bpl ++
+ lda #1
bne ++
+ lda #0
+""")
else
asmgen.out("""
cpy $varName+1
bcc +
bne ++
cmp $varName
bcs ++
+ lda #1
bne ++
+ lda #0
+""")
} else {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
@@ -1419,10 +1509,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc +
bne ++
@@ -1432,12 +1522,40 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
}
}
"<=" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
if(expr.left is PtIdentifier) {
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
if(signed)
asmgen.out("""
cmp $varName
tya
sbc $varName+1
bvc +
eor #${'$'}80
+ bmi +
lda #1
bne ++
+ lda #0
+""")
else
asmgen.out("""
cpy $varName+1
bcc ++
bne +
cmp $varName
bcc ++
+ lda #1
bne ++
+ lda #0
+""")
} else {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
@@ -1448,10 +1566,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc ++
bne +
@@ -1461,12 +1579,40 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
}
}
">" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
if(expr.left is PtIdentifier) {
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
if(signed)
asmgen.out("""
cmp $varName
tya
sbc $varName+1
bvc +
eor #${'$'}80
+ bpl ++
+ lda #1
bne ++
+ lda #0
+""")
else
asmgen.out("""
cpy $varName+1
bcc +
bne ++
cmp $varName
bcs ++
+ lda #1
bne ++
+ lda #0
+""")
} else {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
@@ -1477,10 +1623,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc +
bne ++
@@ -1490,12 +1636,40 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
}
}
">=" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
if(expr.right is PtIdentifier) {
val varName = asmgen.asmVariableName(expr.right as PtIdentifier)
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.AY)
if(signed)
asmgen.out("""
cmp $varName
tya
sbc $varName+1
bvc +
eor #${'$'}80
+ bmi +
lda #1
bne ++
+ lda #0
+""")
else
asmgen.out("""
cpy $varName+1
bcc ++
bne +
cmp $varName
bcc ++
+ lda #1
bne ++
+ lda #0
+""")
} else {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
@@ -1506,10 +1680,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc ++
bne +
@@ -1519,12 +1693,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
bne ++
+ lda #0
+""")
}
}
}
else -> return false
}
assignRegisterByte(assign.target, CpuRegister.A, signed)
assignRegisterByte(assign.target, CpuRegister.A, signed, true)
return true
}
@@ -1541,7 +1716,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
"^", "xor" -> asmgen.out(" eor $operand")
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
}
private fun assignLogicalWithSimpleRightOperandWord(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
@@ -1580,7 +1755,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
beq +
lda #1
+ eor #1""")
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, false)
return true
}
in WordDatatypes -> {
@@ -1591,13 +1766,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
beq +
lda #1
+ eor #1""")
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, false)
return true
}
DataType.FLOAT -> {
assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
asmgen.out(" jsr floats.SIGN | and #1 | eor #1")
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, false)
return true
}
else->{
@@ -1611,7 +1786,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
asmgen.out(" beq + | lda #1")
asmgen.out("+")
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, false)
return true
}
in WordDatatypes -> {
@@ -1619,13 +1794,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
asmgen.out(" beq + | lda #1")
asmgen.out("+")
assignRegisterByte(assign.target, CpuRegister.A, false)
assignRegisterByte(assign.target, CpuRegister.A, false, false)
return true
}
DataType.FLOAT -> {
assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
asmgen.out(" jsr floats.SIGN")
assignRegisterByte(assign.target, CpuRegister.A, true)
assignRegisterByte(assign.target, CpuRegister.A, true, false)
return true
}
else->{
@@ -1697,7 +1872,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
else -> throw AssemblyError("can't use Z or N flags as return 'values'")
}
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
}
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: PtExpression, origTypeCastExpression: PtTypeCast) {
@@ -1753,6 +1928,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
is PtNumber -> throw AssemblyError("a cast of a literal value should have been const-folded away")
is PtArrayIndexer -> {
if(targetDt in ByteDatatypes && valueDt in WordDatatypes) {
// just assign the lsb from the array value
return assignCastViaLsbFunc(value, target)
}
}
else -> {}
}
@@ -1805,8 +1986,9 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
return assignExpressionToRegister(value, target.register!!, false)
assignExpressionToRegister(value, RegisterOrPair.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
else -> {}
}
@@ -1844,13 +2026,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignCastViaLsbFunc(value, target)
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
// word to word, just assign
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
assignExpressionToRegister(value, target.register!!, valueDt in SignedDatatypes)
} else if(valueDt in ByteDatatypes && targetDt in ByteDatatypes) {
// byte to byte, just assign
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
assignExpressionToRegister(value, target.register!!, valueDt in SignedDatatypes)
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
// byte to word, just assign
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
assignExpressionToRegister(value, target.register!!, valueDt==DataType.WORD)
} else
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
}
@@ -2209,7 +2391,27 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else
program.memsizer.memorySize(arrayDt!!, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
} else {
TODO("address-of array element $sourceName with non-const index at ${target.position}")
val eltSize = program.memsizer.memorySize(ArrayToElementTypes.getValue(arrayDt!!))
assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_W1", DataType.UWORD)
when(eltSize) {
1 -> {}
2 -> {
if(arrayDt !in SplitWordArrayTypes)
asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
}
else -> TODO("address-of array element $sourceName size $eltSize with non-const index at ${target.position}")
}
asmgen.out("""
lda #<$sourceName
clc
adc P8ZP_SCRATCH_W1
tax
lda #>$sourceName
adc P8ZP_SCRATCH_W1+1
tay
txa""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
} else 0
@@ -2279,15 +2481,22 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun assignVariableWord(target: AsmAssignTarget, sourceName: String, sourceDt: DataType) {
if(sourceDt==DataType.BYTE) {
// need to sign extend
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(DataType.BYTE)
assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
require(sourceDt in WordDatatypes || sourceDt==DataType.UBYTE)
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(sourceDt==DataType.UBYTE) {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}")
asmgen.out(" stz ${target.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${target.asmVarname}")
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
}
else
asmgen.out("""
@@ -2322,16 +2531,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
lda $sourceName+1
sta ${target.asmVarname}+$scaledIdx+1""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<(${target.asmVarname}+$scaledIdx)
ldy #>(${target.asmVarname}+$scaledIdx)
jsr floats.copy_float""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
}
}
@@ -2357,20 +2556,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
lda $sourceName+1
sta ${target.asmVarname}+1,y""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
ldy #>$sourceName
sty P8ZP_SCRATCH_W1+1
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.copy_float""")
}
else -> throw AssemblyError("weird dt")
}
}
@@ -2732,13 +2917,33 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister, signed: Boolean) {
private fun extendToMSBofVirtualReg(cpuRegister: CpuRegister, vreg: String, signed: Boolean) {
if(signed) {
when(cpuRegister) {
CpuRegister.A -> { }
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out("""
ora #$7f
bmi +
lda #0
+ sta $vreg+1""")
} else {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $vreg+1")
else
asmgen.out(" lda #0 | sta $vreg+1")
}
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister, signed: Boolean, extendWord: Boolean) {
val assignAsWord = target.datatype in WordDatatypes
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" st${register.name.lowercase()} ${target.asmVarname}")
if(assignAsWord) {
if(assignAsWord && extendWord) {
if(target.datatype in SignedDatatypes) {
if(register!=CpuRegister.A)
asmgen.out(" t${register.name.lowercase()}a")
@@ -2763,7 +2968,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
TargetStorageKind.ARRAY -> {
if(assignAsWord)
TODO("assign register byte as word into Array not yet supported")
assignRegisterByteToArray(target, register)
assignRegisterByteToByteArray(target, register)
}
TargetStorageKind.REGISTER -> {
when(register) {
@@ -2772,6 +2977,9 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.X -> { asmgen.out(" tax") }
RegisterOrPair.Y -> { asmgen.out(" tay") }
RegisterOrPair.AY -> {
require(extendWord) {
"no extend"
}
if(signed)
asmgen.out("""
ldy #0
@@ -2783,6 +2991,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldy #0")
}
RegisterOrPair.AX -> {
require(extendWord)
if(signed)
asmgen.out("""
ldx #0
@@ -2794,6 +3003,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldx #0")
}
RegisterOrPair.XY -> {
require(extendWord)
if(signed)
asmgen.out("""
tax
@@ -2807,8 +3017,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sta cx16.${target.register.toString().lowercase()}")
val reg = "cx16.${target.register.toString().lowercase()}"
asmgen.out(" sta $reg")
if(extendWord)
extendToMSBofVirtualReg(CpuRegister.A, reg, signed)
}
else -> throw AssemblyError("weird register")
}
@@ -2817,6 +3029,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.X -> { }
RegisterOrPair.Y -> { asmgen.out(" txy") }
RegisterOrPair.AY -> {
require(extendWord)
if(signed)
asmgen.out("""
txa
@@ -2829,6 +3042,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" txa | ldy #0")
}
RegisterOrPair.AX -> {
require(extendWord)
if(signed)
asmgen.out("""
txa
@@ -2841,6 +3055,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" txa | ldx #0")
}
RegisterOrPair.XY -> {
require(extendWord)
if(signed)
asmgen.out("""
ldy #0
@@ -2853,8 +3068,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" stx cx16.${target.register.toString().lowercase()}")
val reg = "cx16.${target.register.toString().lowercase()}"
asmgen.out(" stx $reg")
if(extendWord)
extendToMSBofVirtualReg(CpuRegister.X, reg, signed)
}
else -> throw AssemblyError("weird register")
}
@@ -2863,6 +3080,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.X -> { asmgen.out(" tyx") }
RegisterOrPair.Y -> { }
RegisterOrPair.AY -> {
require(extendWord)
if(signed)
asmgen.out("""
tya
@@ -2875,6 +3093,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" tya | ldy #0")
}
RegisterOrPair.AX -> {
require(extendWord)
if(signed)
asmgen.out("""
tya
@@ -2887,6 +3106,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" tya | ldx #0")
}
RegisterOrPair.XY -> {
require(extendWord)
if(signed)
asmgen.out("""
tya
@@ -2901,8 +3121,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sty cx16.${target.register.toString().lowercase()}")
val reg = "cx16.${target.register.toString().lowercase()}"
asmgen.out(" sty $reg")
if(extendWord)
extendToMSBofVirtualReg(CpuRegister.Y, reg, signed)
}
else -> throw AssemblyError("weird register")
}
@@ -2911,7 +3133,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignRegisterByteToArray(target: AsmAssignTarget, register: CpuRegister) {
private fun assignRegisterByteToByteArray(target: AsmAssignTarget, register: CpuRegister) {
if(target.array!!.splitWords)
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
@@ -3515,7 +3737,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
TargetStorageKind.ARRAY -> {
asmgen.out(" lda ${address.toHex()}")
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
}
TargetStorageKind.REGISTER -> when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" lda ${address.toHex()}")
@@ -3549,7 +3771,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
TargetStorageKind.ARRAY -> {
asmgen.loadByteFromPointerIntoA(identifier)
assignRegisterByte(target, CpuRegister.A, false)
assignRegisterByte(target, CpuRegister.A, false, true)
}
TargetStorageKind.REGISTER -> {
asmgen.loadByteFromPointerIntoA(identifier)
@@ -3749,10 +3971,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
else -> {
asmgen.assignExpressionToVariable(memory.address, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
eor #255""")
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
lda (P8ZP_SCRATCH_W2)
eor #255""")
} else {
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
eor #255""")
}
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
}
@@ -205,239 +205,251 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
TargetStorageKind.ARRAY -> {
val indexNum = target.array!!.index as? PtNumber
val indexVar = target.array.index as? PtIdentifier
when {
indexNum!=null -> {
val targetVarName = if(target.array.splitWords)
"${target.asmVarname} + ${indexNum.number.toInt()}"
else
"${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when (target.datatype) {
in ByteDatatypes -> {
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression)
} else {
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression!!)
}
if (indexNum!=null) {
val targetVarName = if(target.array.splitWords)
"${target.asmVarname} + ${indexNum.number.toInt()}"
else
"${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when (target.datatype) {
in ByteDatatypes -> {
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, regName(value))
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression)
} else {
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression!!)
}
}
}
in WordDatatypes -> {
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(targetVarName, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!, block)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression, block)
} else {
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!, block)
}
}
}
}
DataType.FLOAT -> {
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into float array")
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationFloatWithValue(targetVarName, operator, value.expression)
} else {
inplacemodificationFloatWithValue(targetVarName, operator, value.expression!!)
}
}
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
in WordDatatypes -> {
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(targetVarName, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!, block)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression, block)
} else {
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!, block)
}
}
}
}
DataType.FLOAT -> {
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into float array")
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationFloatWithValue(targetVarName, operator, value.expression)
} else {
inplacemodificationFloatWithValue(targetVarName, operator, value.expression!!)
}
}
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
indexVar!=null -> {
when (target.datatype) {
in ByteDatatypes -> {
if(value.kind==SourceStorageKind.EXPRESSION
&& value.expression is PtTypeCast
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
}
else {
when (target.datatype) {
in ByteDatatypes -> {
if(value.kind==SourceStorageKind.EXPRESSION
&& value.expression is PtTypeCast
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda ${target.array.variable.name},y")
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
inplacemodificationRegisterAwithVariable(operator, "#${value.number!!.number.toInt()}", target.datatype in SignedDatatypes)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
}
SourceStorageKind.VARIABLE -> {
inplacemodificationRegisterAwithVariable(operator, value.asmVarname, target.datatype in SignedDatatypes)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
}
SourceStorageKind.REGISTER -> {
inplacemodificationRegisterAwithVariable(operator, regName(value), target.datatype in SignedDatatypes)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
}
SourceStorageKind.MEMORY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
if(value.expression is PtTypeCast)
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
else
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
}
asmgen.out(" sta ${target.array.variable.name},y")
}
in WordDatatypes -> {
if(value.kind==SourceStorageKind.EXPRESSION
&& value.expression is PtTypeCast
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
if(target.array.splitWords) {
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
} else {
asmgen.out(" lda ${target.array.variable.name},y")
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
inplacemodificationRegisterAwithVariable(operator, "#${value.number!!.number.toInt()}", target.datatype in SignedDatatypes)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
}
SourceStorageKind.VARIABLE -> {
inplacemodificationRegisterAwithVariable(operator, value.asmVarname, target.datatype in SignedDatatypes)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
}
SourceStorageKind.REGISTER -> {
inplacemodificationRegisterAwithVariable(operator, regName(value), target.datatype in SignedDatatypes)
asmgen.restoreRegisterStack(CpuRegister.Y, true)
}
SourceStorageKind.MEMORY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_B1")
if(value.expression is PtTypeCast)
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
else
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda P8ZP_SCRATCH_B1")
}
}
asmgen.out(" sta ${target.array.variable.name},y")
asmgen.out(" ldx ${target.array.variable.name}+1,y")
}
in WordDatatypes -> {
if(value.kind==SourceStorageKind.EXPRESSION
&& value.expression is PtTypeCast
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y)
asmgen.saveRegisterStack(CpuRegister.Y, false)
if(target.array.splitWords) {
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
} else {
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" ldx ${target.array.variable.name}+1,y")
}
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(!inplacemodificationRegisterAXwithLiteralval(operator, number)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
SourceStorageKind.VARIABLE -> {
if(!inplacemodificationRegisterAXwithVariable(
operator,
value.asmVarname,
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
SourceStorageKind.REGISTER -> {
if(!inplacemodificationRegisterAXwithVariable(
operator,
regName(value),
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
SourceStorageKind.MEMORY -> {
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(!inplacemodificationRegisterAXwithLiteralval(operator, number)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithMemread("P8ZP_SCRATCH_W1", target.datatype, operator, value.memory!!)
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
else
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(target.array.splitWords)
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
else
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
}
DataType.FLOAT -> {
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
pha ; save array ptr lsb
tya
pha ; save array ptr msb
lda #<$tempvar
ldy #>$tempvar
jsr floats.copy_float""")
// calculate on tempvar
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into float")
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
inplacemodificationFloatWithValue(tempvar, operator, value.expression)
} else {
inplacemodificationFloatWithValue(tempvar, operator, value.expression!!)
}
}
}
// copy tempvar back into array
asmgen.out("""
lda #<$tempvar
ldy #>$tempvar
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
pla ; restore array ptr msb
tay
pla ; restore array ptr lsb
jsr floats.copy_float""")
SourceStorageKind.VARIABLE -> {
if(!inplacemodificationRegisterAXwithVariable(
operator,
value.asmVarname,
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
SourceStorageKind.REGISTER -> {
if(!inplacemodificationRegisterAXwithVariable(
operator,
regName(value),
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
SourceStorageKind.MEMORY -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithMemread("P8ZP_SCRATCH_W1", target.datatype, operator, value.memory!!)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
else
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(target.array.splitWords)
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
else
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
}
DataType.FLOAT -> {
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
pha ; save array ptr lsb
tya
pha ; save array ptr msb
lda #<$tempvar
ldy #>$tempvar
jsr floats.copy_float""")
// calculate on tempvar
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("memread into float")
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
inplacemodificationFloatWithValue(tempvar, operator, value.expression)
} else {
inplacemodificationFloatWithValue(tempvar, operator, value.expression!!)
}
}
}
// copy tempvar back into array
asmgen.out("""
lda #<$tempvar
ldy #>$tempvar
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
pla ; restore array ptr msb
tay
pla ; restore array ptr lsb
jsr floats.copy_float""")
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
}
}
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
@@ -445,7 +457,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun inplacemodificationRegisterAXwithVariable(operator: String, variable: String, varDt: DataType): Boolean {
// note: we only optimize addition and subtraction, and these are the same for unsigned or signed.
when(operator) {
"+" -> {
return if(varDt in WordDatatypes) {
@@ -495,12 +506,44 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
true
}
}
else -> return false // TODO optimize more operators, such as the bitwise logical ones? Might need to know if signed
"|" -> {
asmgen.out("""
ora $variable
tay
txa
ora $variable+1
tax
tya
""")
return true
}
"&" -> {
asmgen.out("""
and $variable
tay
txa
and $variable+1
tax
tya
""")
return true
}
"^" -> {
asmgen.out("""
eor $variable
tay
txa
eor $variable+1
tax
tya
""")
return true
}
else -> return false
}
}
private fun inplacemodificationRegisterAXwithLiteralval(operator: String, number: Int): Boolean {
// note: we only optimize addition and subtraction, and these are the same for unsigned or signed.
when(operator) {
"+" -> {
return if(number in -128..255) {
@@ -544,7 +587,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
true
}
}
else -> return false // TODO optimize more operators, such as the bitwise logical ones? Might need to know if signed
"|" -> {
asmgen.out("""
ora #<$number
tay
txa
ora #>$number
tax
tya
""")
return true
}
"&" -> {
asmgen.out("""
and #<$number
tay
txa
and #>$number
tax
tya
""")
return true
}
"^" -> {
asmgen.out("""
eor #<$number
tay
txa
eor #>$number
tax
tya
""")
return true
}
else -> return false
}
}
@@ -918,8 +994,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<<" -> {
asmgen.out("""
tay
beq +
lda $variable
cpy #0
beq +
- asl a
dey
bne -
@@ -929,8 +1006,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(!signed) {
asmgen.out("""
tay
beq +
lda $variable
cpy #0
beq +
- lsr a
dey
bne -
@@ -938,8 +1016,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} else {
asmgen.out("""
tay
beq +
lda $variable
cpy #0
beq +
sta P8ZP_SCRATCH_B1
- asl a
ror P8ZP_SCRATCH_B1
@@ -1899,12 +1978,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl $name
rol $name+1
dey
bne -
ldy $otherName
beq +
- asl $name
rol $name+1
dey
bne -
+""")
}
">>" -> {
+1
View File
@@ -22,6 +22,7 @@ class TestCodegen: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
compTarget = target,
+1 -1
View File
@@ -30,7 +30,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
}
sourceSets {
@@ -33,7 +33,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"peekw" -> funcPeekW(call)
"poke" -> funcPoke(call)
"pokew" -> funcPokeW(call)
"pokemon" -> ExpressionCodeResult.EMPTY // easter egg function
"pokemon" -> funcPokemon(call)
"mkword" -> funcMkword(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
@@ -349,13 +349,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg, reg3 = msbTr.resultReg)
if((call.args[0] as? PtNumber)?.number == 0.0) {
// msb is 0, use EXT
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -547,6 +552,59 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val address = call.args[0]
fun pokeM(result: MutableList<IRCodeChunkBase>, address: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
}
} else {
val tr = exprGen.translateExpression(value)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
}
}
}
fun pokeI(result: MutableList<IRCodeChunkBase>, register: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = register)
}
} else {
val valueTr = exprGen.translateExpression(value)
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = register)
}
}
}
return if(address is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val addressNum = address.number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
}
pokeM(result, addressNum, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
val addressTr = exprGen.translateExpression(address)
addToResult(result, addressTr, addressTr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
}
pokeI(result, addressTr.resultReg, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
}
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(null, null)
@@ -188,18 +188,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val memOffset = (arrayIx.index as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
val tmpRegMsb = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
} else {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val tmpRegMsb = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
}
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
@@ -491,9 +491,9 @@ class IRCodeGen(
val tmpRegLsb = registers.nextFree()
val tmpRegMsb = registers.nextFree()
val concatReg = registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegLsb, reg3=tmpRegMsb)
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
@@ -45,6 +45,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
val indexedInstructions = chunk1.instructions.withIndex()
.map { IndexedValue(it.index, it.value) }
val changed = removeNops(chunk1, indexedInstructions)
|| replaceConcatZeroMsbWithExt(chunk1, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|| removeUselessArithmetic(chunk1, indexedInstructions)
|| removeNeedlessCompares(chunk1, indexedInstructions)
@@ -63,6 +64,22 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
irprog.linkChunks() // re-link
}
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.CONCAT) {
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
val prev = indexedInstructions[idx-1].value
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
chunk.instructions[idx] = IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = ins.reg1, reg2 = ins.reg3)
chunk.instructions.removeAt(idx-1)
changed = true
}
}
}
return changed
}
private fun removeEmptyChunks(sub: IRSubroutine) {
if(sub.chunks.isEmpty())
return
@@ -18,6 +18,7 @@ class TestIRPeepholeOpt: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
emptyList(),
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = true,
compTarget = target,
@@ -19,6 +19,7 @@ class TestVmCodeGen: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
compTarget = target,
@@ -87,7 +87,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
if(expr.operator=="==" && rightconst!=null) {
val leftExpr = expr.left as? BinaryExpression
if(leftExpr!=null) {
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
if(leftExpr?.constValue(program) != null) {
val leftRightConst = leftExpr.right.constValue(program)
if(leftRightConst!=null) {
when (leftExpr.operator) {
@@ -251,7 +251,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
}
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
if(decl.isArray && decl.type==VarDeclType.MEMORY) {
val memaddr = decl.value?.constValue(program)
if(memaddr!=null && memaddr !== decl.value) {
return listOf(IAstModification.SetExpression(
{ decl.value = it }, memaddr, decl
))
}
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
if(decl.isArray){
val arraysize = decl.arraysize
if(arraysize==null) {
@@ -291,8 +300,11 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
// convert the initializer range expression from a range or int, to an actual array.
if(decl.type==VarDeclType.MEMORY)
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
// convert the initializer range expression from a range or int, to an actual array.
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression
@@ -114,6 +114,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
else -> false
}
}
if(subroutine.inline && subroutine.statements.size>1) {
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
}
}
}
super.visit(subroutine)
@@ -430,6 +430,27 @@ class StatementOptimizer(private val program: Program,
}
}
}
val constantValue = whenStmt.condition.constValue(program)?.number
if(constantValue!=null) {
// when condition is a constant
var matchingChoice: WhenChoice? = null
loop@ for(choice in whenStmt.choices) {
for(value in choice.values ?: emptyList()) {
if(value.constValue(program)?.number == constantValue) {
matchingChoice = choice
break@loop
}
}
}
if(matchingChoice==null)
matchingChoice = whenStmt.choices.singleOrNull { it.values==null }
if(matchingChoice!=null) {
// get rid of the whole when-statement and just leave the matching choice
return listOf(IAstModification.ReplaceNode(whenStmt, matchingChoice.statements, parent))
}
}
return noModifications
}
+1 -1
View File
@@ -40,7 +40,7 @@ dependencies {
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
}
configurations.all {
+8
View File
@@ -198,6 +198,14 @@ _longcopy
}}
}
sub disable_caseswitch() {
; no-op
}
sub enable_caseswitch() {
; no-op
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
; TODO
+25 -11
View File
@@ -24,12 +24,24 @@ sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
const uword COLCRS = 85
const uword ROWCRS = 84
sub column(ubyte col) {
; ---- set the cursor on the given column (starting with 0) on the current line
; TODO
%asm {{
rts
}}
pokew(COLCRS, col as uword)
}
sub get_column() -> ubyte {
return peekw(COLCRS) as ubyte
}
sub row(ubyte rownum) {
; ---- set the cursor on the given row (starting with 0) on the current line
@(ROWCRS) = rownum
}
sub get_row() -> ubyte {
return @(ROWCRS)
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
@@ -372,12 +384,14 @@ sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) {
; ---- set cursor at specific position
; TODO
%asm {{
rts
}}
sub plot(ubyte col, ubyte rownum) {
column(col)
row(rownum)
}
sub get_cursor(uword colptr, uword rowptr) {
@(colptr) = get_column()
@(rowptr) = get_row()
}
asmsub width() clobbers(X,Y) -> ubyte @A {
+14
View File
@@ -734,6 +734,20 @@ _longcopy
}}
}
inline asmsub disable_caseswitch() {
%asm {{
lda #$80
sta 247
}}
}
inline asmsub enable_caseswitch() {
%asm {{
lda #0
sta 247
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
+52 -3
View File
@@ -1,4 +1,6 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
; these work with PETSCII encoding instead.
%import syslib
%import conv
@@ -38,6 +40,45 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
@@ -238,10 +279,10 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
@@ -427,7 +468,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
@@ -584,4 +626,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
%asm {{
- jsr cbm.GETIN
beq -
rts
}}
}
}
+14 -9
View File
@@ -1,6 +1,9 @@
; bitmap pixel graphics module for the C64
; only black/white monochrome 320x200 for now
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
; Sets character matrix and bitmap screen memory at a higher memory location $5c00-$7fff
; so that the program itself can be larger without starting to overwrite the graphics memory.
@@ -244,7 +247,8 @@ hline_zero2
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm.
if radius==0
return
ubyte @zp ploty
@@ -273,16 +277,17 @@ hline_zero2
internal_plotx = xcenter - yy
internal_plot(ploty)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
radius--
decisionOver2 += (yy as word -radius)*2+1
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
@@ -295,12 +300,12 @@ hline_zero2
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
radius--
decisionOver2 += (yy as word -radius)*2+1
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
+14
View File
@@ -737,6 +737,20 @@ _longcopy
}}
}
inline asmsub disable_caseswitch() {
%asm {{
lda #$80
sta 657
}}
}
inline asmsub enable_caseswitch() {
%asm {{
lda #0
sta 657
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
+52 -3
View File
@@ -1,4 +1,6 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
; these work with PETSCII encoding instead.
%import syslib
%import conv
@@ -39,6 +41,45 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
@@ -237,10 +278,10 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print null terminated string, in PESCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
@@ -426,7 +467,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
@@ -583,4 +625,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
%asm {{
- jsr cbm.GETIN
beq -
rts
}}
}
}
+76 -32
View File
@@ -1,6 +1,16 @@
; Commander X16 disk drive I/O routines.
; Largely compatible with the default C64 ones, but adds more stuff specific to the X16 as well.
; NOTE: If you experience weird behavior with these routines and you are using them
; in the X16 emulator using HostFs, please try again with an SD-card image instead first.
; It is possible that there are still small differences between HostFS and actual CBM DOS in the emulator.
;
; About the secondary addresses:
; for writes (new files or overwrites), you can use 1 (without the mode string) or 2-14 (with the mode string)
; for reads (existing files) you can use 0 or 2-14 (mode string is optional)
; for modify mode (new files* or existing files), you must use 2-14, and the mode string ,s,m is required
%import textio
%import string
%import syslib
@@ -87,7 +97,7 @@ io_error:
sub diskname() -> uword {
; returns disk label name or 0 if error
cbm.SETNAM(3, "$")
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
@@ -100,12 +110,12 @@ io_error:
; skip up to entry name
}
cx16.r0 = &diskio.list_filename
cx16.r0 = &list_filename
repeat {
@(cx16.r0) = cbm.CHRIN()
if @(cx16.r0)=='"' {
@(cx16.r0) = ' '
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
@(cx16.r0) = 0
cx16.r0--
}
@@ -120,7 +130,7 @@ io_error:
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
return 0
return diskio.list_filename
return list_filename
}
; internal variables for the iterative file lister / loader
@@ -135,7 +145,7 @@ io_error:
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
@@ -270,9 +280,11 @@ close_end:
; ----- iterative file loader functions (uses the read io channel) -----
sub f_open(uword filenameptr) -> bool {
sub f_open(str filenameptr) -> bool {
; -- open a file for iterative reading with f_read
; note: only a single iteration loop can be active at a time!
; Returns true if the file is successfully opened and readable.
; No need to check status(), unlike f_open_w() !
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
@@ -308,13 +320,12 @@ close_end:
reset_read_channel()
list_blocks = 0 ; we reuse this variable for the total number of bytes read
; commander X16 supports fast block-read via MACPTR() kernal call
uword readsize
while num_bytes {
readsize = 255
if num_bytes<readsize
readsize = num_bytes
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false)
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
if_cs
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
list_blocks += readsize
@@ -412,15 +423,35 @@ _end rts
; ----- iterative file writing functions (uses write io channel) -----
sub f_open_w(uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write
f_close_w()
sub f_open_w(str filename) -> bool {
; -- Open a file for iterative writing with f_write
; WARNING: returns true if the open command was received by the device,
; but this can still mean the file wasn't successfully opened for writing!
; (for example, if it already exists). This is different than f_open()!
; To be 100% sure if this call was successful, you have to use status()
; and check the drive's status message!
return internal_f_open_w(filename, false)
}
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
void cbm.OPEN() ; open 13,8,1,"filename"
if_cc
sub f_open_w_seek(str filename) -> bool {
; -- Open an existing file for iterative writing with f_write, and seeking with f_seek_w.
return internal_f_open_w(filename, true)
}
sub internal_f_open_w(str filename, bool open_for_seeks) -> bool {
f_close_w()
list_filename = filename
str modifier = ",s,?"
modifier[3] = 'w'
if open_for_seeks
modifier[3] = 'm'
cx16.r0L = string.append(list_filename, modifier) ; secondary 13 requires a mode suffix to signal we're writing/modifying
cbm.SETNAM(cx16.r0L, list_filename)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, WRITE_IO_CHANNEL)
void cbm.OPEN() ; open 13,8,13,"filename"
if_cc {
return not cbm.READST()
}
f_close_w()
return false
}
@@ -429,6 +460,21 @@ _end rts
; -- write the given number of bytes to the currently open file
if num_bytes!=0 {
reset_write_channel()
do {
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
if_cs
goto no_mciout
num_bytes -= cx16.r0
bufferpointer += cx16.r0
if msb(bufferpointer) == $c0
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
if cbm.READST()!=0
return false
} until num_bytes==0
return not cbm.READST()
no_mciout:
; the device doesn't support MCIOUT, use a normal per-byte write loop
repeat num_bytes {
cbm.CHROUT(@(bufferpointer))
bufferpointer++
@@ -516,9 +562,6 @@ io_error:
if_cc
cx16.r0L = cbm.READST()==0
cbm.CLRCHN()
cbm.CLOSE(1)
return cx16.r0L
}
@@ -567,8 +610,6 @@ io_error:
+
}}
cbm.CLRCHN()
cbm.CLOSE(1)
return cx16.r1
}
@@ -703,7 +744,7 @@ internal_vload:
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
@(cx16.r12)=0
cbm.SETNAM(3, "$=c")
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
goto io_error
@@ -777,7 +818,7 @@ io_error:
sub f_seek(uword pos_hiword, uword pos_loword) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
ubyte[6] command = ['p',0,0,0,0,0]
command[1] = READ_IO_CHANNEL ; f_open uses the read io channel
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
command[2] = lsb(pos_loword)
command[3] = msb(pos_loword)
command[4] = lsb(pos_hiword)
@@ -791,15 +832,18 @@ io_error:
}
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
; diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses the write io channel
; diskio.f_seek.command[2] = lsb(pos_loword)
; diskio.f_seek.command[3] = msb(pos_loword)
; diskio.f_seek.command[4] = lsb(pos_hiword)
; diskio.f_seek.command[5] = msb(pos_hiword)
; goto diskio.f_seek.send_command
; }
sub f_seek_w(uword pos_hiword, uword pos_loword) {
; -- seek in the output file opened with f_open_w_seek, to the given 32-bits position
diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses this secondary address
diskio.f_seek.command[2] = lsb(pos_loword)
diskio.f_seek.command[3] = msb(pos_loword)
diskio.f_seek.command[4] = lsb(pos_hiword)
diskio.f_seek.command[5] = msb(pos_hiword)
cbm.SETNAM(sizeof(diskio.f_seek.command), &diskio.f_seek.command)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
cbm.CLOSE(15)
reset_write_channel() ; back to the write io channel
}
}
+5 -1
View File
@@ -1,5 +1,5 @@
; Emulator debug interface.
; Docs: https://github.com/X16Community/x16-emulator#debug-io-registers
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
emudbg {
@@ -23,10 +23,13 @@ emudbg {
&ubyte EMU_EMU_DETECT2 = EMU_BASE + 15
sub is_emulator() -> bool {
; Test for emulator presence.
; It is recommended to only use the other debug routines if this returns true.
return EMU_EMU_DETECT1=='1' and EMU_EMU_DETECT2=='6'
}
asmsub console_write(str isoString @R0) clobbers(Y) {
; note: make sure the text is in Iso encoding.
%asm {{
ldy #0
- lda (cx16.r0),y
@@ -39,6 +42,7 @@ emudbg {
}
asmsub console_chrout(ubyte char @A) {
; note: make sure the character is in Iso encoding.
%asm {{
sta p8_EMU_CPUCLK_U
}}
+191 -44
View File
@@ -10,14 +10,17 @@
; If you're memory constrained you should probably not use this built-in library,
; but make a copy in your project only containing the code for the required resolution.
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 x 256c (8 bpp)
; mode 2 = bitmap 640 x 480 x 4c (2 bpp. there's not enough vram for more colors in hires mode.)
; mode 3 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
; mode 4 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
; higher color dephts in highres are not supported due to lack of VRAM
; mode 2 = bitmap 640 x 480 x 4c (2 bpp)
; mode 3 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
; mode 4 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
; mode 5 = bitmap 640 x 400 x 16c (not yet implemented)
gfx2 {
@@ -40,9 +43,6 @@ gfx2 {
cx16.VERA_L1_CONFIG = %00000111
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
bpp = 8
}
2 -> {
; highres 4c
@@ -52,27 +52,44 @@ gfx2 {
cx16.VERA_L1_CONFIG = %00000101
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 2
}
else -> {
; back to default text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
cbm.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
width = 0
height = 0
bpp = 0
mode = 0
}
}
active_mode = mode
if bpp
init_mode(mode)
if active_mode
clear_screen(0)
}
sub init_mode(ubyte mode) {
; set the internal configuration variables corresponding to the given screenmode
; doesn't manipulate Vera / the actual display mode
active_mode = mode
when mode {
1 -> {
width = 320
height = 240
bpp = 8
}
2 -> {
width = 640
height = 480
bpp = 2
}
else -> {
width = 0
height = 0
bpp = 0
active_mode = 0
}
}
}
sub clear_screen(ubyte color) {
position(0, 0)
when active_mode {
@@ -189,6 +206,24 @@ gfx2 {
}
}
sub safe_horizontal_line(uword xx, uword yy, uword length, ubyte color) {
; does bounds checking and clipping
if msb(yy)&$80!=0 or yy>=height
return
if msb(xx)&$80!=0 {
length += xx
xx = 0
}
if xx>=width
return
if xx+length>width
length = width-xx
if length>width
return
horizontal_line(xx, yy, length, color)
}
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
when active_mode {
1 -> {
@@ -336,6 +371,7 @@ gfx2 {
}
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm.
if radius==0
return
@@ -349,36 +385,91 @@ gfx2 {
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plot(cx16.r14, cx16.r15, color)
plotq()
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, color)
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plot(cx16.r14, cx16.r15, color)
plotq()
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, color)
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plot(cx16.r14, cx16.r15, color)
plotq()
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, color)
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plot(cx16.r14, cx16.r15, color)
plotq()
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, color)
plotq()
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
xx--
decisionOver2 += (yy as word -xx)*2+1
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, color=color.
plot(cx16.r14, cx16.r15, color)
}
}
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
; This version does bounds checks and clipping, but is a lot slower.
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plotq()
cx16.r14 = xcenter - yy
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plotq()
cx16.r14 = xcenter - yy
plotq()
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, color=color.
safe_plot(cx16.r14, cx16.r15, color)
}
}
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
@@ -391,12 +482,35 @@ gfx2 {
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
radius--
decisionOver2 += (yy as word -radius)*2+1
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
; This version does bounds checks and clipping, but is a lot slower.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
@@ -453,6 +567,15 @@ gfx2 {
}
}
sub safe_plot(uword xx, uword yy, ubyte color) {
; A plot that does bounds checks to see if the pixel is inside the screen.
if msb(xx)&$80!=0 or msb(yy)&$80!=0
return
if xx >= width or yy >= height
return
plot(xx, yy, color)
}
sub pget(uword @zp xx, uword yy) -> ubyte {
when active_mode {
1 -> {
@@ -481,21 +604,32 @@ gfx2 {
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
sta cx16.r0L
pha
lda xx
and #3
tay
lda gfx2.plot.shift4c,y
tay
pla
cpy #0
beq +
- lsr a
dey
bne -
+ and #3
}}
cx16.r1L = lsb(xx) & 3
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L]
return cx16.r0L & 3
}
else -> return 0
}
}
sub fill(word @zp xx, word @zp yy, ubyte new_color) {
sub fill(uword x, uword y, ubyte new_color) {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 48
const ubyte MAXDEPTH = 64
word @zp xx = x as word
word @zp yy = y as word
word[MAXDEPTH] @split @shared stack_xl
word[MAXDEPTH] @split @shared stack_xr
word[MAXDEPTH] @split @shared stack_y
@@ -574,11 +708,15 @@ gfx2 {
while cx16.r12L {
pop_stack()
xx = x1
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L {
plot(xx as uword, yy as uword, cx16.r10L)
; TODO: if mode==1 (256c) use vera autodecrement instead of pget(), but code bloat not worth it?
while xx >= 0 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx--
}
if xx >= x1
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
else
goto skip
left = xx + 1
@@ -587,17 +725,26 @@ gfx2 {
xx = x1 + 1
do {
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L {
plot(xx as uword, yy as uword, cx16.r10L)
cx16.r9 = xx
; TODO: if mode==1 (256c) use vera autoincrement instead of pget(), but code bloat not worth it?
while xx <= width-1 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx++
}
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
skip:
xx++
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
while xx <= x2 {
if pget(xx as uword, yy as uword) == cx16.r11L
break
xx++
}
left = xx
} until xx>x2
}
+29 -16
View File
@@ -7,11 +7,17 @@
; 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" or "monogfx" 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.
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
graphics {
%option no_symbol_prefixing
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the normal FB_cursor_position() call but reuses existing r0 and r1
const uword WIDTH = 320
const ubyte HEIGHT = 240
@@ -69,6 +75,8 @@ graphics {
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
;cx16.r0 = xcenter - radius/2
;cx16.r1 = ycenter - radius/2
;cx16.r2 = radius*2
@@ -85,43 +93,47 @@ graphics {
while xx>=yy {
cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position2()
FB_cursor_position2()
cx16.FB_set_pixel(stroke_color)
yy++
if decisionOver2<=0 {
decisionOver2 += (yy as word)*2+1
} else {
if decisionOver2>=0 {
xx--
decisionOver2 += (yy as word -xx)*2+1
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
@@ -132,13 +144,14 @@ graphics {
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
radius--
decisionOver2 += (yy as word -radius)*2+1
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
+146 -24
View File
@@ -5,6 +5,9 @@
; For color bitmap graphics, see the gfx2 library.
;
; NOTE: a lot of the code here is similar or the same to that in gfx2
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
monogfx {
@@ -229,6 +232,24 @@ _done
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
sub safe_horizontal_line(uword xx, uword yy, uword length, bool draw) {
; does bounds checking and clipping
if msb(yy)&$80!=0 or yy>=height
return
if msb(xx)&$80!=0 {
length += xx
xx = 0
}
if xx>=width
return
if xx+length>width
length = width-xx
if length>width
return
horizontal_line(xx, yy, length, draw)
}
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
cx16.r15L = monogfx.plot.maskbits[xx as ubyte & 7] ; bitmask
if draw {
@@ -385,6 +406,7 @@ _done
}
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm.
if radius==0
return
@@ -398,36 +420,91 @@ _done
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plot(cx16.r14, cx16.r15, draw)
plotq()
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, draw)
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plot(cx16.r14, cx16.r15, draw)
plotq()
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, draw)
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plot(cx16.r14, cx16.r15, draw)
plotq()
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, draw)
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plot(cx16.r14, cx16.r15, draw)
plotq()
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, draw)
plotq()
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
xx--
decisionOver2 += (yy as word -xx)*2+1
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, draw=draw
plot(cx16.r14, cx16.r15, draw)
}
}
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plotq()
cx16.r14 = xcenter - yy
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plotq()
cx16.r14 = xcenter - yy
plotq()
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, draw=draw
safe_plot(cx16.r14, cx16.r15, draw)
}
}
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
@@ -440,12 +517,35 @@ _done
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
if decisionOver2>=0 {
radius--
decisionOver2 += (yy as word -radius)*2+1
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
@@ -497,6 +597,15 @@ _done
}
}
sub safe_plot(uword xx, uword yy, bool draw) {
; A plot that does bounds checks to see if the pixel is inside the screen.
if msb(xx)&$80!=0 or msb(yy)&$80!=0
return
if xx >= width or yy >= height
return
plot(xx, yy, draw)
}
sub pget(uword @zp xx, uword yy) -> ubyte {
%asm {{
lda xx
@@ -525,11 +634,13 @@ _done
}}
}
sub fill(word @zp xx, word @zp yy, bool draw) {
sub fill(uword x, uword y, bool draw) {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 48
const ubyte MAXDEPTH = 64
word @zp xx = x as word
word @zp yy = y as word
word[MAXDEPTH] @split @shared stack_xl
word[MAXDEPTH] @split @shared stack_xr
word[MAXDEPTH] @split @shared stack_y
@@ -608,11 +719,14 @@ _done
while cx16.r12L {
pop_stack()
xx = x1
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L {
plot(xx as uword, yy as uword, cx16.r10L)
while xx >= 0 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx--
}
if xx >= x1
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
else
goto skip
left = xx + 1
@@ -621,17 +735,25 @@ _done
xx = x1 + 1
do {
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L {
plot(xx as uword, yy as uword, cx16.r10L)
cx16.r9 = xx
while xx <= width-1 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx++
}
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
skip:
xx++
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
while xx <= x2 {
if pget(xx as uword, yy as uword) == cx16.r11L
break
xx++
}
left = xx
} until xx>x2
}
+24 -32
View File
@@ -79,15 +79,13 @@ palette {
sub set_grayscale() {
vera_palette_ptr = $fa00
cc=0
repeat 16 {
cc=0
repeat 16 {
cx16.vpoke(1, vera_palette_ptr, cc)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, cc)
vera_palette_ptr++
cc += $11
}
cx16.vpoke(1, vera_palette_ptr, cc)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, cc)
vera_palette_ptr++
cc += $11
}
}
@@ -150,40 +148,34 @@ palette {
sub set_c64pepto() {
vera_palette_ptr = $fa00
repeat 16 {
for cc in 0 to 15 {
uword ccp = C64_colorpalette_pepto[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
for cc in 0 to 15 {
uword ccp = C64_colorpalette_pepto[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
}
sub set_c64light() {
vera_palette_ptr = $fa00
repeat 16 {
for cc in 0 to 15 {
uword ccp = C64_colorpalette_light[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
for cc in 0 to 15 {
uword ccp = C64_colorpalette_light[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
}
sub set_c64dark() {
vera_palette_ptr = $fa00
repeat 16 {
for cc in 0 to 15 {
uword ccp = C64_colorpalette_dark[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
for cc in 0 to 15 {
uword ccp = C64_colorpalette_dark[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
}
+1 -2
View File
@@ -104,7 +104,6 @@ sprites {
}
sub set_palette_offset(ubyte spritenum, ubyte offset) {
sprite_reg = VERA_SPRITEREGS + 7 + spritenum*$0008
cx16.vpoke_mask(1, sprite_reg, %11110000, offset>>4)
cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset>>4)
}
}
+45 -13
View File
@@ -11,9 +11,9 @@ cbm {
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.home or txt.plot
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip, including resetting to the default color palette
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, IRQ, ...)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
@@ -40,7 +40,7 @@ romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device. See also BSAVE
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
@@ -120,6 +120,9 @@ cx16 {
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
&uword edkeyvec = $ac03 ; for intercepting BASIN/CHRIN key strokes. See set_basin_handler()
&uword edkeybk = $ac05 ; ...the RAM bank of this routine if not in low ram
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
&uword r0 = $0002
@@ -364,7 +367,6 @@ romsub $fef6 = FB_init() clobbers(A,X,Y)
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte colorcount @X) clobbers(A,X,Y)
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
@@ -407,7 +409,7 @@ romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
romsub $ff68 = mouse_config(ubyte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
romsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
@@ -450,7 +452,7 @@ asmsub kbdbuf_clear() {
}}
}
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
asmsub mouse_config2(byte shape @A) clobbers (A, X, Y) {
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
%asm {{
pha ; save shape
@@ -526,16 +528,12 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
; -- get a byte from VERA's video memory
; note: inefficient when reading multiple sequential bytes!
%asm {{
pha
lda cx16.VERA_CTRL
ora #1
sta cx16.VERA_CTRL
pla
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
sty cx16.VERA_ADDR_M
stx cx16.VERA_ADDR_L
lda cx16.VERA_DATA1
lda cx16.VERA_DATA0
rts
}}
}
@@ -832,6 +830,24 @@ asmsub restore_vera_context() clobbers(A) {
}}
}
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
; Install a custom CHRIN (BASIN) key handler. Call this before each line you want to read.
; See https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-basin-petscii-code-override-handler
%asm {{
sei
sta cx16.edkeybk
lda $00
pha
stz $00
stx cx16.edkeyvec
sty cx16.edkeyvec+1
pla
sta $00
cli
rts
}}
}
}
sys {
@@ -873,6 +889,8 @@ asmsub init_system() {
jsr cbm.CHROUT
lda #147 ; clear screen
jsr cbm.CHROUT
lda #8 ; disable charset case switch
jsr cbm.CHROUT
lda #PROG8_VARSHIGH_RAMBANK
sta $00 ; select ram bank
lda #0
@@ -1250,6 +1268,20 @@ _longcopy
}}
}
inline asmsub disable_caseswitch() {
%asm {{
lda #8
jsr cbm.CHROUT
}}
}
inline asmsub enable_caseswitch() {
%asm {{
lda #9
jsr cbm.CHROUT
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
+50 -3
View File
@@ -1,4 +1,6 @@
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
; these work with PETSCII encoding instead.
%import syslib
%import conv
@@ -42,6 +44,43 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A, X) {
; ---- fill the character screen with the given fill character and character color.
%asm {{
@@ -384,10 +423,10 @@ _nextline
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
@@ -572,7 +611,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
@@ -756,4 +796,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
%asm {{
- jsr cbm.GETIN
beq -
rts
}}
}
}
+11 -6
View File
@@ -65,10 +65,16 @@ verafx {
; unsigned multiplication just passes the values as signed to muls
; if you do this yourself in your call to muls, it will save a few instructions.
sub mult(uword value1, uword value2) -> uword {
; Returns the lower 16 bits of the 32 bits result,
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
return muls(value1 as word, value2 as word) as uword
}
asmsub muls(word value1 @R0, word value2 @R1) -> word @AY {
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY {
; Returns the lower 16 bits of the 32 bits result in AY,
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
%asm {{
lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25
@@ -104,14 +110,13 @@ verafx {
sta cx16.VERA_ADDR_H ; so we can read out the result
lda cx16.VERA_DATA0
ldy cx16.VERA_DATA0
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in r0
stx cx16.r0
ldx cx16.VERA_DATA0
stx cx16.r0+1
stz cx16.VERA_FX_CTRL ; Cache write disable
stz cx16.VERA_CTRL ; reset DCSEL
rts
; we skip the upper 16 bits of the result:
; lda cx16.VERA_DATA0
; sta $0402
; lda cx16.VERA_DATA0
; sta $0403
}}
}
+2 -7
View File
@@ -107,7 +107,7 @@ io_error:
@(cx16.r0) = cbm.CHRIN()
if @(cx16.r0)=='"' {
@(cx16.r0) = ' '
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
@(cx16.r0) = 0
cx16.r0--
}
@@ -137,7 +137,7 @@ io_error:
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
@@ -476,9 +476,6 @@ io_error:
if_cc
cx16.r0L = cbm.READST()==0
cbm.CLRCHN()
cbm.CLOSE(1)
return cx16.r0L
}
@@ -506,8 +503,6 @@ io_error:
+
}}
cbm.CLRCHN()
cbm.CLOSE(1)
return cx16.r1
}
+1 -1
View File
@@ -74,7 +74,7 @@ multiply_words .proc
; - unrolled the two inner loops once
;
; 16 bit x 16 bit unsigned multiply, 32 bit result
; Average cycles:
; Average cycles: ~442 ?
; 93 bytes
_multiplicand = P8ZP_SCRATCH_W2 ; 2 bytes
+39
View File
@@ -1,4 +1,6 @@
; Internal Math library routines - always included by the compiler
; note: some functions you might expect here are builtin functions,
; such as abs, sqrt, clamp, min, max for example.
math {
%option no_symbol_prefixing
@@ -445,4 +447,41 @@ log2_tab
}}
}
asmsub diff(ubyte v1 @A, ubyte v2 @Y) -> ubyte @A {
; -- returns the (absolute) difference, or distance, between the two bytes
%asm {{
sty P8ZP_SCRATCH_REG
sec
sbc P8ZP_SCRATCH_REG
bcs +
eor #255
inc a
+ rts
}}
}
asmsub diffw(uword w1 @R0, uword w2 @AY) -> uword @AY {
; -- returns the (absolute) difference, or distance, between the two words
%asm {{
sec
sbc cx16.r0L
sta cx16.r0L
tya
sbc cx16.r0H
sta cx16.r0H
bcs +
eor #255
sta cx16.r0H
lda cx16.r0L
eor #255
inc a
sta cx16.r0L
bne +
inc cx16.r0H
+ lda cx16.r0L
ldy cx16.r0H
rts
}}
}
}
+8
View File
@@ -297,6 +297,14 @@ _longcopy
}}
}
sub disable_caseswitch() {
; PET doesn't have a key to swap case, so no-op
}
sub enable_caseswitch() {
; PET doesn't have a key to swap case, so no-op
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
+52 -3
View File
@@ -1,4 +1,6 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore PET
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
; these work with PETSCII encoding instead.
%import syslib
%import conv
@@ -28,6 +30,45 @@ sub spc() {
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character. color is ignored on PET
%asm {{
@@ -139,10 +180,10 @@ asmsub scroll_down () clobbers(A,X) {
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
@@ -328,7 +369,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
@@ -453,4 +495,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
%asm {{
- jsr cbm.GETIN
beq -
rts
}}
}
}
+29 -4
View File
@@ -146,11 +146,11 @@ _startloop dey
iny
bne -
_notfound lda #0
clc
clc
rts
_found tya
sec
rts
_found tya
sec
rts
}}
}
@@ -168,6 +168,31 @@ _found tya
}}
}
asmsub append(uword target @R0, uword suffix @R1) clobbers(Y) -> ubyte @A {
; Append the suffix string to the target. (make sure the buffer is large enough!)
; Returns the length of the resulting string.
%asm {{
lda cx16.r0
ldy cx16.r0+1
jsr length
sty P8ZP_SCRATCH_B1
tya
clc
adc cx16.r0
sta P8ZP_SCRATCH_W1
lda cx16.r0+1
adc #0
sta P8ZP_SCRATCH_W1+1
lda cx16.r1
ldy cx16.r1+1
jsr prog8_lib.strcpy
tya
clc
adc P8ZP_SCRATCH_B1
rts
}}
}
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
+15
View File
@@ -1,4 +1,6 @@
; Internal Math library routines - always included by the compiler
; note: some functions you might expect here are builtin functions,
; such as abs, sqrt, clamp, min, max for example.
math {
@@ -277,4 +279,17 @@ math {
returnr.w r0
}}
}
sub diff(ubyte b1, ubyte b2) -> ubyte {
if b1>b2
return b1-b2
return b2-b1
}
sub diffw(uword w1, uword w2) -> uword {
if w1>w2
return w1-w2
return w2-w1
}
}
+471
View File
@@ -0,0 +1,471 @@
; Monochrome Bitmap pixel graphics routines for the Virtual Machine
; Using the full-screen 640x480 and 320x240 screen modes, but just black/white.
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
monogfx {
%option no_symbol_prefixing
; read-only control variables:
uword width = 0
uword height = 0
bool dont_stipple_flag = true ; set to false to enable stippling mode
sub lores() {
; enable 320*240 bitmap mode
sys.gfx_enable(0)
width = 320
height = 240
clear_screen(0)
}
sub hires() {
; enable 640*480 bitmap mode
sys.gfx_enable(1)
width = 640
height = 480
clear_screen(0)
}
sub textmode() {
; back to normal text mode
}
sub clear_screen(ubyte color) {
stipple(false)
if color
color=255
sys.gfx_clear(color)
}
sub stipple(bool enable) {
dont_stipple_flag = not enable
}
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
if rwidth==0 or rheight==0
return
horizontal_line(xx, yy, rwidth, draw)
if rheight==1
return
horizontal_line(xx, yy+rheight-1, rwidth, draw)
vertical_line(xx, yy+1, rheight-2, draw)
if rwidth==1
return
vertical_line(xx+rwidth-1, yy+1, rheight-2, draw)
}
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
if rwidth==0
return
repeat rheight {
horizontal_line(xx, yy, rwidth, draw)
yy++
}
}
sub horizontal_line(uword xx, uword yy, uword length, bool draw) {
ubyte color = 0
if draw
color = 255
uword xpos
for xpos in xx to xx+length-1
plot(xpos, yy, color)
}
sub safe_horizontal_line(uword xx, uword yy, uword length, bool draw) {
; does bounds checking and clipping
if msb(yy)&$80!=0 or yy>=height
return
if msb(xx)&$80!=0 {
length += xx
xx = 0
}
if xx>=width
return
if xx+length>width
length = width-xx
if length>width
return
horizontal_line(xx, yy, length, draw)
}
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
ubyte color = 0
if draw
color = 255
uword ypos
for ypos in yy to yy+lheight-1
plot(xx, ypos, color)
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, bool draw) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
cx16.r0 = x1
x1 = x2
x2 = cx16.r0
cx16.r0 = y1
y1 = y2
y2 = cx16.r0
}
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy) as uword +1, draw)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx) as uword +1, draw)
return
}
word @zp d = 0
cx16.r1L = true ; 'positive_ix'
if dx < 0 {
dx = -dx
cx16.r1L = false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if cx16.r1L {
repeat {
plot(cx16.r14, y1, draw)
if cx16.r14==x2
return
cx16.r14++
d += dy2
if d > dx {
y1++
d -= dx2
}
}
} else {
repeat {
plot(cx16.r14, y1, draw)
if cx16.r14==x2
return
cx16.r14--
d += dy2
if d > dx {
y1++
d -= dx2
}
}
}
}
else {
if cx16.r1L {
repeat {
plot(cx16.r14, y1, draw)
if y1 == y2
return
y1++
d += dx2
if d > dy {
cx16.r14++
d -= dy2
}
}
} else {
repeat {
plot(cx16.r14, y1, draw)
if y1 == y2
return
y1++
d += dx2
if d > dy {
cx16.r14--
d -= dy2
}
}
}
}
}
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plotq()
cx16.r14 = xcenter - yy
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plotq()
cx16.r14 = xcenter - yy
plotq()
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, draw=draw
plot(cx16.r14, cx16.r15, draw)
}
}
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plotq()
cx16.r14 = xcenter - xx
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plotq()
cx16.r14 = xcenter - yy
plotq()
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plotq()
cx16.r14 = xcenter - yy
plotq()
yy++
if decisionOver2>=0 {
xx--
decisionOver2 -= xx*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
sub plotq() {
; cx16.r14 = x, cx16.r15 = y, draw=draw
safe_plot(cx16.r14, cx16.r15, draw)
}
}
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
yy++
if decisionOver2>=0 {
radius--
decisionOver2 -= radius*$0002
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
if draw {
if dont_stipple_flag
sys.gfx_plot(xx, yy, 255)
else {
if (xx ^ yy)&1
sys.gfx_plot(xx, yy, 255)
else
sys.gfx_plot(xx, yy, 0)
}
}
else
sys.gfx_plot(xx, yy, 0)
}
sub safe_plot(uword xx, uword yy, bool draw) {
; A plot that does bounds checks to see if the pixel is inside the screen.
if msb(xx)&$80!=0 or msb(yy)&$80!=0
return
if xx >= width or yy >= height
return
plot(xx, yy, draw)
}
sub pget(uword @zp xx, uword yy) -> ubyte {
return sys.gfx_getpixel(xx, yy)
}
sub fill(uword x, uword y, bool draw) {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 64
word @zp xx = x as word
word @zp yy = y as word
word[MAXDEPTH] @split @shared stack_xl
word[MAXDEPTH] @split @shared stack_xr
word[MAXDEPTH] @split @shared stack_y
byte[MAXDEPTH] @shared stack_dy
cx16.r12L = 0 ; stack pointer
word x1
word x2
byte dy
cx16.r10L = draw as ubyte
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
if cx16.r12L==MAXDEPTH
return
cx16.r0s = sy+sdy
if cx16.r0s>=0 and cx16.r0s<=height-1 {
stack_xl[cx16.r12L] = sxl
stack_xr[cx16.r12L] = sxr
stack_y[cx16.r12L] = sy
stack_dy[cx16.r12L] = sdy
cx16.r12L++
}
}
sub pop_stack() {
cx16.r12L--
x1 = stack_xl[cx16.r12L]
x2 = stack_xr[cx16.r12L]
yy = stack_y[cx16.r12L]
dy = stack_dy[cx16.r12L]
yy+=dy
}
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
if cx16.r11L == cx16.r10L
return
if xx<0 or xx>width-1 or yy<0 or yy>height-1
return
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L {
pop_stack()
xx = x1
while xx >= 0 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx--
}
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
else
goto skip
left = xx + 1
if left < x1
push_stack(left, x1 - 1, yy, -dy)
xx = x1 + 1
do {
cx16.r9 = xx
while xx <= width-1 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx++
}
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
skip:
xx++
while xx <= x2 {
if pget(xx as uword, yy as uword) == cx16.r11L
break
xx++
}
left = xx
} until xx>x2
}
}
sub text_charset(ubyte charset) {
; -- select the text charset to use with the text() routine
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
; TODO vm has no bitmap charset
}
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; TODO vm has no bitmap charset
}
}
+7
View File
@@ -78,6 +78,13 @@ string {
}
}
sub append(str target, str suffix) -> ubyte {
; Append the suffix string to the target. (make sure the buffer is large enough!)
; Returns the length of the resulting string.
cx16.r0L = length(target)
return copy(suffix, target+cx16.r0L) + cx16.r0L
}
sub compare(str st1, str st2) -> byte {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
+7
View File
@@ -79,6 +79,13 @@ sys {
}}
}
sub disable_caseswitch() {
; no-op
}
sub enable_caseswitch() {
; no-op
}
sub gfx_enable(ubyte mode) {
%ir {{
+3
View File
@@ -49,6 +49,7 @@ private fun compileMain(args: Array<String>): Boolean {
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val includeSourcelines by cli.option(ArgType.Boolean, fullName = "sourcelines", description = "include original Prog8 source lines in generated asm code")
val splitWordArrays by cli.option(ArgType.Boolean, fullName = "splitarrays", description = "treat all word arrays as tagged with @split to make them lsb/msb split in memory")
val breakpointCpuInstruction by cli.option(ArgType.Boolean, fullName = "breakinstr", description = "also use a CPU instruction for %breakpoint")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${PETTarget.NAME}', '${VMTarget.NAME}') (required)")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
@@ -122,6 +123,7 @@ private fun compileMain(args: Array<String>): Boolean {
varsHighBank,
compilationTarget!!,
splitWordArrays == true,
breakpointCpuInstruction = false,
processedSymbols,
srcdirs,
outputPath
@@ -190,6 +192,7 @@ private fun compileMain(args: Array<String>): Boolean {
varsHighBank,
compilationTarget!!,
splitWordArrays == true,
breakpointCpuInstruction == true,
processedSymbols,
srcdirs,
outputPath
@@ -5,11 +5,11 @@ package prog8.buildversion
*/
const val MAVEN_GROUP = "prog8"
const val MAVEN_NAME = "compiler"
const val VERSION = "9.5-SNAPSHOT"
const val GIT_REVISION = 4108
const val GIT_SHA = "c319233ddc7f43a05e2090e293abbed8d80ce8cf"
const val GIT_DATE = "2023-09-18T21:22:03Z"
const val VERSION = "9.6-SNAPSHOT"
const val GIT_REVISION = 4159
const val GIT_SHA = "335213b55f971452a05c904216cb0fd72f5518a1"
const val GIT_DATE = "2023-10-21T00:16:58Z"
const val GIT_BRANCH = "master"
const val BUILD_DATE = "2023-09-18T21:27:55Z"
const val BUILD_UNIX_TIME = 1695072475414L
const val BUILD_DATE = "2023-10-21T20:22:14Z"
const val BUILD_UNIX_TIME = 1697919734197L
const val DIRTY = 1
+12 -1
View File
@@ -38,6 +38,7 @@ class CompilerArguments(val filepath: Path,
val varsHighBank: Int?,
val compilationTarget: String,
val splitWordArrays: Boolean,
val breakpointCpuInstruction: Boolean,
val symbolDefs: Map<String, String>,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
@@ -74,6 +75,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
asmListfile = args.asmListfile
includeSourcelines = args.includeSourcelines
experimentalCodegen = args.experimentalCodegen
breakpointCpuInstruction = args.breakpointCpuInstruction
varsHighBank = args.varsHighBank
splitWordArrays = args.splitWordArrays
outputDir = args.outputDir.normalize()
@@ -105,6 +107,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
if (args.writeAssembly) {
// re-initialize memory areas with final compilationOptions
compilationOptions.compTarget.machine.initializeMemoryAreas(compilationOptions)
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
@@ -299,6 +302,14 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
.map { it[0].int!!..it[1].int!! }
.toList()
val zpAllowed = toplevelModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpallowed" }
.map { (it as Directive).args }
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
.map { it[0].int!!..it[1].int!! }
.toList()
val outputType = if (outputTypeStr == null) {
if(compTarget is AtariTarget)
OutputType.XEX
@@ -328,7 +339,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
return CompilationOptions(
outputType, launcherType,
zpType, zpReserved, floatsEnabled, noSysInit,
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit,
compTarget, 0u
)
}
@@ -127,7 +127,7 @@ class ModuleImporter(private val program: Program,
private fun removeDirectivesFromImportedModule(importedModule: Module) {
// Most global directives don't apply for imported modules, so remove them
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address")
var directives = importedModule.statements.filterIsInstance<Directive>()
importedModule.statements.removeAll(directives.toSet())
directives = directives.filter{ it.directive !in moduleLevelDirectives }
@@ -773,7 +773,7 @@ internal class AstChecker(private val program: Program,
directive.args[0].name != "full")
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, dontuse, or full")
}
"%zpreserved" -> {
"%zpreserved", "%zpallowed" -> {
if(directive.parent !is Module)
err("this directive may only occur at module level")
if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null)
@@ -1348,6 +1348,9 @@ internal class AstChecker(private val program: Program,
if(whenStmt.choices.isEmpty())
errors.err("empty when statement", whenStmt.position)
if(whenStmt.condition.constValue(program)!=null)
errors.warn("when-value is a constant and will always result in the same choice", whenStmt.condition.position)
super.visit(whenStmt)
}
@@ -4,8 +4,10 @@ import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.getTempVar
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.ContainmentCheck
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@@ -39,72 +41,6 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
// this triggers the more efficent augmented assignment code generation more often.
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(options.compTarget.name==VMTarget.NAME) // don't apply this optimization for Vm target
return noModifications
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null && binExpr.operator !in ComparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
// the right part of the expression contains the target variable itself.
// we can't 'split' it trivially because the variable will be changed halfway through.
if(binExpr.operator in AssociativeOperators) {
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
// use the other part of the expression to split.
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
"unknown dt"
)
}, sourceDt, implicit=true)
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.ASMGEN, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
)
}
} else {
if(binExpr.left isSameAs assignment.target)
return noModifications
val typeCast = binExpr.left as? TypecastExpression
if(typeCast!=null && typeCast.expression isSameAs assignment.target)
return noModifications
if(binExpr.operator in "+-") {
val leftDt = binExpr.left.inferType(program)
val rightDt = binExpr.right.inferType(program)
if(leftDt==rightDt && leftDt.isInteger && rightDt.isInteger && binExpr.right is ArrayIndexedExpression) {
// don't split array[i] +/- array[i] (the codegen has an optimized path for this)
return noModifications
}
}
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
"unknown dt"
)
}, sourceDt, implicit=true)
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.ASMGEN, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
)
}
}
}
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
@@ -221,35 +157,4 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(options.compTarget.name!=VMTarget.NAME) { // don't apply this optimization/check for Vm target
val index = arrayIndexedExpression.indexer.indexExpr
if (index !is NumericLiteral && index !is IdentifierReference) {
// replace complex indexing expression with a temp variable to hold the computed index first
return getAutoIndexerVarFor(arrayIndexedExpression)
}
}
return noModifications
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement
val dt = expr.indexer.indexExpr.inferType(program)
val (tempVarName, _) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
val target = AssignTarget(IdentifierReference(tempVarName, expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.ASMGEN, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(
IAstModification.ReplaceNode(
expr.indexer.indexExpr,
target.identifier!!.copy(),
expr.indexer
)
)
return modifications
}
}
@@ -22,7 +22,7 @@ internal class StatementReorderer(
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%option")
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
@@ -52,10 +52,6 @@ internal class StatementReorderer(
// This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null
if(decl.name.startsWith("tempvar_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal temporary variables.
return noModifications
}
if(decl.findInitializer(program)!=null)
return noModifications // an initializer assignment for a vardecl is already here
val nextFor = decl.nextSibling() as? ForLoop
@@ -9,6 +9,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import kotlin.math.sign
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {
@@ -245,96 +246,54 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type
val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(program)) {
is Subroutine -> {
sub.parameters.zip(call.args).forEach { (param, arg) ->
val argItype = arg.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.getOr(DataType.UNDEFINED)
val requiredType = param.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
// don't need a cast for pass-by-reference types that are assigned to UWORD
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = arg as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, arg.position),
call as Node)
}
} else if(arg is NumericLiteral) {
val cast = arg.cast(requiredType)
if(cast.isValid)
modifications += IAstModification.ReplaceNode(
arg,
cast.valueOrZero(),
call as Node)
} else if(requiredType==DataType.BOOL && argtype!=DataType.BOOL) {
// cast to bool
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
}
}
}
else {
// if the argument is an identifier reference and the param is UWORD, add the missing &.
if(arg is IdentifierReference && DataType.UWORD == param.type) {
modifications += IAstModification.ReplaceNode(
arg,
AddressOf(arg, null, arg.position),
call as Node
)
}
}
}
}
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
func.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.getOr(DataType.UNDEFINED)
if (pair.first.possibleDatatypes.all { argtype != it }) {
for (possibleType in pair.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
addTypecastOrCastedValueModification(modifications, pair.second, possibleType, call as Node)
break
}
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(identifier, null, pair.second.position),
call as Node)
break
}
}
}
}
}
else {
// if the argument is an identifier reference and the param is UWORD, add the missing &.
if(pair.second is IdentifierReference && DataType.UWORD in pair.first.possibleDatatypes) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, null, pair.second.position),
call as Node
)
}
}
}
}
else -> { }
val sub = call.target.targetStatement(program)
val params = when(sub) {
is BuiltinFunctionPlaceholder -> BuiltinFunctions.getValue(sub.name).parameters
is Subroutine -> sub.parameters.map { FParam(it.name, listOf(it.type).toTypedArray()) }
else -> emptyList()
}
params.zip(call.args).forEach {
val targetDt = it.first.possibleDatatypes.first()
val argIdt = it.second.inferType(program)
if (argIdt.isKnown) {
val argDt = argIdt.getOr(DataType.UNDEFINED)
if (argDt !in it.first.possibleDatatypes) {
val identifier = it.second as? IdentifierReference
val number = it.second as? NumericLiteral
if(number!=null) {
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
} else if(identifier!=null && targetDt==DataType.UWORD && argDt in PassByReferenceDatatypes) {
if(!identifier.isSubroutineParameter(program)) {
// We allow STR/ARRAY values for UWORD parameters.
// If it's an array (not STR), take the address.
if(argDt != DataType.STR) {
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, it.second.position),
call as Node
)
}
}
} else if(targetDt==DataType.BOOL) {
addTypecastOrCastedValueModification(modifications, it.second, DataType.BOOL, call as Node)
} else if(argDt isAssignableTo targetDt) {
if(argDt!=DataType.STR || targetDt!=DataType.UWORD)
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
}
}
} else {
val identifier = it.second as? IdentifierReference
if(identifier!=null && targetDt==DataType.UWORD) {
// take the address of the identifier
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, it.second.position),
call as Node
)
}
}
}
return modifications
}
@@ -448,7 +407,11 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(expressionToCast is NumericLiteral && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
val castedValue = expressionToCast.cast(requiredType)
if (castedValue.isValid) {
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
val signOriginal = sign(expressionToCast.number)
val signCasted = sign(castedValue.valueOrZero().number)
if(signOriginal==signCasted) {
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
}
return
}
}
@@ -19,8 +19,24 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
errors.err("memory block '${slab.name}' already exists with a different size and/or alignment at ${other.position}", slab.position)
}
}
// remove unused strings from interned strings block
val internedBlock = program.allBlocks.singleOrNull { it.name== internedStringsModuleName }
internedBlock?.statements?.withIndex()?.reversed()?.forEach { (index, st) ->
if(st is VarDecl && st.scopedName !in allStringRefs) {
internedBlock.statements.removeAt(index)
}
}
}
override fun visit(identifier: IdentifierReference) {
if(identifier.wasStringLiteral(program)) {
allStringRefs.add(identifier.nameInSource)
}
}
private val allStringRefs = mutableListOf<List<String>>()
private class Slab(val name: String, val size: Int, val align: Int, val position: Position)
private val memorySlabs = mutableListOf<Slab>()
+1
View File
@@ -35,6 +35,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
varsHighBank = null,
compilationTarget = target.name,
splitWordArrays = false,
breakpointCpuInstruction = false,
symbolDefs = emptyMap(),
outputDir = outputDir
)
@@ -52,6 +52,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
varsHighBank = null,
compilationTarget = Cx16Target.NAME,
splitWordArrays = false,
breakpointCpuInstruction = false,
symbolDefs = emptyMap(),
sourceDirs,
outputDir
+1
View File
@@ -16,6 +16,7 @@ class TestGoldenRam: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.FULL,
listOf((0x00u..0xffu)),
CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
compTarget = VMTarget(),
+3 -4
View File
@@ -58,11 +58,10 @@ class TestTypecasts: FunSpec({
val result2 = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts2 = result2.compilerAst.entrypoint.statements
stmts2.size shouldBe 6
val expr2 = (stmts2[4] as Assignment).value as BinaryExpression
stmts2.size shouldBe 5
val expr2 = (stmts2[3] as Assignment).value as BinaryExpression
expr2.operator shouldBe "&"
expr2.right shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
(expr2.left as IdentifierReference).nameInSource shouldBe listOf("bb")
}
test("bool expressions with functioncalls") {
@@ -740,7 +739,7 @@ main {
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
result.compilerAst.entrypoint.statements.size shouldBe 15
result.compilerAst.entrypoint.statements.size shouldBe 14
}
test("invalid typecasts of numbers") {
+32 -30
View File
@@ -33,6 +33,7 @@ class TestAbstractZeropage: FunSpec({
free.addAll(0u..255u)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -47,6 +48,7 @@ class TestAbstractZeropage: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.FULL,
listOf((0x50u..0x5fu)),
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = false,
compTarget = DummyCompilationTarget,
@@ -65,7 +67,7 @@ class TestC64Zeropage: FunSpec({
val c64target = C64Target()
test("testNames") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(),
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = false,
compTarget = c64target, loadAddress = 999u
@@ -83,33 +85,33 @@ class TestC64Zeropage: FunSpec({
}
test("testZpFloatEnable") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled floats" }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target, 999u))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
result = zp2.allocate("", DataType.FLOAT, null, null, errors)
result.expectError { "should be allocation error due to disabled ZP use" }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
zp3.allocate("", DataType.FLOAT, null, null, errors)
}
test("testZpModesWithFloats") {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
}
shouldThrow<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, c64target, 999u))
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
}
}
test("testZpDontuse") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target, 999u))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
println(zp.free)
zp.availableBytes() shouldBe 0
val result = zp.allocate("", DataType.BYTE, null, null, errors)
@@ -117,13 +119,13 @@ class TestC64Zeropage: FunSpec({
}
test("testFreeSpacesBytes") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
zp1.availableBytes() shouldBe 17
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
zp2.availableBytes() shouldBe 87
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
zp3.availableBytes() shouldBe 96
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
zp4.availableBytes() shouldBe 207
zp4.allocate("test", DataType.UBYTE, null, null, errors)
zp4.availableBytes() shouldBe 206
@@ -132,7 +134,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
zp1.availableBytes() shouldBe 207
4u shouldNotBeIn zp1.free
35u shouldNotBeIn zp1.free
@@ -143,7 +145,7 @@ class TestC64Zeropage: FunSpec({
200u shouldBeIn zp1.free
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))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
zp2.availableBytes() shouldBe 107
4u shouldNotBeIn zp2.free
35u shouldNotBeIn zp2.free
@@ -154,14 +156,14 @@ 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))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
zp2.availableBytes() shouldBe 107
4u shouldBeIn zp3.free
35u shouldNotBeIn zp3.free
}
test("testBasicsafeAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
zp.availableBytes() shouldBe 17
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -183,7 +185,7 @@ class TestC64Zeropage: FunSpec({
}
test("testFullAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
zp.availableBytes() shouldBe 207
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -214,7 +216,7 @@ class TestC64Zeropage: FunSpec({
}
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
zp.availableBytes() shouldBe 17
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
@@ -232,7 +234,7 @@ class TestC64Zeropage: FunSpec({
}
test("testReservedLocations") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target, 999u))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
@@ -245,18 +247,18 @@ class TestCx16Zeropage: FunSpec({
val cx16target = Cx16Target()
test("testReservedLocations") {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, cx16target, 999u))
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
}
test("testFreeSpacesBytes") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, cx16target, 999u))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, cx16target, 999u))
zp1.availableBytes() shouldBe 88
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, cx16target, 999u))
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
zp2.availableBytes() shouldBe 175
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
zp3.availableBytes() shouldBe 216
zp3.allocate("test", DataType.UBYTE, null, null, errors)
zp3.availableBytes() shouldBe 215
@@ -265,7 +267,7 @@ class TestCx16Zeropage: FunSpec({
}
test("testReservedSpace") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
zp1.availableBytes() shouldBe 216
0x22u shouldBeIn zp1.free
0x80u shouldBeIn zp1.free
@@ -275,7 +277,7 @@ class TestCx16Zeropage: FunSpec({
}
test("preallocated zp vars") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
zp1.allocatedVariables["test"] shouldBe null
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
@@ -366,5 +366,18 @@ main {
compileText(VMTarget(), optimize=false, src, writeAssembly=false, errors = errors) shouldBe null
errors.errors.single() shouldContain "cannot use byte value"
}
test("const eval of address-of a memory mapped variable") {
val src = """
main {
sub start() {
&ubyte mappedvar = 1000
cx16.r0 = &mappedvar
&ubyte[8] array = &mappedvar
cx16.r0 = &array
}
}"""
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
})
@@ -8,6 +8,7 @@ import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
class TestArrayThings: FunSpec({
test("assign prefix var to array should compile fine and is not split into inplace array modification") {
@@ -147,5 +148,32 @@ main {
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
test("array target with expression for index") {
val text = """
main {
sub start() {
ubyte[] array = [1,2,3]
array[cx16.r0L+1] += 42
cx16.r0L = array[cx16.r0L+1]
}
}"""
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("split array in zeropage is okay") {
val text = """
main {
sub start() {
uword[3] @zp @split @shared thearray
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val assemblyFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
val assembly = assemblyFile.readText()
assembly shouldContain "thearray_lsb"
assembly shouldContain "thearray_msb"
}
})
@@ -74,7 +74,7 @@ class TestAsmGenSymbols: StringSpec({
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, C64Target(), 999u)
val ptProgram = IntermediateAstMaker(program, errors).transform()
val st = SymbolTableMaker(ptProgram, options).make()
return AsmGen6502Internal(ptProgram, st, options, errors)
@@ -9,6 +9,7 @@ import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.types.instanceOf
import prog8.code.ast.PtArrayIndexer
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtBinaryExpression
import prog8.code.ast.PtVariable
import prog8.code.core.DataType
import prog8.code.target.C64Target
@@ -93,7 +94,7 @@ main {
seed.type shouldBe DataType.ARRAY_UW
val assign = start.children[1] as PtAssignment
assign.target.identifier!!.name shouldBe "cx16.r0"
assign.value shouldBe instanceOf<PtArrayIndexer>()
assign.value shouldBe instanceOf<PtBinaryExpression>()
}
test("peek and poke argument types") {
@@ -318,4 +319,24 @@ main
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("inlining sub with 2 statements") {
val src="""
main {
sub start() {
init()
}
sub init() {
init_handler()
return
}
sub init_handler() {
cx16.r0++
}
}"""
compileText(VMTarget(), true, src, writeAssembly = false) shouldNotBe null
compileText(Cx16Target(), true, src, writeAssembly = false) shouldNotBe null
}
})
+2 -1
View File
@@ -34,7 +34,8 @@ internal fun compileFile(
symbolDefs = emptyMap(),
outputDir = outputDir,
errors = errors ?: ErrorReporterForTests(),
splitWordArrays = false
splitWordArrays = false,
breakpointCpuInstruction = false
)
return compileProgram(args)
}
-43
View File
@@ -1,52 +1,9 @@
package prog8.ast
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.Expression
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
fun Program.getTempVar(dt: DataType, altNames: Boolean=false): Pair<List<String>, VarDecl> {
val tmpvarName = if(altNames) {
when (dt) {
DataType.UBYTE, DataType.BOOL -> listOf("prog8_lib", "tempvar_ub2")
DataType.BYTE -> listOf("prog8_lib", "tempvar_b2")
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw2")
DataType.WORD -> listOf("prog8_lib", "tempvar_w2")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float2")
else -> throw FatalAstException("invalid dt")
}
} else {
when (dt) {
DataType.UBYTE, DataType.BOOL -> listOf("prog8_lib", "tempvar_ub")
DataType.BYTE -> listOf("prog8_lib", "tempvar_b")
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw")
DataType.WORD -> listOf("prog8_lib", "tempvar_w")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
else -> throw FatalAstException("invalid dt")
}
}
val block = this.allBlocks.first { it.name==tmpvarName[0] }
val existingDecl = block.statements.firstOrNull { it is VarDecl && it.name == tmpvarName[1] }
if(existingDecl!=null)
return Pair(tmpvarName, existingDecl as VarDecl)
// add new temp variable to the ast directly (we can do this here because we're not iterating inside those container blocks)
val decl = VarDecl(
VarDeclType.VAR, VarDeclOrigin.AUTOGENERATED, dt, ZeropageWish.DONTCARE,
null, tmpvarName[1], null, isArray = false, sharedWithAsm = false, splitArray = false, position = Position.DUMMY
)
block.statements.add(decl)
decl.linkParents(block)
return Pair(tmpvarName, decl)
}
fun maySwapOperandOrder(binexpr: BinaryExpression): Boolean {
fun ok(expr: Expression): Boolean {
return when(expr) {
+51 -51
View File
@@ -34,7 +34,7 @@ private fun ParserRuleContext.toPosition() : Position {
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
}
internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
internal fun BlockContext.toAst(isInLibrary: Boolean) : Block {
val blockstatements = block_statement().map {
when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
@@ -49,10 +49,10 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
return Block(identifier().text, integerliteral()?.toAst()?.number?.toUInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Statement> =
private fun Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
private fun VariabledeclarationContext.toAst() : Statement {
vardecl()?.let {
return it.toAst(VarDeclType.VAR, null)
}
@@ -74,7 +74,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
throw FatalAstException("weird variable decl $this")
}
private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst() : Subroutine {
private fun SubroutinedeclarationContext.toAst() : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
asmsubroutine()!=null -> asmsubroutine().toAst()
@@ -83,7 +83,7 @@ private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst() : Subroutine {
}
}
private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
private fun StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
@@ -157,7 +157,7 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
private fun AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
@@ -166,7 +166,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
subdecl.asmClobbers, null, true, inline, false, statements, toPosition())
}
private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine {
private fun RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toUInt()
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
@@ -182,7 +182,7 @@ private class AsmsubDecl(val name: String,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>)
private fun Prog8ANTLRParser.Asmsub_declContext.toAst(): AsmsubDecl {
private fun Asmsub_declContext.toAst(): AsmsubDecl {
val name = identifier().text
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
@@ -204,7 +204,7 @@ private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?)
private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
private fun Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map {
val register = it.register.text
var registerorpair: RegisterOrPair? = null
@@ -222,7 +222,7 @@ private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineRe
statusregister)
}
private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map {
val vardecl = it.vardecl()
var datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
@@ -244,7 +244,7 @@ private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutinePar
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
}
private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(): Statement {
private fun Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst()
return if(expression_list() == null)
@@ -253,7 +253,7 @@ private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(): Statement {
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
}
private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpression {
private fun FunctioncallContext.toAst(): FunctionCallExpression {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallExpression(location, mutableListOf(), toPosition())
@@ -261,30 +261,30 @@ private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpression
FunctionCallExpression(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun Prog8ANTLRParser.InlineasmContext.toAst(): InlineAssembly {
private fun InlineasmContext.toAst(): InlineAssembly {
val text = INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), false, toPosition())
}
private fun Prog8ANTLRParser.InlineirContext.toAst(): InlineAssembly {
private fun InlineirContext.toAst(): InlineAssembly {
val text = INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), true, toPosition())
}
private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
private fun ReturnstmtContext.toAst() : Return {
return Return(expression()?.toAst(), toPosition())
}
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
private fun UnconditionaljumpContext.toAst(): Jump {
val address = integerliteral()?.toAst()?.number?.toUInt()
val identifier = scoped_identifier()?.toAst()
return Jump(address, identifier, null, toPosition())
}
private fun Prog8ANTLRParser.LabeldefContext.toAst(): Statement =
private fun LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
private fun SubroutineContext.toAst() : Subroutine {
// non-asm subroutine
val returntype = sub_return_part()?.datatype()?.toAst()
return Subroutine(
@@ -302,7 +302,7 @@ private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
)
}
private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
@@ -310,7 +310,7 @@ private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
private fun Prog8ANTLRParser.Assign_targetContext.toAst() : AssignTarget {
private fun Assign_targetContext.toAst() : AssignTarget {
return when(this) {
is IdentifierTargetContext ->
AssignTarget(scoped_identifier().toAst(), null, null, scoped_identifier().toPosition())
@@ -327,31 +327,31 @@ private fun Prog8ANTLRParser.Assign_targetContext.toAst() : AssignTarget {
}
}
private fun Prog8ANTLRParser.ClobberContext.toAst() : Set<CpuRegister> {
private fun ClobberContext.toAst() : Set<CpuRegister> {
val names = this.NAME().map { it.text }
try {
return names.map { CpuRegister.valueOf(it) }.toSet()
} catch(ax: IllegalArgumentException) {
throw SyntaxError("invalid pu register", toPosition())
throw SyntaxError("invalid cpu register", toPosition())
}
}
private fun Prog8ANTLRParser.DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
private fun DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
private fun Prog8ANTLRParser.ArrayindexContext.toAst() : ArrayIndex =
private fun ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
internal fun Prog8ANTLRParser.DirectiveContext.toAst() : Directive =
internal fun DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
private fun DirectiveargContext.toAst() : DirectiveArg {
val str = stringliteral()
if(str?.encoding?.text!=null)
throw SyntaxError("don't use a string encoding for directive arguments", toPosition())
return DirectiveArg(str?.text?.substring(1, text.length-1), identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
}
private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteralNode {
private fun IntegerliteralContext.toAst(): NumericLiteralNode {
fun makeLiteral(text: String, radix: Int): NumericLiteralNode {
val integer: Int
var datatype = DataType.UBYTE
@@ -395,14 +395,14 @@ private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteralNode {
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
return when (terminal.symbol.type) {
Prog8ANTLRParser.DEC_INTEGER -> makeLiteral(integerPart, 10)
Prog8ANTLRParser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
Prog8ANTLRParser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
DEC_INTEGER -> makeLiteral(integerPart, 10)
HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text)
}
}
private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
private fun ExpressionContext.toAst() : Expression {
val litval = literalvalue()
if(litval!=null) {
@@ -482,7 +482,7 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
throw FatalAstException(text)
}
private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral {
private fun CharliteralContext.toAst(): CharLiteral {
val text = this.SINGLECHAR().text
val enc = this.encoding?.text
val encoding =
@@ -499,7 +499,7 @@ private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral {
}
}
private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteral {
private fun StringliteralContext.toAst(): StringLiteral {
val text=this.STRING().text
val enc = encoding?.text
val encoding =
@@ -516,23 +516,23 @@ private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteral {
}
}
private fun Prog8ANTLRParser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun Prog8ANTLRParser.Scoped_identifierContext.toAst() : IdentifierReference =
private fun Scoped_identifierContext.toAst() : IdentifierReference =
IdentifierReference(NAME().map { it.text }, toPosition())
private fun Prog8ANTLRParser.FloatliteralContext.toAst() = text.toDouble()
private fun FloatliteralContext.toAst() = text.toDouble()
private fun Prog8ANTLRParser.BooleanliteralContext.toAst() = when(text) {
private fun BooleanliteralContext.toAst() = when(text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(text)
}
private fun Prog8ANTLRParser.ArrayliteralContext.toAst() : Array<Expression> =
private fun ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfElse {
private fun If_stmtContext.toAst(): IfElse {
val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
@@ -542,11 +542,11 @@ private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfElse {
return IfElse(condition, trueScope, elseScope, toPosition())
}
private fun Prog8ANTLRParser.Else_partContext.toAst(): MutableList<Statement> {
private fun Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun Prog8ANTLRParser.Branch_stmtContext.toAst(): ConditionalBranch {
private fun Branch_stmtContext.toAst(): ConditionalBranch {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
@@ -556,11 +556,11 @@ private fun Prog8ANTLRParser.Branch_stmtContext.toAst(): ConditionalBranch {
return ConditionalBranch(branchcondition, trueScope, elseScope, toPosition())
}
private fun Prog8ANTLRParser.BranchconditionContext.toAst() = BranchCondition.valueOf(
private fun BranchconditionContext.toAst() = BranchCondition.valueOf(
text.substringAfter('_').uppercase()
)
private fun Prog8ANTLRParser.ForloopContext.toAst(): ForLoop {
private fun ForloopContext.toAst(): ForLoop {
val loopvar = scoped_identifier().toAst()
val iterable = expression()!!.toAst()
val scope =
@@ -571,9 +571,9 @@ private fun Prog8ANTLRParser.ForloopContext.toAst(): ForLoop {
return ForLoop(loopvar, iterable, scope, toPosition())
}
private fun Prog8ANTLRParser.BreakstmtContext.toAst() = Break(toPosition())
private fun BreakstmtContext.toAst() = Break(toPosition())
private fun Prog8ANTLRParser.WhileloopContext.toAst(): WhileLoop {
private fun WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
@@ -581,7 +581,7 @@ private fun Prog8ANTLRParser.WhileloopContext.toAst(): WhileLoop {
return WhileLoop(condition, scope, toPosition())
}
private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
private fun RepeatloopContext.toAst(): RepeatLoop {
val iterations = expression()?.toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
@@ -589,7 +589,7 @@ private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
return RepeatLoop(iterations, scope, toPosition())
}
private fun Prog8ANTLRParser.UnrollloopContext.toAst(): UnrollLoop {
private fun UnrollloopContext.toAst(): UnrollLoop {
val iterations = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
@@ -597,7 +597,7 @@ private fun Prog8ANTLRParser.UnrollloopContext.toAst(): UnrollLoop {
return UnrollLoop(iterations, scope, toPosition())
}
private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
private fun UntilloopContext.toAst(): UntilLoop {
val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
@@ -605,13 +605,13 @@ private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
return UntilLoop(scope, untilCondition, toPosition())
}
private fun Prog8ANTLRParser.WhenstmtContext.toAst(): When {
private fun WhenstmtContext.toAst(): When {
val condition = expression().toAst()
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
return When(condition, choices, toPosition())
}
private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
private fun When_choiceContext.toAst(): WhenChoice {
val values = expression_list()?.toAst()
val stmt = statement()?.toAst()
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
@@ -621,7 +621,7 @@ private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
return WhenChoice(values?.toMutableList(), scope, toPosition())
}
private fun Prog8ANTLRParser.VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
val options = decloptions()
val zp = when {
options.ZEROPAGEREQUIRE().isNotEmpty() -> ZeropageWish.REQUIRE_ZEROPAGE
@@ -395,7 +395,15 @@ data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayI
}
override fun copy() = AddressOf(identifier.copy(), arrayIndex?.copy(), position)
override fun constValue(program: Program): NumericLiteral? = null
override fun constValue(program: Program): NumericLiteral? {
val target = this.identifier.targetStatement(program) as? VarDecl
if(target?.type==VarDeclType.MEMORY) {
val address = target.value?.constValue(program)
if(address!=null)
return NumericLiteral(DataType.UWORD, address.number, position)
}
return null
}
override fun referencesIdentifier(nameInSource: List<String>) = identifier.nameInSource==nameInSource || arrayIndex?.referencesIdentifier(nameInSource)==true
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@@ -178,8 +178,7 @@ enum class VarDeclOrigin {
USERCODE,
SUBROUTINEPARAM,
STRINGLITERAL,
ARRAYLITERAL,
AUTOGENERATED
ARRAYLITERAL
}
enum class VarDeclType {
+15 -2
View File
@@ -11,12 +11,18 @@ Before you can compile Prog8 programs, you'll have to download or build the comp
First make sure you have installed the :ref:`requirements`.
Then you can choose a few ways to get a compiler:
**Download a precompiled version from github:**
**Download an official release version from Github:**
#. download a recent "fat-jar" (called something like "prog8compiler-all.jar") from `the releases on Github <https://github.com/irmen/prog8/releases>`_
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it (use the correct name and version of the jar file you've downloaded).
**Using the Gradle build system to build it yourself:**
**Or, download a bleeding edge development version from Github:**
#. find the latest CI build on `the actions page on Github <https://github.com/irmen/prog8/actions>`_
#. download the zipped jar artifact from that build, and unzip it.
#. run the compiler with "java -jar prog8compiler.jar" (use the correct name and version of the jar file you've downloaded).
**Or, use the Gradle build system to build it yourself from source:**
The Gradle build system is used to build the compiler.
The most interesting gradle commands to run are probably the ones listed below.
@@ -166,6 +172,13 @@ One or more .p8 module files
``-asmlist``
Generate an assembler listing file as well.
``-breakinstr``
Also output a CPU instruction for a ``%breakpoint``, as well as the entry in the vice monitor list file.
This can be useful on emulators/systems that don't parse the breakpoint information in the list file,
such as the X16Emu emulator for the Commander X16. Prog8 then uses a STP instruction (65c02) to trigger a
breakpoint in the debugger (if enabled with -debug on the emulator). On a 6502 CPU, this option
will output a BRK instruction instead.
``-expericodegen``
Use experimental code generation backend (*incomplete*).
+3 -3
View File
@@ -21,7 +21,7 @@ while still being low level enough to create high performance programs.
You can compile programs for various machines with this CPU:
* Commodore 64
* Commander X16 (release R42 or newer is required)
* Commander X16
* Commodore 128 (limited support for now)
* Atari 800 XL (limited support for now)
@@ -171,7 +171,7 @@ It's easy to compile yourself, but a recent precompiled .exe (only for Windows)
`the files section <https://sourceforge.net/projects/tass64/files/binaries/>`_ in the official project on sourceforge.
*You need at least version 1.58.0 of this assembler.*
If you are on a Debian based Linux, there's a "64tass" package in the repositories, which is a bit old, but it seems to work.
It's possible to use old versions of 64tass, but it is likely that certain things will break.
It might be possible to use old versions of 64tass, but it is likely that you'll run into assembler errors later.
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK or similar in their packages repository instead.
@@ -181,7 +181,7 @@ For MacOS you can use the Homebrew system to install a recent version of OpenJDK
Finally: an **emulator** (or a real machine of course) to test and run your programs on.
In C64 mode, the compiler assumes the presence of the `VICE emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the Commander X16 instead,
download a recent emulator version (R42 or newer) for the CommanderX16, such as `x16emu <https://cx16forum.com/forum/viewforum.php?f=30>`_
download a recent emulator version for the CommanderX16, such as `x16emu <https://cx16forum.com/forum/viewforum.php?f=30>`_
(preferred, this is the official emulator. If required, source code is `here <https://github.com/X16Community/x16-emulator/>`_.
There is also `Box16 <https://github.com/indigodarkwolf/box16>`_ which has powerful debugging features.
You can select which one you want to launch using the ``-emu`` or ``-emu2`` command line options.
+40 -3
View File
@@ -132,6 +132,9 @@ sys (part of syslib)
``set_leds_brightness (ubyte activity, ubyte power)`` (commander x16 only)
Sets the brightness of the activity and power leds on the computer.
``disable_caseswitch()`` and ``enable_caseswitch()``
Disable or enable the ability to switch character set case using a keyboard combination.
conv
----
@@ -155,6 +158,9 @@ dealing with text-based input and output (to the screen). Such as
- scrolling the text on the screen
- placing individual characters on the screen
All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
these work with PETSCII encoding instead.
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/textio.p8>`_
to see what's in there. (Note: slight variations for different compiler targets)
@@ -176,11 +182,21 @@ On the Commander X16 it tries to use that machine's fast Kernal loading routines
Routines to directly load data into video ram are also present (vload and vload_raw).
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
to 16 bits, 64Kb)
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir) and to relabel the disk.
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir), to relabel the disk,
and to seek in open files.
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/diskio.p8>`_
to see what's in there. (Note: slight variations for different compiler targets)
.. note::
If you are using the X16 emulator with HostFS, and are experiencing weird behavior with these
routines, please first try again with an SD-card image instead of HostFs.
It is possible that there are still small differences between HostFS and actual CBM DOS in the X16 emulator.
.. attention::
Error handling is peculiar on CBM dos systems (C64, C128, cx16, PET). Read the
descriptions for the various methods in this library for details and tips.
string
------
@@ -225,6 +241,10 @@ Provides string manipulation routines.
Often you don't have to call this explicitly and can just write ``string1 = string2``
but this function is useful if you're dealing with addresses for instance.
``append (string, suffix) -> ubyte length``
Appends the suffix string to the other string (make sure the memory buffer is large enough!)
Returns the length of the combined string.
``lower (string)``
Lowercases the PETSCII-string in place.
@@ -402,6 +422,20 @@ but perhaps the provided ones can be of service too.
If you already know the quadrant and x/y deltas, calculate discrete direction between 0 and 23.
This is a heavily optimized routine (small and fast).
``diff (ubyte b1, ubyte b2) -> ubyte``
Returns the absolute difference, or distance, between the two byte values.
(This routine is more efficient than doing a compare and a subtract separately, or using abs)
``diffw (uword w1, uword w2) -> uword``
Returns the absolute difference, or distance, between the two word values.
(This routine is more efficient than doing a compare and a subtract separately, or using abs)
``mul16_last_upper () -> uword``
Fetches the upper 16 bits of the previous 16*16 bit multiplication.
To avoid corrupting the result, it is best performed immediately after the multiplication.
Note: It is only for the regular 6502 cpu multiplication routine.
It does not work for the verafx multiplication routines on the Commander X16!
These have a different way to obtain the upper 16 bits of the result: just read cx16.r0.
cx16logo
@@ -447,8 +481,8 @@ to see what's in there.
Information about the exposed debug registers is in the `emulator's documentation <https://github.com/X16Community/x16-emulator#debug-io-registers>`_.
monogfx (cx16 only)
--------------------
monogfx (cx16 and virtual)
---------------------------
Full-screen lores or hires monochrome bitmap graphics routines, available on the Cx16 machine only.
Same interface as gfx2, but is optimized for monochrome (1 bpp) screens.
@@ -531,6 +565,9 @@ the emulators already support it).
Note: there is a block level %option "verafxmuls" that automatically replaces all word multiplications in that block
by calls to verafx.muls/mult, but be careful with it because it may interfere with other Vera operations or IRQs.
Note: the lower 16 bits of the 32 bits result is returned as the normal subroutine's returnvalue,
but the upper 16 bits is returned in `cx16.r0` so you can still access those separately.
``clear``
Very quickly clear a piece of vram to a given byte value (it writes 4 bytes at a time).
The routine is around 3 times faster as a regular unrolled loop to clear vram.
+2 -1
View File
@@ -865,7 +865,8 @@ pokew (address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
pokemon (address, value)
Doesn't do anything useful. Also doesn't have anything to do with a certain video game.
Like poke(), but also returns the previous value in the given address.
Also doesn't have anything to do with a certain video game.
push (value)
pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.
+6
View File
@@ -100,6 +100,12 @@ Directives
Global setting, can occur multiple times. It allows you to reserve or 'block' a part of the zeropage so
that it will not be used by the compiler.
.. data:: %zpallowed <fromaddress>,<toaddress>
Level: module.
Global setting, can occur multiple times. It allows you to designate a part of the zeropage that
the compiler is allowed to use (if other options don't prevent usage).
.. data:: %address <address>
+4 -3
View File
@@ -1,9 +1,10 @@
TODO
====
- [on branch: shortcircuit] investigate McCarthy evaluation again? this may also reduce code size perhaps for things like if a>4 or a<2 ....
- [on branch: ir-less-branch-opcodes] IR: reduce the number of branch instructions such as BEQ, BEQR, etc (gradually), replace with CMP(I) + status branch instruction
- IR: reduce amount of CMP/CMPI after instructions that set the status bits correctly (LOADs? INC? etc), but only after setting the status bits is verified!
- IR: reduce amount of CMP/CMPI after instructions that set the status bits correctly (LOADs? INC? Bitwise operations, etc), but only after setting the status bits is verified!
...
@@ -53,10 +54,10 @@ Compiler:
Libraries:
- gfx2 cs_innerloop640() extend number of bytes cleared, and use vera fx cached writes
- gfx2 cs_innerloop640() extend number of bytes cleared, and use vera fx cached writes (speed up screen clear)
- gfx2 horizontal_line and vertical_line: use vera fx cached writes
- port monogfx, gf2, graphics to the vm
- use verafx transparent writes to speed up pixel plotting in gfx2 and monogfx modules (avoids read/mask/write)
- port gfx2 to the vm? First add colors to the vm?
- fix the problems in atari target, and flesh out its libraries.
- c128 target: make syslib more complete (missing kernal routines)?
- pet32 target: make syslib more complete (missing kernal routines)?
+1
View File
@@ -6,6 +6,7 @@
; "unlimited sprites / bobs" demo effect.
; Note that everything is prog8, no inline assembly used/required.
; Note2: these aren't actual sprites, it's a bitmap backbuffer display trick.
; TODO don't add bobs in pairs of 2 make them appear individually
main {
sub start() {
+35 -75
View File
@@ -6,14 +6,13 @@
main {
ubyte[256] buffer = 0 to 255
const ubyte REPEATS = 2
sub print_speed(uword jiffies) {
if jiffies==0 {
txt.print("\n 0 jiffies measured, speed is extremely high\n")
return
}
float speed = 65536.0 * REPEATS / (jiffies as float / 60.0)
float speed = floats.floor(65536.0 / (jiffies as float / 60.0))
txt.nl()
txt.print_uw(jiffies)
txt.print(" jiffies = ")
@@ -22,95 +21,56 @@ main {
}
sub start() {
txt.print("\n\ndisk benchmark. drive 8. repeats = ")
txt.print_ub(REPEATS)
txt.print("\n\ndisk benchmark on drive 8.\n\n")
uword batchtotaltime
txt.print("\n\nwriting 64kb using save")
batchtotaltime = 0
repeat REPEATS {
cbm.SETTIM(0,0,0)
void diskio.save("@:benchmark.dat", $100, 32768)
void diskio.save("@:benchmark.dat", $100, 32768)
batchtotaltime += cbm.RDTIM16()
txt.chrout('.')
}
print_speed(batchtotaltime)
txt.print("writing 64kb using save()")
cbm.SETTIM(0,0,0)
; save 2 times 32Kb to make it 64Kb total
void diskio.save("@:benchmark.dat", $100, 32768)
void diskio.save("@:benchmark.dat", $100, 32768)
print_speed(cbm.RDTIM16())
txt.print("\nwriting 64kb sequentially")
batchtotaltime = 0
repeat REPEATS {
if diskio.f_open_w("@:benchmark.dat") {
cbm.SETTIM(0,0,0)
repeat 65536/256 {
if not diskio.f_write(buffer, 256)
sys.exit(1)
}
batchtotaltime += cbm.RDTIM16()
diskio.f_close_w()
if diskio.f_open_w("@:benchmark.dat") {
cbm.SETTIM(0,0,0)
repeat 65536/256 {
if not diskio.f_write(buffer, 256)
sys.exit(1)
}
txt.chrout('.')
diskio.f_close_w()
print_speed(cbm.RDTIM16())
}
print_speed(batchtotaltime)
txt.print("\nreading 64kb using load into hiram")
batchtotaltime = 0
repeat REPEATS {
cbm.SETTIM(0,0,0)
cx16.rambank(4)
if not diskio.load("benchmark.dat", $a000)
sys.exit(1)
batchtotaltime += cbm.RDTIM16()
txt.chrout('.')
}
print_speed(batchtotaltime)
txt.print("\nreading 64kb using load() into hiram")
cbm.SETTIM(0,0,0)
cx16.rambank(4)
if not diskio.load("benchmark.dat", $a000)
sys.exit(1)
print_speed(cbm.RDTIM16())
txt.print("\nreading 64kb using vload into videoram")
batchtotaltime = 0
repeat REPEATS {
cbm.SETTIM(0,0,0)
if not diskio.vload("benchmark.dat", 0, $0000)
sys.exit(1)
batchtotaltime += cbm.RDTIM16()
txt.chrout('.')
}
print_speed(batchtotaltime)
txt.print("\nreading 64kb using vload() into vram")
cbm.SETTIM(0,0,0)
if not diskio.vload("benchmark.dat", 0, $0000)
sys.exit(1)
print_speed(cbm.RDTIM16())
txt.print("\nreading 64kb sequentially")
batchtotaltime = 0
repeat REPEATS {
if diskio.f_open("benchmark.dat") {
cbm.SETTIM(0,0,0)
repeat 65536/255 {
if not diskio.f_read(buffer, 255)
sys.exit(1)
}
batchtotaltime += cbm.RDTIM16()
diskio.f_close()
if diskio.f_open("benchmark.dat") {
cbm.SETTIM(0,0,0)
repeat 65536/255 {
if not diskio.f_read(buffer, 255)
sys.exit(1)
}
txt.chrout('.')
diskio.f_close()
print_speed(cbm.RDTIM16())
}
print_speed(batchtotaltime)
txt.print("\nreading 64kb sequentially (x16 optimized)")
batchtotaltime = 0
repeat REPEATS {
if diskio.f_open("benchmark.dat") {
cbm.SETTIM(0,0,0)
repeat 65536/255 {
if not diskio.f_read(buffer, 255)
sys.exit(1)
}
batchtotaltime += cbm.RDTIM16()
diskio.f_close()
}
txt.chrout('.')
}
print_speed(batchtotaltime)
txt.nl()
txt.print(diskio.status())
txt.print("\ndone.\n")
diskio.delete("benchmark.dat")
}
}
+10 -11
View File
@@ -31,17 +31,16 @@ main {
read_last_bytes()
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
; txt.print("\nseeking to 1292 and writing a few bytes...\n")
; if diskio.f_open_w("seektestfile.bin,p,a") {
; diskio.f_seek_w(0, 1292)
; void diskio.f_write("123", 3)
; diskio.f_close_w()
; } else {
; txt.print("error: ")
; txt.print(diskio.status())
; sys.exit(1)
; }
txt.print("\nseeking to 1292 and writing a few bytes...\n")
if diskio.f_open_w_seek("seektestfile.bin") {
diskio.f_seek_w(0, 1292)
void diskio.f_write("123", 3)
diskio.f_close_w()
} else {
txt.print("error: ")
txt.print(diskio.status())
sys.exit(1)
}
read_last_bytes()
}
+18 -14
View File
@@ -47,8 +47,8 @@ adpcm {
uword @requirezp predict_2 ; decoded 16 bit pcm sample for second channel.
ubyte @requirezp index
ubyte @requirezp index_2
uword @zp pstep
uword @zp pstep_2
uword @requirezp pstep
uword @requirezp pstep_2
sub init(uword startPredict, ubyte startIndex) {
; initialize first decoding channel.
@@ -65,8 +65,10 @@ adpcm {
}
sub decode_nibble(ubyte nibble) {
; decoder for nibbles for the first channel.
; Decoder for nibbles for the first channel.
; this is the hotspot of the decoder algorithm!
; Note that the generated assembly from this is pretty efficient,
; rewriting it by hand in asm seems to improve it only 5-10%
cx16.r0s = 0 ; difference
if nibble & %0100
cx16.r0s += pstep
@@ -91,28 +93,30 @@ adpcm {
; predicted = - 32767
index += t_index[nibble]
if_neg ; was: if index & 128
if_neg
index = 0
else if index > len(t_step)-1
else if index >= len(t_step)-1
index = len(t_step)-1
pstep = t_step[index]
}
sub decode_nibble_second(ubyte nibble_2) {
; decoder for nibbles for the second channel.
sub decode_nibble_second(ubyte nibble) {
; Decoder for nibbles for the second channel.
; this is the hotspot of the decoder algorithm!
; Note that the generated assembly from this is pretty efficient,
; rewriting it by hand in asm seems to improve it only 5-10%
cx16.r0s = 0 ; difference
if nibble_2 & %0100
if nibble & %0100
cx16.r0s += pstep_2
pstep_2 >>= 1
if nibble_2 & %0010
if nibble & %0010
cx16.r0s += pstep_2
pstep_2 >>= 1
if nibble_2 & %0001
if nibble & %0001
cx16.r0s += pstep_2
pstep_2 >>= 1
cx16.r0s += pstep_2
if nibble_2 & %1000
if nibble & %1000
predict_2 -= cx16.r0s
else
predict_2 += cx16.r0s
@@ -124,10 +128,10 @@ adpcm {
; elif predicted < -32767:
; predicted = - 32767
index_2 += t_index[nibble_2]
if_neg ; was: if index & 128
index_2 += t_index[nibble]
if_neg
index_2 = 0
else if index_2 > len(t_step)-1
else if index_2 >= len(t_step)-1
index_2 = len(t_step)-1
pstep_2 = t_step[index_2]
}
+129 -87
View File
@@ -1,10 +1,3 @@
%import textio
%import diskio
%import floats
%import adpcm
%import wavfile
%option no_sysinit
;
; This program can stream a regular .wav file from the sdcard.
; It can be uncompressed or IMA-adpcm compressed (factor 4 lossy compression).
@@ -22,33 +15,54 @@
; is slightly more efficient because the data blocks are then sector-aligned on the disk
;
%import diskio
%import floats
%import adpcm
%import wavfile
%import textio
%option no_sysinit
main {
str MUSIC_FILENAME = "?"*32
uword vera_rate_hz
ubyte vera_rate
sub start() {
uword buffer = memory("buffer", 1024, 256)
str MUSIC_FILENAME = "?"*32
txt.print("name of .wav file to play on drive 8: ")
while 0==txt.input_chars(MUSIC_FILENAME) {
; until user types a name...
}
prepare_music()
txt.print("\ngood file! playback starts! ")
cx16.rombank(0) ; activate kernal bank for faster calls
interrupts.wait()
interrupts.set_handler()
play_stuff()
txt.print("done!\n")
repeat { }
}
bool wav_ok = false
sub error(str msg) {
txt.print(msg)
repeat { }
}
sub prepare_music() {
txt.print("\nchecking ")
txt.print(MUSIC_FILENAME)
txt.nl()
bool wav_ok = false
if diskio.f_open(MUSIC_FILENAME) {
void diskio.f_read(buffer, 128)
wav_ok = wavfile.parse_header(buffer)
void diskio.f_read(music.buffer, 128)
wav_ok = wavfile.parse_header(music.buffer)
diskio.f_close()
}
if not wav_ok {
if not wav_ok
error("no good wav file!")
}
calculate_vera_rate()
txt.print("wav format: ")
txt.print_ub(wavfile.wavefmt)
txt.print("\nchannels: ")
@@ -74,9 +88,8 @@ main {
if wavfile.nchannels>2 or
(wavfile.wavefmt!=wavfile.WAVE_FORMAT_DVI_ADPCM and wavfile.wavefmt!=wavfile.WAVE_FORMAT_PCM) or
wavfile.sample_rate > 48828 or
wavfile.bits_per_sample>16 {
error("unsupported format!")
}
wavfile.bits_per_sample>16
error("unsupported format!")
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
if(wavfile.block_align!=256) {
@@ -84,8 +97,6 @@ main {
}
}
txt.print("\ngood file! playback starts!\n")
cx16.rombank(0) ; activate kernal bank for faster calls
cx16.VERA_AUDIO_RATE = 0 ; halt playback
cx16.VERA_AUDIO_CTRL = %10101011 ; mono 16 bit, volume 11
if wavfile.nchannels==2
@@ -94,37 +105,6 @@ main {
cx16.VERA_AUDIO_CTRL &= %11011111 ; set to 8 bit instead
repeat 1024
cx16.VERA_AUDIO_DATA = 0 ; fill buffer with short silence
sys.set_irqd()
cx16.CINV = &interrupt.handler
cx16.VERA_IEN = %00001000 ; enable AFLOW only for now
sys.clear_irqd()
if diskio.f_open(MUSIC_FILENAME) {
uword block_size = 1024
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM
block_size = wavfile.block_align * 2 ; read 2 adpcm blocks at a time (512 bytes)
void diskio.f_read(buffer, wavfile.data_offset) ; skip to actual sample data start
void diskio.f_read(buffer, block_size) ; preload buffer
cx16.VERA_AUDIO_RATE = vera_rate ; start playback
repeat {
interrupt.wait_and_clear_aflow_semaphore()
;; cx16.vpoke(1,$fa00, $a0) ; paint a screen color
uword size = diskio.f_read(buffer, block_size)
;; cx16.vpoke(1,$fa00, $00) ; paint a screen color
if size<block_size
break
txt.chrout('.')
}
diskio.f_close()
} else {
txt.print("load error")
}
cx16.VERA_AUDIO_RATE = 0 ; halt playback
txt.print("done!\n")
repeat {
}
}
sub calculate_vera_rate() {
@@ -133,51 +113,78 @@ main {
vera_rate_hz = (vera_rate as float) * vera_freq_factor as uword
}
sub error(str msg) {
txt.print(msg)
repeat {
sub play_stuff() {
if diskio.f_open(MUSIC_FILENAME) {
uword block_size = 1024
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM
block_size = wavfile.block_align * 2 ; read 2 adpcm blocks at a time (512 bytes)
void diskio.f_read(music.buffer, wavfile.data_offset) ; skip to actual sample data start
music.pre_buffer(block_size)
cx16.VERA_AUDIO_RATE = vera_rate ; start audio playback
str progress_chars = "-\\|/-\\|/"
ubyte progress = 0
repeat {
interrupts.wait()
if interrupts.vsync {
interrupts.vsync=false
; ...do something triggered by vsync irq...
}
if interrupts.aflow {
interrupts.aflow=false
if not music.load_next_block(block_size)
break
; Note: copying the samples into the fifo buffer is done by the aflow interrupt handler itself.
txt.chrout(progress_chars[progress/2 & 7])
txt.chrout($9d) ; cursor left
progress++
}
}
diskio.f_close()
} else {
error("load error")
}
cx16.VERA_AUDIO_RATE = 0 ; halt playback
}
}
interrupt {
interrupts {
bool aflow_semaphore
uword @requirezp nibblesptr
sub set_handler() {
sys.set_irqd()
cx16.CINV = &handler ; handles both AFLOW and VSYNC
cx16.VERA_IEN = %00001001 ; enable AFLOW + VSYNC
sys.clear_irqd()
}
asmsub wait_and_clear_aflow_semaphore() {
bool aflow
bool vsync
asmsub wait() {
%asm {{
- wai
lda p8_aflow_semaphore
bne -
inc p8_aflow_semaphore
rts
wai
}}
}
sub handler() {
if cx16.VERA_ISR & %00001000 {
; AFLOW irq occurred, refill buffer
aflow_semaphore = 0
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
nibblesptr = main.start.buffer
if wavfile.nchannels==2 {
adpcm_block_stereo()
adpcm_block_stereo()
}
else {
adpcm_block_mono()
adpcm_block_mono()
}
}
else if wavfile.bits_per_sample==16
uncompressed_block_16()
else
uncompressed_block_8()
} else if cx16.VERA_ISR & %00000001 {
; Filling the fifo is the only way to clear the Aflow irq.
; So we do this here, otherwise the aflow irq will keep triggering.
; Note that filling the buffer with fresh audio samples is NOT done here,
; but instead in the main program code that triggers on the 'aflow' being true!
cx16.save_virtual_registers()
music.aflow_play_block()
cx16.restore_virtual_registers()
aflow = true
}
if cx16.VERA_ISR & %00000001 {
cx16.VERA_ISR = %00000001
; handle vsync irq here
vsync = true
}
%asm {{
@@ -188,13 +195,49 @@ interrupt {
}}
}
}
music {
uword @requirezp nibblesptr
uword buffer = memory("buffer", 1024, 256)
sub pre_buffer(uword block_size) {
; pre-buffer first block
void diskio.f_read(buffer, block_size)
}
sub aflow_play_block() {
; play block that is currently in the buffer
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
nibblesptr = buffer
if wavfile.nchannels==2 {
adpcm_block_stereo()
adpcm_block_stereo()
}
else {
adpcm_block_mono()
adpcm_block_mono()
}
}
else if wavfile.bits_per_sample==16
uncompressed_block_16()
else
uncompressed_block_8()
}
sub load_next_block(uword block_size) -> bool {
; read next block from disk into the buffer, for next time the irq triggers
return diskio.f_read(buffer, block_size) == block_size
}
asmsub uncompressed_block_8() {
; optimized loop to put 1024 bytes of data into the fifo as fast as possible
; converting unsigned wav 8 bit samples to signed 8 bit on the fly
%asm {{
lda p8_main.p8_start.p8_buffer
lda p8_buffer
sta cx16.r0L
lda p8_main.p8_start.p8_buffer+1
lda p8_buffer+1
sta cx16.r0H
ldx #4
- ldy #0
@@ -222,9 +265,9 @@ interrupt {
asmsub uncompressed_block_16() {
; optimized loop to put 1024 bytes of data into the fifo as fast as possible
%asm {{
lda p8_main.p8_start.p8_buffer
lda p8_buffer
sta cx16.r0L
lda p8_main.p8_start.p8_buffer+1
lda p8_buffer+1
sta cx16.r0H
ldx #4
- ldy #0
@@ -279,7 +322,6 @@ interrupt {
decode_nibbles_unrolled()
}
sub decode_nibbles_unrolled() {
; decode 4 left channel nibbles
uword[8] left
+1 -1
View File
@@ -54,6 +54,6 @@ main {
return 0
else if screenx>255+32
return 255
return lsb(screenx-32)
return lsb(screenx)-32
}
}
+6 -1
View File
@@ -2,10 +2,15 @@
from PIL import Image
def make_cx16_palette(palette: list[int]) -> bytes:
def to4bit(color: int) -> int:
return (color * 15 + 135) >> 8 # see https://threadlocalmutex.com/?p=48
cx16palette = bytearray()
for pi in range(0, len(palette), 3):
r, g, b = palette[pi] >> 4, palette[pi + 1] >> 4, palette[pi + 2] >> 4
r = to4bit(palette[pi])
g = to4bit(palette[pi+1])
b = to4bit(palette[pi+2])
cx16palette.append(g << 4 | b)
cx16palette.append(r)
return cx16palette
+1
View File
@@ -0,0 +1 @@
pillow
+20 -20
View File
@@ -61,21 +61,21 @@ main {
byte decisionOver2 = 1-x as byte
while x>=y {
txt.setcc(xcenter + x, ycenter + y as ubyte, 81, 1)
txt.setcc(xcenter - x, ycenter + y as ubyte, 81, 2)
txt.setcc(xcenter + x, ycenter - y as ubyte, 81, 3)
txt.setcc(xcenter - x, ycenter - y as ubyte, 81, 4)
txt.setcc(xcenter + y, ycenter + x as ubyte, 81, 5)
txt.setcc(xcenter - y, ycenter + x as ubyte, 81, 6)
txt.setcc(xcenter + y, ycenter - x as ubyte, 81, 7)
txt.setcc(xcenter - y, ycenter - x as ubyte, 81, 8)
txt.setcc(xcenter + x, ycenter + y, 81, 1)
txt.setcc(xcenter - x, ycenter + y, 81, 2)
txt.setcc(xcenter + x, ycenter - y, 81, 3)
txt.setcc(xcenter - x, ycenter - y, 81, 4)
txt.setcc(xcenter + y, ycenter + x, 81, 5)
txt.setcc(xcenter - y, ycenter + x, 81, 6)
txt.setcc(xcenter + y, ycenter - x, 81, 7)
txt.setcc(xcenter - y, ycenter - x, 81, 8)
y++
if decisionOver2<=0
decisionOver2 += 2*y+1
else {
if decisionOver2>=0 {
x--
decisionOver2 += 2*(y-x)+1
decisionOver2 -= 2*x
}
decisionOver2 += 2*y
decisionOver2++
}
}
@@ -89,23 +89,23 @@ main {
while x>=y {
xx = cx-x
repeat 2*x+1 {
txt.setcc(xx, cy + y as ubyte, 81, 11)
txt.setcc(xx, cy - y as ubyte, 81, 12)
txt.setcc(xx, cy + y, 81, 11)
txt.setcc(xx, cy - y, 81, 12)
xx++
}
xx = cx-y
repeat 2*y+1 {
txt.setcc(xx, cy + x as ubyte, 81, 13)
txt.setcc(xx, cy - x as ubyte, 81, 14)
txt.setcc(xx, cy + x, 81, 13)
txt.setcc(xx, cy - x, 81, 14)
xx++
}
y++
if decisionOver2<=0
decisionOver2 += 2*y+1
else {
if decisionOver2>=0 {
x--
decisionOver2 += 2*(y-x)+1
decisionOver2 -= 2*x
}
decisionOver2 += 2*y
decisionOver2++
}
}
}
+124 -13
View File
@@ -1,18 +1,129 @@
%option no_sysinit
%import textio
%import diskio
%zeropage basicsafe
main {
sub start() {
cx16.r1H = %00000001 ; enable auto-indent
cx16.r2L = 4
cx16.r2H = 80
cx16.r3L = 8
cx16.r3H = 11<<4 | 7; background_color<<4 | text_color
cx16.r4 = 0
cx16.r1L = 0
cx16.rombank($d)
%asm {{
ldx #2
ldy #64
jmp $c006
}}
str filename = "doesnotexist.xyz"
ubyte status
txt.print("read open...\n")
if diskio.f_open(filename) {
status = cbm.READST()
txt.print("open ok - wrong! status=")
} else {
status = cbm.READST()
txt.print("open failed - good! status=")
}
txt.print_ub(status)
txt.nl()
txt.print(diskio.status())
txt.nl()
txt.print("\nwriting the file\n")
if diskio.f_open_w(filename) {
cx16.r0 = diskio.status()
if @(cx16.r0)=='0' {
diskio.f_write("test", 4)
diskio.f_close_w()
status = cbm.READST()
txt.print("write ok! good! status=\n")
} else {
txt.print("write open error, status=")
txt.print(cx16.r0)
txt.nl()
}
} else {
status = cbm.READST()
txt.print("write open failed! wrong! status=\n")
}
txt.print_ub(status)
txt.nl()
txt.print(diskio.status())
txt.nl()
txt.print("\nread open...\n")
if diskio.f_open(filename) {
status = cbm.READST()
txt.print("open ok - good! status=")
} else {
status = cbm.READST()
txt.print("open failed - wrong! status=")
}
txt.print_ub(status)
txt.nl()
txt.print(diskio.status())
txt.nl()
}
}
main33 {
sub print_buffer() {
uword ptr = $4000
uword firstAA = ptr
repeat 320*6 {
txt.print_ubhex(@(ptr), false)
if @(ptr)==$aa {
firstAA = ptr
break
}
ptr++
}
txt.nl()
txt.print("first $aa is at ")
txt.print_uwhex(firstAA, true)
txt.print(" = offset ")
txt.print_uw(firstAA-$4000)
txt.nl()
}
sub start() {
uword buffer = $4000
sys.memset(buffer, 2000, $aa)
print_buffer()
void cx16.screen_mode($80, false) ; 320*240 * 256C
cx16.FB_cursor_position(0, 0)
cx16.FB_get_pixels($4000, 256)
void cx16.screen_mode(0, false) ; 320*240 * 256C
print_buffer()
repeat { }
void cx16.screen_mode($80, false) ; 320*240 * 256C
;; cx16.mouse_config2(1)
cx16.FB_cursor_position(0, 0)
cx16.FB_set_pixels(0, 320*4)
; expected result: exactly 12 rows of 320 pixels written
; actual result: almost 13 rows of pixels written.
repeat {}
; sys.wait(9999)
;
; ; this works:
; ubyte y
; for y in 0 to 239 step 24 {
; cx16.FB_cursor_position(0, y)
; cx16.FB_set_pixels(0, 320*24)
; }
; sys.wait(120)
;
; ; this works too:
; cx16.FB_cursor_position(0,0)
; repeat 240 cx16.FB_set_pixels(0, 320)
; sys.wait(120)
;
; ; this kills the mouse pointer because it writes past the bitmap screen data
; ; expected is to write 10 times 24 rows = 240 rows of pixels, exactly 1 screen...
; cx16.FB_cursor_position(0,0)
; unroll 10 cx16.FB_set_pixels(0, 320*24)
;
; repeat {}
}
}
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more