Compare commits

...
85 Commits
Author SHA1 Message Date
Irmen de Jong f75fd0811e restructure play-adpcm example code, stream-wav can now play stereo adpcm wavs 2023-10-11 17:37:42 +02:00
Irmen de Jong 836bc9d456 added verafx.available() 2023-10-10 22:12:21 +02:00
Irmen de Jong a37769aafe cx16 adpcm example is now able to decode and play stereo music as well as mono. 2023-10-10 02:41:20 +02:00
Irmen de Jong 68e62e4bd2 added cx16.MCIOUT() kernal call
correct case of several other cx16 kernal calls.

corrected to upper case: cx16 kernal calls CLOSE_ALL, LKUPLA, LKUPSA, JSRFAR, PRIMM, MACPTR.
2023-10-09 22:44:36 +02:00
Irmen de Jong a5cd3728c9 3d rotation multiplications now using verafx acceleration 2023-10-05 22:36:30 +02:00
Irmen de Jong a48ce35f0b added %option verafxmuls 2023-10-05 22:06:33 +02:00
Irmen de Jong e1835b5775 removed dysfunctional c128.graphics library module 2023-10-05 21:03:47 +02:00
Irmen de Jong 433832b329 gfx2.clear_screen and monogfx.clear_screen() now have color parameter to clear the screen with
this is much faster than filling a rectangle of the full screen size with a color.
2023-10-05 21:00:39 +02:00
Irmen de Jong ee81da14d6 cx16: removed monochrome modes from gfx2 (use monogfx instead). New screen mode numbering!
programs will now be a lot smaller than before if they use gfx2 (or monogfx if they were only using monochrome drawing)
monogfx also fixes some drawing errors with small horizontal lines, and stippled vertical lines.
2023-10-05 02:12:46 +02:00
Irmen de Jong 6395d1908e cx16: added monogfx library module, replaces gfx2 for monochrome screenmodes. 2023-10-04 22:32:13 +02:00
Irmen de Jong 989a5a2f8a some notes about array alignment 2023-10-04 01:10:36 +02:00
Irmen de Jong b7a622c68e fix alignment of uninitialized arrays in aligned blocks (make them initialized with zeros so they don't end up in the BSS section)
fix alignment of uninitialized arrays in aligned blocks (make them initialized with zeros so they don't end up in the BSS section)
2023-10-04 00:12:36 +02:00
Irmen de Jong a8507b437d add verafx.transparency() 2023-10-03 01:47:52 +02:00
Irmen de Jong e505bf9ccf added "emudbg" library (cx16 only) to interface with the emulator 2023-10-02 22:23:09 +02:00
Irmen de Jong a289b32053 Revert "added -verafxmul compiler option to use vera fx multiplication routine on cx16"
This reverts commit 690782bf.
It was too risky, using vera (especially fx) transparently in multiple places especially perhaps in IRQ handlers will create havoc unless much intricate care is taken to save/restore the vera state. Better to do vera fx explicitly where the programmer has full control.
2023-10-02 21:08:52 +02:00
Irmen de Jong c3f1f09ad1 added verafx.clear() 2023-10-02 01:34:56 +02:00
Irmen de Jong 70ee2026ff fix gfx2 screen fill broken when using verafx 2023-10-02 00:12:48 +02:00
Irmen de Jong 690782bf60 added -verafxmul compiler option to use vera fx multiplication routine on cx16 2023-10-01 22:44:45 +02:00
Irmen de Jong 755cc4835e \n (newline) now also maps to Petscii $0d (return), like \r.
It used to map to $8d (shift-return)
2023-09-29 01:49:15 +02:00
Irmen de Jong a684ea46e4 fix c64 zp test and improve error for text encoding problem 2023-09-29 01:25:05 +02:00
Irmen de Jong 8fbe13f99d c64: $a5 removed from free ZP (it's actually used by kernal disk routines) 2023-09-29 00:28:04 +02:00
Irmen de Jong 452e9e275f diskio module: set correct read or write i/o channel every time f_read or f_write is called 2023-09-28 23:39:37 +02:00
Irmen de Jong cd40088636 vm: added math.mul16_last_upper() 2023-09-28 03:18:49 +02:00
Irmen de Jong 9b9e6f4af5 added math.mul16_last_upper() to fetch the upper 16 bits of the last word multiplication 2023-09-25 23:59:57 +02:00
Irmen de Jong ae6eeadf54 doc about range step value has to be a constant 2023-09-25 23:19:32 +02:00
Irmen de Jong 5268b05060 added bonkram chunk to chunkfile example 2023-09-25 22:24:40 +02:00
Irmen de Jong 390263a34e added cx16 verafx library module 2023-09-24 23:00:40 +02:00
Irmen de Jong 55646edc3e added cx16 chunkedfile example 2023-09-24 20:56:36 +02:00
Irmen de Jong 8d177beb78 fix possible register corruption when calling asmsubs that require Carry flag as a parameter 2023-09-24 14:03:31 +02:00
Irmen de Jong 1da0c59182 vm: remove BNER opcode -> CMP + BSTNE 2023-09-23 11:47:24 +02:00
Irmen de Jong 36e8f10d2b vm: remove BEQR opcode -> CMP + BSTEQ 2023-09-23 11:42:58 +02:00
Irmen de Jong cdf5a8f20f vm: remove BNE opcode -> CMPI + BSTNE 2023-09-23 11:22:33 +02:00
Irmen de Jong eb64d92333 vm: remove BEQ opcode -> CMPI + BSTEQ 2023-09-23 11:21:43 +02:00
Irmen de Jong eb55da63ef weird 2023-09-23 11:21:17 +02:00
Irmen de Jong 918302f79b ir: fix possible crash in validity check about PREPARECALL 2023-09-23 01:35:18 +02:00
Irmen de Jong 9d7131d9f6 vm: setting status bits 2023-09-22 22:50:20 +02:00
Irmen de Jong 229c1114dd vm: fixed array initialization values with address-ofs 2023-09-19 23:54:18 +02:00
Irmen de Jong 885df9156f todo 2023-09-19 00:08:17 +02:00
Irmen de Jong c319233ddc ir: added preparecall 'meta' instruction for functioncalls 2023-09-18 23:22:03 +02:00
Irmen de Jong 958b5c0780 Merge branch 'addrof-arrayelt'
# Conflicts:
#	docs/source/todo.rst
2023-09-18 04:48:45 +02:00
Irmen de Jong 880c0a5da8 allow taking address of array element 2023-09-18 04:37:41 +02:00
Irmen de Jong 237c6dc856 allow taking address of array element 2023-09-18 04:29:15 +02:00
Irmen de Jong ccf6e32bf9 adding setlsb() and setmsb() builtin functions to 6502 codegen 2023-09-17 15:16:47 +02:00
Irmen de Jong a1874f6f00 adding setlsb() and setmsb() builtin functions to 6502 codegen 2023-09-17 01:48:29 +02:00
Irmen de Jong 95e4490a8a adding setlsb() and setmsb() builtin functions 2023-09-15 02:39:16 +02:00
Irmen de Jong 31c132c2eb several optimizations and compiler error fix for @(&var) and @(&var+1) 2023-09-14 23:04:23 +02:00
Irmen de Jong 00b0ec58b4 update to Antlr 4.13.1 2023-09-14 21:11:55 +02:00
Irmen de Jong a1d0e5bb65 added list of software to docs 2023-09-13 21:51:48 +02:00
Irmen de Jong 03e0d4b2e8 reducing expression codegen complexity (no longer splitting conditional expressions, and using r9 as temp var) 2023-09-13 01:08:42 +02:00
Irmen de Jong 6afdd4e6fd preparing next version 2023-09-12 21:53:49 +02:00
Irmen de Jong b500a0d477 c64: added a couple of routines that calculate the correct memory locations for video ram and sprite pointers etc. based on current VIC-II memory setup.
the examples with sprites, now use it.
2023-09-08 21:27:38 +02:00
Irmen de Jong dd2463a440 proper fix for the previous commit. + fix for i/o channel reset in diskio.f_seek()
it wasn't the adressing mode, it was that it assumed the pointer variable was always in zeropage (which might not be)
2023-09-07 22:17:46 +02:00
Irmen de Jong 23a8bebd9e fix invalid addressing mode on 6502 cpu for bytevalue +/- bytearray[i] 2023-09-07 21:40:07 +02:00
Irmen de Jong 3caf9108ad finalizing 9.4.1 release 2023-09-06 21:18:01 +02:00
Irmen de Jong bde4be8231 fix VM indexed instructions to only use lsb part of the index 2023-09-06 02:44:04 +02:00
Irmen de Jong 0bbbb12ed2 fix bench8 examples 2023-09-05 23:40:54 +02:00
Irmen de Jong b570bdaed7 fix codegen for array[i] += float expression 2023-09-05 22:38:52 +02:00
Irmen de Jong 8c0843cc87 fix an invalid 6502 instruction on c64 in certain float assignment 2023-09-05 21:54:52 +02:00
Irmen de Jong 31458ffd81 examples cleanup and improving c64 graphics module (shift bitmap to higher ram area) 2023-09-05 20:39:12 +02:00
Irmen de Jong c15c10a94e fixed 'unroll CONSTANTEXPR' compiler errors 2023-09-05 01:03:35 +02:00
Irmen de Jong 9fca978725 optimized plasma examples even more 2023-09-05 00:23:50 +02:00
Irmen de Jong b125901717 added cx16 plasma example 2023-09-04 23:54:13 +02:00
Irmen de Jong eb018ae660 code optimization for bytearray[x] +/- bytearray[y]
use adc array,y or sbc array,y instead of tempvar
2023-09-04 23:01:53 +02:00
Irmen de Jong 7e5a9474fe improve plasma example 2023-09-04 20:35:43 +02:00
Irmen de Jong 525a9b5036 prepare parser to allow chained array indexing later 2023-09-03 19:06:47 +02:00
Irmen de Jong c3fbdf34ca fixed c64 float problem 2023-09-03 16:40:10 +02:00
Irmen de Jong 48bd51e1a5 c64 float problem 2023-09-03 16:29:01 +02:00
Irmen de Jong 10d0b03a90 use less tempvars 2023-09-03 01:32:47 +02:00
Irmen de Jong e1b3582f08 fix wordvar -= @(memory) 2023-09-03 01:12:26 +02:00
Irmen de Jong 95be1c9e22 fix optimized swapped in-place byte comparisons 2023-09-03 00:47:55 +02:00
Irmen de Jong 1ce8fe06d5 fix in-place <= for bytes 2023-09-03 00:01:11 +02:00
Irmen de Jong 15c649024e float problems on c64 2023-09-02 23:09:55 +02:00
Irmen de Jong e97303c226 fix word multiplication to not clobber r0 and r1 anymore
This was causing corruption in certain programs such as the cx16/amiga example.
The problem was introduced in 9.4 with the new multiply_words routine
2023-09-02 20:52:16 +02:00
Irmen de Jong 3b786c819d avoid using temp var even more 2023-09-01 23:47:01 +02:00
Irmen de Jong 04959dbd8b optimize asm: don't use temp var for some additions 2023-09-01 22:24:17 +02:00
Irmen de Jong 5cd4b874ea tweak sprites module 2023-09-01 21:25:19 +02:00
Irmen de Jong f14ea1b3de micro optimization to save 2 cycles: change some pha+pla into tax+txa 2023-09-01 20:37:24 +02:00
Irmen de Jong 9cc0cda0fb added sprites library module (cx16 only) 2023-09-01 17:35:07 +02:00
Irmen de Jong 09a7a4bbe5 optimize comparison against zero 2023-09-01 02:28:11 +02:00
Irmen de Jong cfea8b3745 save a cycle 2023-09-01 00:50:24 +02:00
Irmen de Jong 28bf0b61ce added math.log2() and math.log2w() 2023-09-01 00:42:15 +02:00
Irmen de Jong 2dc2429735 tweaks to the cx16 sprite example 2023-08-31 23:24:46 +02:00
Irmen de Jong 83d4592526 tweaks to the cx16 sprite example 2023-08-31 22:33:49 +02:00
Irmen de Jong 2d528c26ae added cx16 sprite demo 2023-08-31 16:56:52 +02:00
Irmen de Jong 66b3dce794 doc tweak 2023-08-30 13:16:39 +02:00
147 changed files with 5596 additions and 3076 deletions

No files matched your search

+3 -3
View File
@@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.12.0">
<properties maven-id="org.antlr:antlr4:4.13.1">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.12.0/antlr4-4.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.12.0/antlr4-runtime-4.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
+2 -2
View File
@@ -1,8 +1,8 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.5" />
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+3 -3
View File
@@ -1,9 +1,9 @@
<component name="libraryTable">
<library name="slf4j.simple" type="repository">
<properties maven-id="org.slf4j:slf4j-simple:2.0.7" />
<properties maven-id="org.slf4j:slf4j-simple:2.0.9" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.7/slf4j-simple-2.0.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.9/slf4j-simple-2.0.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+3
View File
@@ -16,6 +16,9 @@
</list>
</option>
</component>
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
+2 -2
View File
@@ -32,10 +32,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
+1 -4
View File
@@ -196,13 +196,10 @@ class StStaticVariable(name: String,
}
if(onetimeInitializationNumericValue!=null) {
require(dt in NumericDatatypes)
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
}
if(onetimeInitializationArrayValue!=null) {
require(dt in ArrayDatatypes)
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
}
require(length==onetimeInitializationArrayValue.size)
}
if(onetimeInitializationStringValue!=null) {
require(dt == DataType.STR)
+7 -4
View File
@@ -74,10 +74,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
numElements = value.value.length + 1 // include the terminating 0-byte
}
is PtArray -> {
val array = makeInitialArray(value)
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
initialArray = makeInitialArray(value)
initialString = null
numElements = array.size
numElements = initialArray.size
require(node.arraySize?.toInt()==numElements)
}
else -> {
@@ -126,7 +125,11 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
return value.children.map {
when(it) {
is PtAddressOf -> StArrayElement(null, it.identifier.name)
is PtAddressOf -> {
if(it.isFromArrayElement)
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name)
}
is PtIdentifier -> StArrayElement(null, it.name)
is PtNumber -> StArrayElement(it.number, null)
else -> throw AssemblyError("invalid array element $it")
+1
View File
@@ -75,6 +75,7 @@ class PtBlock(name: String,
val library: Boolean,
val forceOutput: Boolean,
val noSymbolPrefixing: Boolean,
val veraFxMuls: Boolean,
val alignment: BlockAlignment,
val source: SourceCode, // taken from the module the block is defined in.
position: Position
+17 -2
View File
@@ -24,7 +24,17 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
infix fun isSameAs(other: PtExpression): Boolean {
return when(this) {
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
is PtAddressOf -> {
if(other !is PtAddressOf)
return false
if (other.type!==type || !(other.identifier isSameAs identifier))
return false
if(other.children.size!=children.size)
return false
if(children.size==1)
return true
return arrayIndexExpr!! isSameAs other.arrayIndexExpr!!
}
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index && other.splitWords==splitWords
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
@@ -105,7 +115,12 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
val identifier: PtIdentifier
get() = children.single() as PtIdentifier
get() = children[0] as PtIdentifier
val arrayIndexExpr: PtExpression?
get() = if(children.size==2) children[1] as PtExpression else null
val isFromArrayElement: Boolean
get() = children.size==2
}
@@ -70,6 +70,8 @@ class FSignature(val pure: Boolean, // does it have side effects?
val BuiltinFunctions: Map<String, FSignature> = mapOf(
// this set of function have no return value and operate in-place:
"setlsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
"setmsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
@@ -131,4 +133,4 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
@@ -53,11 +53,11 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
).map{it.toUInt()})
}
if(options.zeropage!= ZeropageType.DONTUSE) {
if(options.zeropage != ZeropageType.DONTUSE) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
@@ -24,7 +24,7 @@ object PetsciiEncoding {
'\ufffe', // 0x0A -> UNDEFINED
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\r' , // 0x0D -> CARRIAGE RETURN
'\n' , // 0x0D -> LINE FEED (RETURN)
'\u000e', // 0x0E -> SHIFT OUT
'\ufffe', // 0x0F -> UNDEFINED
'\ufffe', // 0x10 -> UNDEFINED
@@ -152,7 +152,7 @@ object PetsciiEncoding {
'\uf113', //  0x8A -> FUNCTION KEY 4 (CUS)
'\uf115', //  0x8B -> FUNCTION KEY 6 (CUS)
'\uf117', //  0x8C -> FUNCTION KEY 8 (CUS)
'\n' , // 0x8D -> LINE FEED
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\u000f', //  0x8E -> SHIFT IN
'\ufffe', // 0x8F -> UNDEFINED
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
@@ -283,7 +283,7 @@ object PetsciiEncoding {
'\ufffe', // 0x0A -> UNDEFINED
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\r' , // 0x0D -> CARRIAGE RETURN
'\n' , // 0x0D -> LINE FEED (RETURN)
'\u000e', // 0x0E -> SHIFT OUT
'\ufffe', // 0x0F -> UNDEFINED
'\ufffe', // 0x10 -> UNDEFINED
@@ -411,7 +411,7 @@ object PetsciiEncoding {
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
'\n' , // 0x8D -> LINE FEED
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\u000f', // 0x8E -> SHIFT IN
'\ufffe', // 0x8F -> UNDEFINED
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
@@ -1061,6 +1061,7 @@ object PetsciiEncoding {
'}' -> '├'
'|' -> '│'
'\\' -> '╲'
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
else -> chr
}
@@ -1076,7 +1077,10 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
if(chr.isISOControl())
throw CharConversionException("no ${case}Petscii character for char #${chr.code}")
else
throw CharConversionException("no ${case}Petscii character for char #${chr.code} '${chr}'")
}
}
}
@@ -1119,7 +1123,10 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
if(chr.isISOControl())
throw CharConversionException("no ${case}Screencode character for char #${chr.code}")
else
throw CharConversionException("no ${case}Screencode character for char #${chr.code} '${chr}'")
}
}
}
@@ -53,6 +53,6 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
}
override fun allocateCx16VirtualRegisters() {
TODO("Not known if C128 can put the virtual regs in ZP")
TODO("Not known if PET can put the virtual regs in ZP")
}
}
+2 -2
View File
@@ -36,10 +36,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
@@ -2417,8 +2417,7 @@ $repeatLabel""")
cmp #<$number
bne $jumpIfFalseLabel
cpy #>$number
bne $jumpIfFalseLabel
""")
bne $jumpIfFalseLabel""")
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -2426,18 +2425,21 @@ $repeatLabel""")
cmp ${asmVariableName(right)}
bne $jumpIfFalseLabel
cpy ${asmVariableName(right)}+1
bne $jumpIfFalseLabel
""")
bne $jumpIfFalseLabel""")
}
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel
""")
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
// assignmentAsmGen.assignAddressOf(target, name, right.arrayIndexExpr)
} else {
out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel""")
}
}
else -> {
if(left.isSimple()) {
@@ -2518,12 +2520,16 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
}
}
else -> {
if(left.isSimple()) {
@@ -2849,8 +2855,12 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
val name = asmSymbolName(left.identifier)
code("#>$name", "#<$name")
return true
if(left.isFromArrayElement) {
TODO("address-of array element $name at ${left.position}")
} else {
code("#>$name", "#<$name")
return true
}
}
is PtIdentifier -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
@@ -2898,8 +2908,12 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
code("#>$name", "#<$name")
return true
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
code("#>$name", "#<$name")
return true
}
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -3031,7 +3045,11 @@ $repeatLabel""")
}
internal fun popFAC1() {
out(" jsr floats.popFAC1")
out(" clc | jsr floats.popFAC")
}
internal fun popFAC2() {
out(" sec | jsr floats.popFAC")
}
internal fun needAsaveForExpr(arg: PtExpression): Boolean =
@@ -43,6 +43,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods = optimizeUnneededTempvarInAdd(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
if(mods.isNotEmpty()) {
@@ -508,4 +515,27 @@ private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<Stri
optimize('y', lines)
}
return mods
}
}
private fun optimizeUnneededTempvarInAdd(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sequence: sta P8ZP_SCRATCH_XX / lda something / clc / adc P8ZP_SCRATCH_XX
// this can be performed without the scratch variable: clc / adc something
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
val fourth = lines[3].value.trimStart()
if(first.startsWith("sta P8ZP_SCRATCH_") && second.startsWith("lda") && third.startsWith("clc") && fourth.startsWith("adc P8ZP_SCRATCH_") ) {
if(fourth.substring(4)==first.substring(4)) {
mods.add(Modification(lines[0].index, false, " clc"))
mods.add(Modification(lines[1].index, false, " adc ${second.substring(3).trimStart()}"))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
}
return mods
}
@@ -41,6 +41,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
"ror2" -> funcRor2(fcall)
"setlsb" -> funcSetLsbMsb(fcall, false)
"setmsb" -> funcSetLsbMsb(fcall, true)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
@@ -181,7 +183,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.jsrfar
jsr cx16.JSRFAR
+ .word 0
+ .byte 0""")
// note that by convention the values in A+Y registers are now the return value of the call.
@@ -609,6 +611,85 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE)
}
private fun funcSetLsbMsb(fcall: PtBuiltinFunctionCall, msb: Boolean) {
val target: AsmAssignTarget
when(fcall.args[0]) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(fcall.args[0] as PtIdentifier) + if(msb) "+1" else ""
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, variableAsmName = varname)
}
is PtNumber -> {
val num = (fcall.args[0] as PtNumber).number + if(msb) 1 else 0
val mem = PtMemoryByte(fcall.position)
mem.add(PtNumber(DataType.UBYTE, num, fcall.position))
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
}
is PtAddressOf -> {
val mem = PtMemoryByte(fcall.position)
if(msb) {
val address = PtBinaryExpression("+", DataType.UWORD, fcall.args[0].position)
address.add(fcall.args[0])
address.add(PtNumber(address.type, 1.0, fcall.args[0].position))
mem.add(address)
} else {
mem.add(fcall.args[0])
}
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
}
is PtArrayIndexer -> {
val indexer = fcall.args[0] as PtArrayIndexer
require(!indexer.usesPointerVariable)
val elementSize: Int
val msbAdd: Int
if(indexer.splitWords) {
val arrayVariable = indexer.variable
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.ARRAY_UB, arrayVariable.position)
indexer.children[0].parent = indexer
elementSize = 1
msbAdd = 0
} else {
elementSize = 2
msbAdd = if(msb) 1 else 0
}
// double the index because of word array (if not split), add one if msb (if not split)
val constIndexNum = (indexer.index as? PtNumber)?.number
if(constIndexNum!=null) {
indexer.children[1] = PtNumber(indexer.index.type, constIndexNum*elementSize + msbAdd, indexer.position)
indexer.children[1].parent = indexer
} else {
val multipliedIndex: PtExpression
if(elementSize==1) {
multipliedIndex = indexer.index
} else {
multipliedIndex = PtBinaryExpression("<<", indexer.index.type, indexer.position)
multipliedIndex.add(indexer.index)
multipliedIndex.add(PtNumber(DataType.UBYTE, 1.0, indexer.position))
}
if(msbAdd>0) {
val msbIndex = PtBinaryExpression("+", indexer.index.type, indexer.position)
msbIndex.add(multipliedIndex)
msbIndex.add(PtNumber(DataType.UBYTE, msbAdd.toDouble(), indexer.position))
indexer.children[1] = msbIndex
msbIndex.parent = indexer
} else {
indexer.children[1] = multipliedIndex
multipliedIndex.parent=indexer
}
}
target = AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, array = indexer)
}
else -> throw AssemblyError("setlsb/setmsb on weird target ${fcall.args[0]}")
}
if(fcall.args[1].asConstInteger() == 0) {
assignAsmGen.assignConstantByte(target, 0)
} else {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false)
}
}
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
@@ -742,11 +823,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out("""
ldy #0
lda ($varname),y
pha
tax
iny
lda ($varname),y
tay
pla""")
txa""")
}
} else fallback()
}
@@ -759,11 +840,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
asmgen.out("""
lda ($varname),y
pha
tax
iny
lda ($varname),y
tay
pla""")
txa""")
} else fallback()
}
else -> fallback()
@@ -986,17 +1067,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya")
}
RegisterOrPair.X -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" pla")
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" pla")
}
else -> throw AssemblyError("invalid reg")
@@ -1032,35 +1113,35 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
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.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.X -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.Y -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tay | cpy #0")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" ldy #0 | cmp #0")
}
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" ldx #0 | cmp #0")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
asmgen.out(" ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args.single(), resultRegister)
asmgen.assignExpressionToRegister(arg, resultRegister)
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
zero.parent=fcall
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
@@ -255,11 +255,11 @@ $loopLabel""")
sec
sbc #<${stepsize.absoluteValue}
sta $varname
pha
tax
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
pla
txa
$modifiedLabel2 cmp #0 ; modified
lda $varname+1
$modifiedLabel sbc #0 ; modified
@@ -141,6 +141,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
is PtIdentifier -> {
val sourceName = asmgen.asmVariableName(value)
// note: cannot use X register here to store A because it might be used for other arguments
asmgen.out("""
pha
clc
@@ -1,7 +1,10 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.ast.PtBinaryExpression
import prog8.code.ast.PtExpression
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.AsmGen6502Internal
//
@@ -180,37 +183,25 @@ internal class AnyExprAsmGen(
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.operator) {
"+" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true)
if(!expr.right.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FADDT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"-" -> {
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true)
if(!expr.left.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true)
if(!expr.left.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.right, expr.left)
asmgen.out(" jsr floats.FSUBT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"*" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true)
if(!expr.right.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FMULTT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"/" -> {
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true)
if(!expr.left.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true)
if(!expr.left.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.right, expr.left)
asmgen.out(" jsr floats.FDIVT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
@@ -255,6 +246,31 @@ internal class AnyExprAsmGen(
}
}
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
when(asmgen.options.compTarget.name) {
C64Target.NAME -> {
// c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK) otherwise the result will be corrupt on C64
// this requires some more forced copying around of float values in certain cases
if (right.isSimple()) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
} else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
asmgen.pushFAC1()
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.popFAC2()
}
}
Cx16Target.NAME -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
if (!right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
if (!right.isSimple()) asmgen.popFAC1()
}
else -> TODO("don't know how to evaluate float expression for selected compilation target")
}
}
private fun setupFloatComparisonFAC1vsVarAY(expr: PtBinaryExpression) {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
@@ -49,7 +49,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
DataType.WORD -> assignVariableWord(assign.target, variable, assign.source.datatype)
DataType.UWORD -> {
if(assign.source.datatype in PassByReferenceDatatypes)
assignAddressOf(assign.target, variable)
assignAddressOf(assign.target, variable, null, null)
else
assignVariableWord(assign.target, variable, assign.source.datatype)
}
@@ -88,7 +88,7 @@ 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 | pha | lda ${arrayVarName}_msb,y | tay | pla")
asmgen.out(" lda ${arrayVarName}_lsb,y | tax | lda ${arrayVarName}_msb,y | tay | txa")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
return
@@ -122,7 +122,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
in WordDatatypes -> {
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
asmgen.out(" lda $arrayVarName,y | pha | lda $arrayVarName+1,y | tay | pla")
asmgen.out(" lda $arrayVarName,y | tax | lda $arrayVarName+1,y | tay | txa")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
@@ -181,7 +181,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
when(val value = assign.source.expression!!) {
is PtAddressOf -> {
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
val arrayDt = value.identifier.type
assignAddressOf(assign.target, sourceName, arrayDt, value.arrayIndexExpr)
}
is PtNumber -> throw AssemblyError("source kind should have been literalnumber")
is PtIdentifier -> throw AssemblyError("source kind should have been variable")
@@ -257,13 +258,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
tax
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
txa
jsr prog8_lib.strcpy""")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
@@ -306,14 +307,14 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sec
eor #255
adc #0
pha
tax
tya
eor #255
adc #0
tay
pla""")
txa""")
}
"~" -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
"~" -> asmgen.out(" tax | tya | eor #255 | tay | txa | eor #255")
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
@@ -416,7 +417,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val translatedOk = when (expr.operator) {
in ComparisonOperators -> optimizedComparison(expr, assign)
in setOf("&", "|", "^", "and", "or", "xor") -> optimizedLogicalOrBitwiseExpr(expr, assign.target)
"==", "!=" -> optimizedEqualityExpr(expr, assign.target)
"+", "-" -> optimizedPlusMinExpr(expr, assign.target)
"<<", ">>" -> optimizedBitshiftExpr(expr, assign.target)
"*" -> optimizedMultiplyExpr(expr, assign.target)
@@ -544,9 +544,28 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return true
}
in WordDatatypes -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r0")
asmgen.out(" jsr math.multiply_words")
assignRegisterpairWord(target, RegisterOrPair.AY)
if(expr.definingBlock()!!.veraFxMuls) {
// cx16 verafx hardware mul
if(expr.right.isSimple()) {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R0, expr.left.type in SignedDatatypes)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R1, expr.left.type in SignedDatatypes)
} else {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.left.type in SignedDatatypes)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R1, expr.left.type in SignedDatatypes)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out(" sta cx16.r0 | sty cx16.r0+1")
}
asmgen.out(" jsr verafx.muls")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
} else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "math.multiply_words.multiplier")
asmgen.out(" jsr math.multiply_words")
assignRegisterpairWord(target, RegisterOrPair.AY)
}
return true
}
else -> return false
@@ -568,8 +587,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" jsr math.mul_word_${value}")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r0")
asmgen.out(" jsr math.multiply_words")
if(expr.definingBlock()!!.veraFxMuls){
// cx16 verafx hardware mul
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r1")
asmgen.out("""
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls""")
} else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "math.multiply_words.multiplier")
asmgen.out(" jsr math.multiply_words")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
@@ -740,15 +768,43 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return true
}
else -> {
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
asmgen.out(" pha")
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", right.type)
asmgen.out(" pla")
if(expr.operator=="+")
asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
else
asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
val rightArrayIndexer = expr.right as? PtArrayIndexer
if(rightArrayIndexer!=null && rightArrayIndexer.type in ByteDatatypes && left.type in ByteDatatypes) {
// special optimization for bytevalue +/- bytearr[y] : no need to use a tempvar, just use adc array,y or sbc array,y or adc (ptr),y / sbc (ptr),y
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pha")
asmgen.assignExpressionToRegister(rightArrayIndexer.index, RegisterOrPair.Y, false)
if(!rightArrayIndexer.index.isSimple()) asmgen.out(" pla")
if(rightArrayIndexer.usesPointerVariable && !asmgen.isZpVar(rightArrayIndexer.variable)) {
asmgen.out("""
ldx ${rightArrayIndexer.variable.name}
stx P8ZP_SCRATCH_W1
ldx ${rightArrayIndexer.variable.name}+1
stx P8ZP_SCRATCH_W1+1""")
if (expr.operator == "+")
asmgen.out(" clc | adc (P8ZP_SCRATCH_W1),y")
else
asmgen.out(" sec | sbc (P8ZP_SCRATCH_W1),y")
} else {
val arrayvarname = if (rightArrayIndexer.usesPointerVariable)
"(${rightArrayIndexer.variable.name})"
else
asmgen.asmSymbolName(rightArrayIndexer.variable)
if (expr.operator == "+")
asmgen.out(" clc | adc $arrayvarname,y")
else
asmgen.out(" sec | sbc $arrayvarname,y")
}
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
} else {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", right.type)
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
if (expr.operator == "+")
asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
else
asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
}
return true
}
}
@@ -760,47 +816,51 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc P8ZP_SCRATCH_W1
pha
tax
tya
adc P8ZP_SCRATCH_W1+1
tay
pla""")
txa""")
else
asmgen.out("""
sec
sbc P8ZP_SCRATCH_W1
pha
tax
tya
sbc P8ZP_SCRATCH_W1+1
tay
pla""")
txa""")
assignRegisterpairWord(target, RegisterOrPair.AY)
}
when (right) {
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
val symbol = asmgen.asmVariableName(right.identifier)
if(expr.operator=="+")
asmgen.out("""
clc
adc #<$symbol
pha
tya
adc #>$symbol
tay
pla""")
else
asmgen.out("""
sec
sbc #<$symbol
pha
tya
sbc #>$symbol
tay
pla""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
if(right.isFromArrayElement) {
TODO("address-of array element $symbol at ${right.position}")
} else {
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
if(expr.operator=="+")
asmgen.out("""
clc
adc #<$symbol
tax
tya
adc #>$symbol
tay
txa""")
else
asmgen.out("""
sec
sbc #<$symbol
tax
tya
sbc #>$symbol
tay
txa""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
}
is PtIdentifier -> {
val symname = asmgen.asmVariableName(right)
@@ -809,20 +869,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc $symname
pha
tax
tya
adc $symname+1
tay
pla""")
txa""")
else
asmgen.out("""
sec
sbc $symname
pha
tax
tya
sbc $symname+1
tay
pla""")
txa""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
@@ -832,20 +892,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc #<${right.number.toHex()}
pha
tax
tya
adc #>${right.number.toHex()}
tay
pla""")
txa""")
} else if(expr.operator=="-") {
asmgen.out("""
sec
sbc #<${right.number.toHex()}
pha
tax
tya
sbc #>${right.number.toHex()}
tay
pla""")
txa""")
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
@@ -888,63 +948,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return false
}
private fun optimizedEqualityExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
// expression datatype is BOOL (ubyte) but operands can be anything
if(expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes &&
expr.left.isSimple() && expr.right.isSimple()) {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.restoreRegisterStack(CpuRegister.A, false)
if(expr.operator=="==") {
asmgen.out("""
cmp P8ZP_SCRATCH_B1
bne +
lda #1
bne ++
+ lda #0
+""")
} else {
asmgen.out("""
cmp P8ZP_SCRATCH_B1
beq +
lda #1
bne ++
+ lda #0
+""")
}
assignRegisterByte(target, CpuRegister.A, false)
return true
} else if(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes &&
expr.left.isSimple() && expr.right.isSimple()) {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
if(expr.operator=="==") {
asmgen.out("""
cmp P8ZP_SCRATCH_W1
bne +
cpy P8ZP_SCRATCH_W1+1
bne +
lda #1
bne ++
+ lda #0
+""")
} else {
asmgen.out("""
cmp P8ZP_SCRATCH_W1
bne +
cpy P8ZP_SCRATCH_W1+1
bne +
lda #0
beq ++
+ lda #1
+""")
}
assignRegisterByte(target, CpuRegister.A, false)
return true
}
return false
}
private fun optimizedLogicalOrBitwiseExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
if (expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) {
if (expr.right.isSimple()) {
@@ -984,9 +987,9 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
when (expr.operator) {
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_W1 | pha | tya | and P8ZP_SCRATCH_W1+1 | tay | pla")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_W1 | pha | tya | ora P8ZP_SCRATCH_W1+1 | tay | pla")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_W1 | pha | tya | eor P8ZP_SCRATCH_W1+1 | tay | pla")
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_W1 | tax | tya | and P8ZP_SCRATCH_W1+1 | tay | txa")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_W1 | tax | tya | ora P8ZP_SCRATCH_W1+1 | tay | txa")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_W1 | tax | tya | eor P8ZP_SCRATCH_W1+1 | tay | txa")
else -> throw AssemblyError("invalid operator")
}
assignRegisterpairWord(target, RegisterOrPair.AY)
@@ -1085,7 +1088,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
// checked OK:
private fun byteLess(expr: PtBinaryExpression, signed: Boolean) {
// note: this is the inverse of byteGreaterEqual
when(expr.right) {
@@ -1232,78 +1234,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun byteLessEqualsWRONG(expr: PtBinaryExpression, signed: Boolean) {
// note: this is the inverse of byteGreater
when(expr.right) {
is PtNumber -> {
// TODO verify if this is correct code on all corner cases
val number = (expr.right as PtNumber).number.toInt()
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, signed)
if(signed)
asmgen.out("""
sec
sbc #$number
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
else
asmgen.out("""
cmp #$number
lda #0
rol a
eor #1""")
}
is PtIdentifier -> {
// TODO verify if this is correct code on all corner cases
val varname = (expr.right as PtIdentifier).name
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, signed)
if(signed)
asmgen.out("""
sec
sbc $varname
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
else
asmgen.out("""
cmp $varname
lda #0
rol a
eor #1""")
}
else -> {
// TODO verify if this is correct code on all corner cases
// note: left and right operands get reversed here to reduce code size
asmgen.assignByteOperandsToAAndVar(expr.right, expr.left, "P8ZP_SCRATCH_B1")
if(signed)
asmgen.out("""
sec
sbc P8ZP_SCRATCH_B1
bvc +
eor #$80
+ bmi +
lda #1
bne ++
+ lda #0
+""")
else
asmgen.out("""
cmp P8ZP_SCRATCH_B1
lda #0
rol a""")
}
}
}
// checked OK:
private fun byteGreater(expr: PtBinaryExpression, signed: Boolean) {
// note: this is the inverse of byteLessEqual
when(expr.right) {
@@ -1381,7 +1311,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
// checked OK:
private fun byteGreaterEquals(expr: PtBinaryExpression, signed: Boolean) {
// note: this is the inverse of byteLess
when(expr.right) {
@@ -1621,18 +1550,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
is PtNumber -> {
val number = right.number.toHex()
when (operator) {
"&", "and" -> asmgen.out(" and #<$number | pha | tya | and #>$number | tay | pla")
"|", "or" -> asmgen.out(" ora #<$number | pha | tya | ora #>$number | tay | pla")
"^", "xor" -> asmgen.out(" eor #<$number | pha | tya | eor #>$number | tay | pla")
"&", "and" -> asmgen.out(" and #<$number | tax | tya | and #>$number | tay | txa")
"|", "or" -> asmgen.out(" ora #<$number | tax | tya | ora #>$number | tay | txa")
"^", "xor" -> asmgen.out(" eor #<$number | tax | tya | eor #>$number | tay | txa")
else -> throw AssemblyError("invalid operator")
}
}
is PtIdentifier -> {
val name = asmgen.asmSymbolName(right)
when (operator) {
"&", "and" -> asmgen.out(" and $name | pha | tya | and $name+1 | tay | pla")
"|", "or" -> asmgen.out(" ora $name | pha | tya | ora $name+1 | tay | pla")
"^", "xor" -> asmgen.out(" eor $name | pha | tya | eor $name+1 | tay | pla")
"&", "and" -> asmgen.out(" and $name | tax | tya | and $name+1 | tay | txa")
"|", "or" -> asmgen.out(" ora $name | tax | tya | ora $name+1 | tay | txa")
"^", "xor" -> asmgen.out(" eor $name | tax | tya | eor $name+1 | tay | txa")
else -> throw AssemblyError("invalid operator")
}
}
@@ -1649,10 +1578,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+""")
lda #1
+ eor #1""")
assignRegisterByte(assign.target, CpuRegister.A, false)
return true
}
@@ -1662,10 +1589,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
beq +
lda #0
beq ++
+ lda #1
+""")
lda #1
+ eor #1""")
assignRegisterByte(assign.target, CpuRegister.A, false)
return true
}
@@ -1727,7 +1652,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
// use subroutine
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.saveRegisterStack(CpuRegister.A, true)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), symbol.astNode.position,"P8ZP_SCRATCH_W1"), varname)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), symbol.astNode.position,"P8ZP_SCRATCH_W1"), varname, null, null)
asmgen.restoreRegisterStack(CpuRegister.A, false)
val stringVal = (variable as PtVariable).value as PtString
asmgen.out(" ldy #${stringVal.value.length}")
@@ -1740,7 +1665,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
DataType.ARRAY_B, DataType.ARRAY_UB -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
asmgen.saveRegisterStack(CpuRegister.A, true)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), symbol.astNode.position, "P8ZP_SCRATCH_W1"), varname)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), symbol.astNode.position, "P8ZP_SCRATCH_W1"), varname, null, null)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_bytearray")
@@ -1748,7 +1673,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), symbol.astNode.position, "P8ZP_SCRATCH_W2"), varname)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), symbol.astNode.position, "P8ZP_SCRATCH_W2"), varname, null, null)
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_wordarray")
return
@@ -1988,7 +1913,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return
}
// No more special optmized cases yet. Do the rest via more complex evaluation
// No more special optimized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
asmgen.assignExpressionTo(origTypeCastExpression, target)
@@ -2231,12 +2156,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(regs!=RegisterOrPair.AY)
throw AssemblyError("only supports AY here")
asmgen.out("""
pha
tax
lda #<$targetAsmVarName
sta P8ZP_SCRATCH_W2
lda #>$targetAsmVarName
sta P8ZP_SCRATCH_W2+1
pla
txa
jsr floats.cast_from_uw""")
}
else -> throw AssemblyError("weird type")
@@ -2259,12 +2184,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(regs!=RegisterOrPair.AY)
throw AssemblyError("only supports AY here")
asmgen.out("""
pha
tax
lda #<$targetAsmVarName
sta P8ZP_SCRATCH_W2
lda #>$targetAsmVarName
sta P8ZP_SCRATCH_W2+1
pla
txa
jsr floats.cast_from_w""")
}
else -> throw AssemblyError("weird type")
@@ -2275,12 +2200,24 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String) {
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
val offset = if(arrayIndexExpr!=null) {
val constIndex = arrayIndexExpr.asConstInteger()
if(constIndex!=null) {
if(arrayDt in SplitWordArrayTypes)
constIndex
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}")
}
} else 0
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
lda #<$sourceName+$offset
ldy #>$sourceName+$offset
sta ${target.asmVarname}
sty ${target.asmVarname}+1
""")
@@ -2289,20 +2226,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
throw AssemblyError("can't store word into memory byte")
}
TargetStorageKind.ARRAY -> {
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
asmgen.out(" lda #<$sourceName+$offset | ldy #>$sourceName+$offset")
assignRegisterpairWord(target, RegisterOrPair.AY)
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" ldx #>$sourceName | lda #<$sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
RegisterOrPair.AX -> asmgen.out(" ldx #>$sourceName+$offset | lda #<$sourceName+$offset")
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName+$offset | lda #<$sourceName+$offset")
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName+$offset | ldx #<$sourceName+$offset")
in Cx16VirtualRegisters -> {
asmgen.out(
"""
lda #<$sourceName
lda #<$sourceName+$offset
sta cx16.${target.register.toString().lowercase()}
lda #>$sourceName
lda #>$sourceName+$offset
sta cx16.${target.register.toString().lowercase()}+1
""")
}
@@ -2698,12 +2635,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
pla""")
RegisterOrPair.AY -> asmgen.out("""
lda $sourceName
pha
tax
ora #$7f
bmi +
lda #0
+ tay
pla""")
txa""")
RegisterOrPair.XY -> asmgen.out("""
lda $sourceName
tax
@@ -2825,68 +2762,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
TargetStorageKind.ARRAY -> {
if(assignAsWord)
TODO("assign register as word into Array not yet supported")
if(target.array!!.splitWords)
TODO("assign register into split words ${target.position}")
if(assignsIndexedPointerVar(target)) {
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy #${target.constArrayIndexValue} | sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy #${target.constArrayIndexValue}
sta (P8ZP_SCRATCH_W1),y""")
}
}
else {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
val indexVar = target.array.index as PtIdentifier
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy ${asmgen.asmVariableName(indexVar)}
sta (P8ZP_SCRATCH_W1),y""")
}
}
return
} else {
// assign regular array indexing
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
}
else {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
val indexVar = target.array.index as PtIdentifier
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
}
}
TODO("assign register byte as word into Array not yet supported")
assignRegisterByteToArray(target, register)
}
TargetStorageKind.REGISTER -> {
when(register) {
@@ -3034,6 +2911,79 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignRegisterByteToArray(target: AsmAssignTarget, register: CpuRegister) {
if(target.array!!.splitWords)
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
if(assignsIndexedPointerVar(target)) {
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy #${target.constArrayIndexValue} | sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy #${target.constArrayIndexValue}
sta (P8ZP_SCRATCH_W1),y""")
}
}
else {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
val indexVar = target.array.index as PtIdentifier
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy ${asmgen.asmVariableName(indexVar)}
sta (P8ZP_SCRATCH_W1),y""")
}
}
return
} else {
// assign regular array indexing
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
}
else {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
val indexVar = target.array.index as? PtIdentifier
if(indexVar!=null) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
} else {
require(target.array.index.type in ByteDatatypes)
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out(" sta ${target.asmVarname},y")
}
}
}
}
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes) {
"assign target must be word type ${target.position}"
@@ -3313,7 +3263,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
internal fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
if(byte==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this cpu
when(target.kind) {
@@ -3341,14 +3291,14 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return
}
if(target.array!!.splitWords)
TODO("assign into split words ${target.position}")
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
if (target.constArrayIndexValue!=null) {
val indexValue = target.constArrayIndexValue!!
asmgen.out(" stz ${target.asmVarname}+$indexValue")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.X, false)
asmgen.out(" stz ${target.asmVarname},x")
}
}
TargetStorageKind.REGISTER -> when(target.register!!) {
@@ -3414,17 +3364,11 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx #${byte.toHex()}")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(
" lda #${byte.toHex()} | sta cx16.${
target.register.toString().lowercase()
}")
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().lowercase()}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${target.register.toString().lowercase()}+1\n")
else
asmgen.out(
" lda #0 | sta cx16.${
target.register.toString().lowercase()
}+1\n")
asmgen.out(" lda #0 | sta cx16.${target.register.toString().lowercase()}+1\n")
}
else -> throw AssemblyError("weird register")
}
@@ -3697,9 +3641,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun storeRegisterAInMemoryAddress(memoryAddress: PtMemoryByte) {
internal fun storeRegisterAInMemoryAddress(memoryAddress: PtMemoryByte) {
val addressExpr = memoryAddress.address
val addressLv = addressExpr as? PtNumber
val addressOf = addressExpr as? PtAddressOf
fun storeViaExprEval() {
when(addressExpr) {
@@ -3721,10 +3666,29 @@ internal class AssignmentAsmGen(private val program: PtProgram,
addressLv != null -> {
asmgen.out(" sta ${addressLv.number.toHex()}")
}
addressOf != null -> {
if(addressOf.isFromArrayElement) {
TODO("address-of array element $addressOf")
} else {
asmgen.out(" sta ${asmgen.asmSymbolName(addressOf.identifier)}")
}
}
addressExpr is PtIdentifier -> {
asmgen.storeAIntoPointerVar(addressExpr)
}
addressExpr is PtBinaryExpression -> {
if(addressExpr.operator=="+" || addressExpr.operator=="-") {
val addrOf = addressExpr.left as? PtAddressOf
val offset = (addressExpr.right as? PtNumber)?.number?.toInt()
if(addrOf!=null && offset!=null) {
if(addrOf.isFromArrayElement) {
TODO("address-of array element $addrOf")
} else {
asmgen.out(" sta ${asmgen.asmSymbolName(addrOf.identifier)}${addressExpr.operator}${offset}")
return
}
}
}
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, addressExpr.operator, true))
storeViaExprEval()
}
@@ -3906,12 +3870,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sec
eor #255
adc #0
pha
tax
tya
eor #255
adc #0
tay
pla""")
txa""")
}
RegisterOrPair.XY -> {
asmgen.out("""
@@ -57,9 +57,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
// the asm-gen code can deal with situations where you want to assign a byte into a word.
// it will create the most optimized code to do this (so it type-extends for us).
// But we can't deal with writing a word into a byte - explicit typeconversion should be done
if(program.memsizer.memorySize(value.datatype) > program.memsizer.memorySize(target.datatype)) {
TODO("missing type cast: value type > target type ${target.position}")
}
if(program.memsizer.memorySize(value.datatype) > program.memsizer.memorySize(target.datatype))
throw AssemblyError("missing type cast: value type > target type ${target.position}")
fun regName(v: AsmAssignSource) = "cx16.${v.register!!.name.lowercase()}"
@@ -84,19 +83,20 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
in WordDatatypes -> {
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype)
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(target.asmVarname, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!, block)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression)
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression, block)
}
else {
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!)
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!, block)
}
}
}
@@ -231,18 +231,19 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
in WordDatatypes -> {
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype)
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!!)
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)
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression, block)
} else {
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!)
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!, block)
}
}
}
@@ -328,12 +329,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
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)
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -344,7 +346,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype)
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -355,7 +357,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype)
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -366,15 +368,15 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!)
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)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
else
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -385,8 +387,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
// 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
@@ -395,9 +398,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
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""") // copy from array into float temp var, clobbers A,Y
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)
@@ -414,18 +422,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
// copy tempvar back into array
asmgen.out("""
lda P8ZP_SCRATCH_W1
pha
lda P8ZP_SCRATCH_W1+1
pha
lda #<$tempvar
ldy #>$tempvar
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ply
pla
jsr floats.copy_float""") // copy from array into float temp var, clobbers A,Y
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}")
}
@@ -445,11 +452,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc $variable
pha
tay
txa
adc $variable+1
tax
pla""")
tya""")
true
} else {
asmgen.out("""
@@ -469,11 +476,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
sec
sbc $variable
pha
tay
txa
sbc $variable+1
tax
pla""")
tya""")
true
} else {
asmgen.out("""
@@ -508,11 +515,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc #<$number
pha
tay
txa
adc #>$number
tax
pla""")
tya""")
true
}
}
@@ -529,11 +536,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
sec
sbc #<$number
pha
tay
txa
sbc #>$number
tax
pla""")
tya""")
true
}
}
@@ -708,10 +715,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun inplacemodificationByteVariableWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
val tmpVar = if(name!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.assignExpressionToVariable(value, tmpVar, value.type)
asmgen.out(" lda $name")
inplacemodificationRegisterAwithVariable(operator, tmpVar, dt in SignedDatatypes)
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
asmgen.out(" sta $name")
}
@@ -722,15 +727,16 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun inplacemodificationRegisterAwithVariable(operator: String, variable: String, signed: Boolean) {
// A = A <operator> variable
when (operator) {
"+" -> asmgen.out(" clc | adc $variable")
"-" -> asmgen.out(" sec | sbc $variable")
"*" -> asmgen.out(" ldy $variable | jsr math.multiply_bytes")
"/" -> {
if(signed)
asmgen.out(" ldy $variable | jsr math.divmod_b_asm | tya")
asmgen.out(" ldy $variable | jsr math.divmod_b_asm | tya")
else
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm | tya")
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm | tya")
}
"%" -> {
if(signed)
@@ -792,11 +798,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<" -> {
if(!signed) {
asmgen.out("""
tay
cmp $variable
bcc +
lda #0
cpy $variable
rol a
eor #1""")
beq ++
+ lda #1
+""")
}
else {
// see http://www.6502.org/tutorials/compare_beyond.html
@@ -815,10 +822,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<=" -> {
if(!signed) {
asmgen.out("""
tay
cmp $variable
bcc +
beq +
lda #0
ldy $variable
rol a""")
beq ++
+ lda #1
+""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
@@ -881,6 +891,164 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplacemodificationRegisterAwithVariableWithSwappedOperands(operator: String, variable: String, signed: Boolean) {
// A = variable <operator> A
if(operator in AssociativeOperators)
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for associative operators
// now implement the non-assiciative operators...
when (operator) {
"-" -> {
// A = variable - A
val tmpVar = if(variable!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.out(" sta $tmpVar | lda $variable | sec | sbc $tmpVar")
}
"/" -> {
if(signed)
asmgen.out(" tay | lda $variable | jsr math.divmod_b_asm | tya")
else
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm | tya")
}
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm")
}
"<<" -> {
asmgen.out("""
tay
beq +
lda $variable
- asl a
dey
bne -
+""")
}
">>" -> {
if(!signed) {
asmgen.out("""
tay
beq +
lda $variable
- lsr a
dey
bne -
+""")
} else {
asmgen.out("""
tay
beq +
lda $variable
sta P8ZP_SCRATCH_B1
- asl a
ror P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_B1
dey
bne -
+""")
}
}
"<" -> {
// variable<A --> A>variable?
if(!signed) {
asmgen.out("""
tay
lda #0
cpy $variable
beq +
rol a
+""")
}
else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
clc
sbc $variable
bvc +
eor #$80
+ bpl +
lda #0
beq ++
+ lda #1
+""")
}
}
"<=" -> {
// variable<=A --> A>=variable?
if(!signed) {
asmgen.out("""
tay
lda #0
cpy $variable
rol a""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
sec
sbc $variable
bvc +
eor #$80
+ bpl +
lda #0
beq ++
+ lda #1
+""")
}
}
">" -> {
// variable>A --> A<variable?
if(!signed) {
asmgen.out("""
cmp $variable
bcc +
lda #0
beq ++
+ lda #1
+""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
sec
sbc $variable
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
}
}
">=" -> {
// variable>=A --> A<=variable?
if(!signed) {
asmgen.out("""
cmp $variable
bcc +
beq +
lda #0
beq ++
+ lda #1
+""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
clc
sbc $variable
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
}
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
private fun inplacemodificationByteVariableWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
when (operator) {
@@ -1080,6 +1248,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
+""")
}
"-" -> {
// name -= @(memory)
asmgen.translateDirectMemReadExpressionToRegA(memread)
val tmpByte = if(name!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.out("""
@@ -1088,7 +1257,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
sec
sbc $tmpByte
sta $name
bcc +
bcs +
dec $name+1
+""")
}
@@ -1111,12 +1280,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" eor $name | sta $name")
}
else -> {
inplacemodificationWordWithValue(name, dt, operator, memread)
inplacemodificationWordWithValue(name, dt, operator, memread, memread.definingBlock())
}
}
}
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int, block: PtBlock?) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
when (operator) {
"+" -> {
@@ -1176,16 +1345,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
asmgen.out("""
lda $name
sta cx16.r0
lda $name+1
sta cx16.r0+1
lda #<$value
ldy #>$value
jsr math.multiply_words
sta $name
sty $name+1""")
if(block?.veraFxMuls==true)
// cx16 verafx hardware mul
asmgen.out("""
lda $name
ldy $name+1
sta cx16.r0
sty cx16.r0+1
lda #<$value
ldy #>$value
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
sta $name
sty $name+1""")
else
asmgen.out("""
lda $name
sta math.multiply_words.multiplier
lda $name+1
sta math.multiply_words.multiplier+1
lda #<$value
ldy #>$value
jsr math.multiply_words
sta $name
sty $name+1""")
}
}
"/" -> {
@@ -1575,7 +1759,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType) {
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType, block: PtBlock?) {
when (valueDt) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that
@@ -1628,23 +1812,46 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
sta $name+1""")
}
"*" -> {
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta cx16.r0")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.r0+1")
else
asmgen.out(" lda #0 | sta cx16.r0+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta cx16.r0 | sty cx16.r0+1")
}
asmgen.out("""
if(block?.veraFxMuls==true) {
// cx16 verafx hardware muls
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta cx16.r1")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.r1+1")
else
asmgen.out(" lda #0 | sta cx16.r1+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta cx16.r1 | sty cx16.r1+1")
}
asmgen.out("""
lda $name
ldy $name+1
jsr math.multiply_words
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls
sta $name
sty $name+1""")
} else {
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta math.multiply_words.multiplier")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz math.multiply_words.multiplier+1")
else
asmgen.out(" lda #0 | sta math.multiply_words.multiplier+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta math.multiply_words.multiplier | sty math.multiply_words.multiplier+1")
}
asmgen.out("""
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1""")
}
}
"/" -> {
if(dt==DataType.UWORD) {
@@ -1773,26 +1980,26 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
"-" -> asmgen.out(" lda $name | sec | sbc $otherName | sta $name | lda $name+1 | sbc $otherName+1 | sta $name+1")
"*" -> {
if(otherName=="cx16.r0")
if(block?.veraFxMuls==true)
// cx16 verafx hardware muls
asmgen.out("""
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1""")
else if(name=="cx16.r0")
asmgen.out("""
sta cx16.r0
sty cx16.r0+1
lda $otherName
ldy $otherName+1
jsr math.multiply_words
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
sta $name
sty $name+1""")
else
asmgen.out("""
lda $otherName
ldy $otherName+1
sta cx16.r0
sty cx16.r0+1
sta math.multiply_words.multiplier
sty math.multiply_words.multiplier+1
lda $name
ldy $name+1
jsr math.multiply_words
@@ -1975,27 +2182,41 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
fun multiplyVarByWordInAY() {
asmgen.out("""
sta cx16.r0
sty cx16.r0+1
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1
""")
if(block?.veraFxMuls==true)
// cx16 verafx hardware muls
asmgen.out("""
sta cx16.r1
sty cx16.r1+1
lda $name
ldy $name+1
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls
sta $name
sty $name+1
""")
else
asmgen.out("""
sta math.multiply_words.multiplier
sty math.multiply_words.multiplier+1
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1
""")
}
fun divideVarByWordInAY() {
asmgen.out("""
pha
tax
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
pla""")
txa""")
if (dt == DataType.WORD)
asmgen.out(" jsr math.divmod_w_asm")
else
@@ -2007,12 +2228,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(dt==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out("""
pha
tax
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
pla
txa
jsr math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
@@ -2026,20 +2247,21 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
// the other variable is a BYTE type so optimize for that
when (operator) {
"+" -> {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", valueDt)
if(valueDt==DataType.UBYTE)
// name += byteexpression
if(valueDt==DataType.UBYTE) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, false)
asmgen.out("""
lda $name
clc
adc P8ZP_SCRATCH_B1
adc $name
sta $name
bcc +
inc $name+1
+""")
else
} else {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.out("""
ldy #0
lda P8ZP_SCRATCH_B1
cmp #0
bpl +
dey ; sign extend
+ clc
@@ -2048,8 +2270,10 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
tya
adc $name+1
sta $name+1""")
}
}
"-" -> {
// name -= byteexpression
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", valueDt)
if(valueDt==DataType.UBYTE)
asmgen.out("""
+2 -2
View File
@@ -44,7 +44,7 @@ class TestCodegen: FunSpec({
//}
val codegen = AsmGen6502(prefixSymbols = false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -94,7 +94,7 @@ class TestCodegen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
+2 -2
View File
@@ -36,10 +36,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
+2 -2
View File
@@ -36,10 +36,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
@@ -40,6 +40,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(Opcode.ROXL, call)
"ror" -> funcRolRor(Opcode.ROXR, call)
"rol2" -> funcRolRor(Opcode.ROL, call)
@@ -69,6 +71,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentwordTr = exprGen.translateExpression(call.args[2])
@@ -110,6 +113,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
addToResult(result, left, left.resultReg, -1)
@@ -144,6 +148,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
else -> throw IllegalArgumentException("weird type")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
@@ -165,6 +170,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
else -> throw IllegalArgumentException("weird type")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
@@ -308,6 +314,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
else -> throw IllegalArgumentException("weird type to reverse")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
@@ -331,6 +338,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
else -> throw IllegalArgumentException("weird type to sort")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
@@ -354,6 +362,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val type = irType(call.type)
val valueTr = exprGen.translateExpression(call.args[0])
val minimumTr = exprGen.translateExpression(call.args[1])
@@ -549,6 +558,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return exprGen.translateExpression(call.args.single())
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
// TODO to be more strict, maybe we *should* introduce a new result register that is of type .b?
}
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -575,6 +585,122 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, vmDt, -1, -1)
}
private fun funcSetLsbMsb(call: PtBuiltinFunctionCall, msb: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val target = call.args[0]
val isConstZeroValue = call.args[1].asConstInteger()==0
when(target) {
is PtIdentifier -> {
if(isConstZeroValue) {
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
}
}
}
is PtArrayIndexer -> {
require(!target.usesPointerVariable)
if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
}
}
}
}
else {
val eltSize = codeGen.program.memsizer.memorySize(target.type)
val constIndex = target.index.asConstInteger()
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
}
}
}
}
}
else -> throw AssemblyError("weird target for setlsb/setmsb: $target")
}
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
}
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
@@ -3,10 +3,7 @@ package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PassByValueDatatypes
import prog8.code.core.SignedDatatypes
import prog8.code.core.*
import prog8.intermediate.*
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
@@ -69,10 +66,28 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val vmDt = irType(expr.type)
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val code = IRCodeChunk(null, null)
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
ExpressionCodeResult(code, vmDt, resultRegister, -1)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol), null)
if(expr.isFromArrayElement) {
val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1)
if(expr.identifier.type in SplitWordArrayTypes) {
result += IRCodeChunk(null, null).also {
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
}
} else {
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
result += IRCodeChunk(null, null).also {
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
if(eltSize>1)
it += IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=indexTr.resultReg, immediate = eltSize)
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
}
}
}
ExpressionCodeResult(result, vmDt, resultRegister, -1)
}
is PtMemoryByte -> {
val result = mutableListOf<IRCodeChunkBase>()
@@ -107,6 +122,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
when(check.iterable.type) {
DataType.STR -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
@@ -115,6 +131,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
@@ -126,6 +143,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
@@ -366,6 +384,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -386,8 +405,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
}
// create the call
val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
addInstr(result, call, null)
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
@@ -397,6 +415,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is StRomSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -543,14 +562,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val resultRegister = codeGen.registers.nextFree()
val valueReg = codeGen.registers.nextFree()
val label = codeGen.createLabelName()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1), null)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
if (notEquals) {
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
} else {
addInstr(result, IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0)
it += if (notEquals)
IRInstruction(Opcode.BSTNE, labelSymbol = label)
else
IRInstruction(Opcode.BSTEQ, labelSymbol = label)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
result += IRCodeChunk(label, null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
@@ -415,13 +415,19 @@ class IRCodeGen(
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = endLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
}
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = choiceLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
}
}
}
}
@@ -464,7 +470,8 @@ class IRCodeGen(
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1 = tmpReg, immediate = 0, labelSymbol = endLabel)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = tmpReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
@@ -492,7 +499,8 @@ class IRCodeGen(
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
it += IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength, labelSymbol = loopLabel)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else -> {
@@ -507,7 +515,10 @@ class IRCodeGen(
}
result += translateNode(forLoop.statements)
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes, labelSymbol = loopLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
}
}
@@ -555,8 +566,11 @@ class IRCodeGen(
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol), null)
addInstr(result, IRInstruction(Opcode.BEQR, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol = labelAfterFor), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
@@ -601,8 +615,8 @@ class IRCodeGen(
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.XOR, loopvarDtIr, reg1 = 999, immediate = 111)
chunk2 += IRInstruction(Opcode.BNE, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped, labelSymbol = loopLabel)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
result += chunk2
return result
}
@@ -961,21 +975,33 @@ class IRCodeGen(
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
)
val gotoOpcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
"<=" -> Opcode.BLES
">=" -> Opcode.BGES
else -> throw AssemblyError("weird operator")
when(condition.operator) {
// TODO: the converted list of operators
"==" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTEQ)
}
"!=" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTNE)
}
else -> {
// TODO: the old list of operators, still to be converted
val gotoOpcode = when (condition.operator) {
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
"<=" -> Opcode.BLES
">=" -> Opcode.BGES
else -> throw AssemblyError("weird operator")
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.generatedLabel)
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
}
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.generatedLabel)
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
}
return result
} else {
@@ -990,6 +1016,14 @@ class IRCodeGen(
}
}
// TODO use this everywhere
private fun branchInstr(goto: PtJump, branchOpcode: Opcode) = if (goto.address != null)
IRInstruction(branchOpcode, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(branchOpcode, labelSymbol = goto.generatedLabel)
else
IRInstruction(branchOpcode, labelSymbol = goto.identifier!!.name)
private fun ifZeroIntThenJump(
result: MutableList<IRCodeChunkBase>,
ifElse: PtIfElse,
@@ -1000,21 +1034,33 @@ class IRCodeGen(
val condition = ifElse.condition as PtBinaryExpression
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val opcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
">" -> if (signed) Opcode.BGTS else Opcode.BGT
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
">=" -> if (signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
when(condition.operator) {
// TODO: converted list of operators
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
// TODO: to-be converted operators
val opcode = when (condition.operator) {
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
">" -> if (signed) Opcode.BGTS else Opcode.BGT
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
">=" -> if (signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
}
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
}
private fun ifNonZeroIntThenJump(
@@ -1028,47 +1074,60 @@ class IRCodeGen(
if(condition==null) {
val tr = expressionEval.translateExpression(ifElse.condition)
result += tr.chunks
if (goto.address != null)
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTNE)
}
} else {
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val opcode: Opcode
val number = (condition.right as? PtNumber)?.number?.toInt()
if(number!=null) {
val firstReg = leftTr.resultReg
opcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
">=" -> if(signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
when(condition.operator) {
// TODO: the converted operators
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
// TODO: to-be converted operators
val opcode = when (condition.operator) {
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
">=" -> if(signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
}
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
} else {
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
val firstReg: Int
val secondReg: Int
val opcode: Opcode
var useCmp = false
when (condition.operator) {
"==" -> {
opcode = Opcode.BEQR
useCmp = true
opcode = Opcode.BSTEQ
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"!=" -> {
opcode = Opcode.BNER
useCmp = true
opcode = Opcode.BSTNE
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
@@ -1096,12 +1155,20 @@ class IRCodeGen(
}
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
if(useCmp) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, irDtLeft, reg1 = firstReg, reg2 = secondReg)
it += branchInstr(goto, opcode)
}
} else {
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
}
}
}
}
@@ -1109,6 +1176,7 @@ class IRCodeGen(
private fun translateIfElseZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
val compResultReg: Int
val branchDt: IRDataType
val condition = ifElse.condition as PtBinaryExpression
@@ -1122,13 +1190,19 @@ class IRCodeGen(
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0f)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightFpReg)
}
elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> Opcode.BGES
">" -> Opcode.BLES
"<=" -> Opcode.BGTS
">=" -> Opcode.BLTS
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = Opcode.BGES
">" -> elseBranch = Opcode.BLES
"<=" -> elseBranch = Opcode.BGTS
">=" -> elseBranch = Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
} else {
@@ -1137,13 +1211,19 @@ class IRCodeGen(
val tr = expressionEval.translateExpression(condition.left)
compResultReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if (signed) Opcode.BGES else Opcode.BGE
">" -> if (signed) Opcode.BLES else Opcode.BLE
"<=" -> if (signed) Opcode.BGTS else Opcode.BGT
">=" -> if (signed) Opcode.BLTS else Opcode.BLT
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = if (signed) Opcode.BGES else Opcode.BGE
">" -> elseBranch = if (signed) Opcode.BLES else Opcode.BLE
"<=" -> elseBranch = if (signed) Opcode.BGTS else Opcode.BGT
">=" -> elseBranch = if (signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("weird operator")
}
}
@@ -1151,14 +1231,26 @@ class IRCodeGen(
if(ifElse.elseScope.children.isEmpty()) {
// just if
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
} else {
// if and else
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1169,31 +1261,39 @@ class IRCodeGen(
private fun translateIfElseNonZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseBranchOpcode: Opcode
val elseBranchFirstReg: Int
val elseBranchSecondReg: Int
val branchDt: IRDataType
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null) {
if(irDtLeft==IRDataType.FLOAT)
throw AssemblyError("condition value should not be float")
val tr = expressionEval.translateExpression(ifElse.condition)
result += tr.chunks
if(ifElse.elseScope.children.isNotEmpty()) {
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
result += IRCodeChunk(afterIfLabel, null)
} else {
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
return result
throw AssemblyError("if-else condition is not a binaryexpression, should have been converted?")
// if(irDtLeft==IRDataType.FLOAT)
// throw AssemblyError("condition value should not be float")
// val tr = expressionEval.translateExpression(ifElse.condition)
// result += tr.chunks
// if(ifElse.elseScope.children.isNotEmpty()) {
// val elseLabel = createLabelName()
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
// TODO("test this")
// }
// result += translateNode(ifElse.ifScope)
// val afterIfLabel = createLabelName()
// addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
// result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
// result += IRCodeChunk(afterIfLabel, null)
// } else {
// val afterIfLabel = createLabelName()
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel)
// TODO("test this")
// }
// result += translateNode(ifElse.ifScope)
// result += IRCodeChunk(afterIfLabel, null)
// }
// return result
} else {
if (irDtLeft == IRDataType.FLOAT) {
val leftTr = expressionEval.translateExpression(condition.left)
@@ -1201,35 +1301,35 @@ class IRCodeGen(
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val compResultReg = registers.nextFree()
addInstr(
result,
IRInstruction(
Opcode.FCOMP,
IRDataType.FLOAT,
reg1 = compResultReg,
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
),
null
)
val elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> Opcode.BGES
">" -> Opcode.BLES
"<=" -> Opcode.BGTS
">=" -> Opcode.BLTS
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = Opcode.BGES
">" -> elseBranch = Opcode.BLES
"<=" -> elseBranch = Opcode.BGTS
">=" -> elseBranch = Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
if (ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result,
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel),
null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1237,11 +1337,13 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result,
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel),
null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
@@ -1252,26 +1354,34 @@ class IRCodeGen(
addToResult(result, leftTr, leftTr.resultReg, -1)
val number = (condition.right as? PtNumber)?.number?.toInt()
if (number!=null) {
elseBranchOpcode = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if(signed) Opcode.BGES else Opcode.BGE
">" -> if(signed) Opcode.BLES else Opcode.BLE
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = if(signed) Opcode.BGES else Opcode.BGE
">" -> elseBranch = if(signed) Opcode.BLES else Opcode.BLE
"<=" -> elseBranch = if(signed) Opcode.BGTS else Opcode.BGT
">=" -> elseBranch = if(signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("invalid comparison operator")
}
if (ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = leftTr.resultReg, immediate = number,
labelSymbol = elseLabel
), null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1279,52 +1389,56 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = leftTr.resultReg, immediate = number,
labelSymbol = afterIfLabel
), null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
} else {
val rightTr = expressionEval.translateExpression(condition.right)
val elseBranch: Opcode
var useCmp = false
addToResult(result, rightTr, rightTr.resultReg, -1)
when (condition.operator) {
"==" -> {
elseBranchOpcode = Opcode.BNER
useCmp = true
elseBranch = Opcode.BSTNE
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"!=" -> {
elseBranchOpcode = Opcode.BEQR
useCmp = true
elseBranch = Opcode.BSTEQ
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"<" -> {
// else part when left >= right
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">" -> {
// else part when left <= right --> right >= left
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
"<=" -> {
// else part when left > right
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">=" -> {
// else part when left < right --> right > left
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
@@ -1334,13 +1448,14 @@ class IRCodeGen(
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
labelSymbol = elseLabel
), null
)
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = elseLabel), null)
}
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1348,13 +1463,14 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
labelSymbol = afterIfLabel
), null
)
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = afterIfLabel), null)
}
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
@@ -1525,12 +1641,15 @@ class IRCodeGen(
addToResult(result, countTr, countTr.resultReg, -1)
if(constIntValue(repeat.count)==null) {
// check if the counter is already zero
addInstr(result, IRInstruction(Opcode.BEQ, irDt, reg1=countTr.resultReg, immediate = 0, labelSymbol = skipRepeatLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDt, reg1=countTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel)
}
}
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg)
it += IRInstruction(Opcode.BNE, irDt, reg1 = countTr.resultReg, immediate = 0, labelSymbol = repeatLabel)
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
}
result += IRCodeChunk(skipRepeatLabel, null)
return result
@@ -25,18 +25,29 @@ class IRUnusedCodeRemover(
}
private fun pruneSymboltable(blockLabel: String) {
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
val prefix = "$blockLabel."
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
blockVars.forEach { stVar ->
irprog. allSubs().flatMap { it.chunks }.forEach { chunk ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
if(ins.labelSymbol == stVar.name) {
return
return // symbol occurs in an instruction
}
}
}
irprog.st.allVariables().forEach { stVar->
val initValue = stVar.onetimeInitializationArrayValue
if(initValue!=null && !initValue.isEmpty()) {
if(initValue.any {
it.addressOfSymbol?.startsWith(blockLabel)==true
})
return // symbol occurs in an initializer value (address-of this symbol)_
}
}
}
irprog.st.removeTree(blockLabel)
}
@@ -207,9 +218,7 @@ class IRUnusedCodeRemover(
return removeUnlinkedChunks(linkedChunks)
}
private fun removeUnlinkedChunks(
linkedChunks: Set<IRCodeChunkBase>
): Int {
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
var numRemoved = 0
irprog.foreachSub { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
+14 -10
View File
@@ -41,7 +41,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -91,7 +91,7 @@ class TestVmCodeGen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
@@ -120,7 +120,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -183,7 +183,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -242,7 +242,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -289,7 +289,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -352,7 +352,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -411,7 +411,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -451,7 +451,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(romsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
@@ -466,7 +466,11 @@ class TestVmCodeGen: FunSpec({
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
val callInstr = irChunks.single().instructions.single()
irChunks[0].instructions.size shouldBe 2
val preparecallInstr = irChunks[0].instructions[0]
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
preparecallInstr.immediate shouldBe 0
val callInstr = irChunks[0].instructions[1]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
}
+2 -2
View File
@@ -33,10 +33,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
@@ -19,12 +19,13 @@ import kotlin.math.floor
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
// @( &thing ) --> thing (but only if thing is a byte type!)
val addrOf = memread.addressExpression as? AddressOf
return if(addrOf!=null)
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
else
noModifications
if(addrOf!=null) {
if(addrOf.identifier.inferType(program).isBytes)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
}
return noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
@@ -628,5 +629,4 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return null
}
}
}
@@ -276,107 +276,10 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
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
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
in ArrayDatatypes -> {
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
if(replacedArrayInitializer!=null)
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
}
else -> {
// nothing to do for this type
@@ -386,4 +289,109 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
// 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
if(rangeExpr!=null) {
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return null
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
}
}
else -> return null
}
return null
}
}
@@ -30,7 +30,7 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
arg.identifier
if(arg.arrayIndex==null) arg.identifier else null
} else {
arg as? IdentifierReference
}
@@ -384,7 +384,8 @@ class StatementOptimizer(private val program: Program,
}
override fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> {
return if(unrollLoop.iterations<1)
val iterations = unrollLoop.iterations.constValue(program)?.number?.toInt()
return if(iterations!=null && iterations<1)
listOf(IAstModification.Remove(unrollLoop, parent as IStatementContainer))
else
noModifications
+4 -4
View File
@@ -33,10 +33,10 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation "org.antlr:antlr4-runtime:4.12.0"
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation project(':intermediate')
@@ -59,10 +59,10 @@ configurations {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
-62
View File
@@ -1,62 +0,0 @@
%import syslib
%import textio
; Bitmap pixel graphics module for the Commodore 128
; TODO c128 actually implement the graphics routines. Ideally a way to 'borrow' the code form the C64 version without just copy-pasting that here?
graphics {
%option no_symbol_prefixing
const uword WIDTH = 320
const ubyte HEIGHT = 200
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
; TODO
}
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
; TODO
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
; TODO
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; TODO
}
sub fillrect(uword x, uword y, uword width, uword height) {
; TODO
}
sub rect(uword x, uword y, uword width, uword height) {
; TODO
}
sub horizontal_line(uword x, uword y, uword length) {
; TODO
}
sub vertical_line(uword x, uword y, uword height) {
; TODO
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; TODO
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; TODO
}
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
%asm {{
nop ; TODO
}}
}
}
+7 -3
View File
@@ -418,8 +418,9 @@ pushFAC1 .proc
rts
.pend
popFAC1 .proc
; -- pop floating point value from cpu stack into FAC1
popFAC .proc
; -- pop floating point value from cpu stack into FAC1 or FAC2 (
; carry flag clear=FAC1, carry set=FAC2
; save return address
pla
sta P8ZP_SCRATCH_W2
@@ -437,8 +438,11 @@ popFAC1 .proc
sta floats.floats_temp_var
lda #<floats.floats_temp_var
ldy #>floats.floats_temp_var
bcs +
jsr floats.MOVFM
; re-push return address
jmp ++
+ jsr floats.CONUPK
+ ; re-push return address
lda P8ZP_SCRATCH_W2+1
pha
lda P8ZP_SCRATCH_W2
+22 -14
View File
@@ -1,35 +1,43 @@
%import textio
; bitmap pixel graphics module for the C64
; only black/white monochrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
;
; 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.
graphics {
%option no_symbol_prefixing
const uword BITMAP_ADDRESS = $2000
const uword WIDTH = 320
const ubyte HEIGHT = 200
const uword BITMAP_ADDRESS = $6000 ; MUST BE IN REGULAR RAM if you are not messing with ROM/RAM banking. (and $2000-aligned)
; note: this constant is also used in a asm multiplication table below!
const uword CHARS_ADDRESS = $5c00 ; must be in same vic memory bank as the bitmap.
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
c64.SCROLY = %00111011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
clear_screen(1, 0)
c64.SCROLY = %00111011 ; enable bitmap graphics mode
c64.SCROLX = %00001000 ; 40 column mode, no scrolling, multicolor bitmap off
c64.VMCSB = (lsb(CHARS_ADDRESS >> 6) & $F0) | (lsb((BITMAP_ADDRESS & $3fff) / $0800) << 1) ; set bitmap address
c64.CIA2DDRA |= %11
c64.CIA2PRA = lsb(BITMAP_ADDRESS >> 14) ^ 3 ; set VIC bank.
}
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
c64.SCROLY = %00011011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00000100 ; $1000-$2fff
txt.fill_screen(' ', 1)
; enables erase the text screen, text mode
sys.memset(CHARS_ADDRESS, 40*25, sc:' ')
c64.SCROLY = %00011011 ; disable bitmap graphics mode
c64.SCROLX = %00001000 ; 40 column mode, no scrolling
c64.VMCSB = %00010100 ; screen addresses back to defaults
c64.CIA2DDRA |= %11
c64.CIA2PRA = %11 ; back to VIC bank 0.
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
sys.memset(CHARS_ADDRESS, 40*25, pixelcolor << 4 | bgcolor)
sys.memset(cbm.Colors, 40*25, 255)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
@@ -352,7 +360,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
_plot_y_values := BITMAP_ADDRESS + 320*(range(200)>>3) + (range(200) & 7)
_y_lookup_lo .byte <_plot_y_values
_y_lookup_hi .byte >_plot_y_values
+35
View File
@@ -136,6 +136,8 @@ c64 {
%option no_symbol_prefixing
; the default locations of the 8 sprite pointers (store address of sprite / 64)
; (depending on the VIC bank and screen ram address selection these can be shifted around though,
; see the two routines after this for a dynamic way of determining the correct memory location)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
@@ -284,6 +286,39 @@ c64 {
; ---- end of SID registers ----
sub get_vic_memory_base() -> uword {
; one of the 4 possible banks. $0000/$4000/$8000/$c000.
c64.CIA2DDRA |= %11
return ((c64.CIA2PRA & 3) ^ 3) as uword << 14
}
sub get_char_matrix_ptr() -> uword {
; Usually the character screen matrix is at 1024-2039 (see above)
; However the vic memory configuration can be altered and this moves these registers with it.
; So this routine determines it dynamically from the VIC memory setup.
uword chars_matrix_offset = (c64.VMCSB & $f0) as uword << 6
return get_vic_memory_base() + chars_matrix_offset
}
sub get_bitmap_ptr() -> uword {
return get_vic_memory_base() + ((c64.VMCSB & %00001000) as uword << 10)
}
sub get_sprite_addr_ptrs() -> uword {
; Usually the sprite address pointers are at addresses 2040-2047 (see above)
; However the vic memory configuration can be altered and this moves these registers with it.
; So this routine determines it dynamically from the VIC memory setup.
return get_char_matrix_ptr() + 1016
}
sub set_sprite_ptr(ubyte sprite_num, uword sprite_data_address) {
; Sets the sprite data pointer to the given address.
; Because it takes some time to calculate things based on the vic memory setup,
; its only suitable if you're not continuously changing the data address.
; Otherwise store the correct sprite data pointer location somewhere yourself and reuse it.
@(get_sprite_addr_ptrs() + sprite_num) = lsb(sprite_data_address / 64)
}
}
sys {
+48 -37
View File
@@ -8,22 +8,33 @@
diskio {
%option no_symbol_prefixing
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
ubyte drivenumber = 8
sub set_drive(ubyte number) {
drivenumber = number
}
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
}
sub reset_write_channel() {
cbm.CHKOUT(WRITE_IO_CHANNEL)
}
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -61,7 +72,7 @@ diskio {
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
@@ -76,12 +87,12 @@ io_error:
sub diskname() -> uword {
; returns disk label name or 0 if error
cbm.SETNAM(3, "$")
cbm.SETLFS(12, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -106,7 +117,7 @@ io_error:
io_error:
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
return 0
return diskio.list_filename
@@ -150,7 +161,7 @@ io_error:
return files_found
}
; ----- iterative file lister functions (uses io channel 12) -----
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
@@ -161,11 +172,11 @@ io_error:
iteration_in_progress = true
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -190,7 +201,7 @@ io_error:
return false
repeat {
void cbm.CHKIN(12) ; use #12 as input channel again
reset_read_channel() ; use the input io channel again
uword nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
@@ -251,13 +262,13 @@ close_end:
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file loader functions (uses io channel 12) -----
; ----- iterative file loader functions (uses the read io channel) -----
sub f_open(uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read
@@ -265,18 +276,18 @@ close_end:
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
void cbm.OPEN() ; open 12,8,12,"filename"
if_cc {
if cbm.READST()==0 {
iteration_in_progress = true
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cc {
void cbm.CHRIN() ; read first byte to test for file not found
if not cbm.READST() {
cbm.CLOSE(12) ; close file because we already consumed first byte
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
void cbm.OPEN() ; re-open the file
void cbm.CHKIN(12)
cbm.CLRCHN() ; reset default i/o channels
return true
}
}
@@ -290,21 +301,22 @@ close_end:
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress or not num_bytes
return 0
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
; 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)
if_cs
goto byte_read_loop ; macptr block read not supported, do fallback loop
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
list_blocks += readsize
bufferpointer += readsize
if msb(bufferpointer) == $c0
@@ -317,7 +329,7 @@ close_end:
}
return list_blocks ; number of bytes read
byte_read_loop: ; fallback if macptr() isn't supported on the device
byte_read_loop: ; fallback if MACPTR isn't supported on the device
%asm {{
lda bufferpointer
sta m_in_buffer+1
@@ -348,10 +360,11 @@ m_in_buffer sta $ffff
; optimized for Commander X16 to use MACPTR block read kernal call
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress
return 0
reset_read_channel()
uword total_read = 0
while not cbm.READST() {
cx16.r0 = f_read(bufferpointer, 256)
@@ -370,8 +383,7 @@ m_in_buffer sta $ffff
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #12
jsr cbm.CHKIN ; use channel 12 again for input
jsr reset_read_channel
ldy #0
_loop jsr cbm.CHRIN
sta (P8ZP_SCRATCH_W1),y
@@ -392,25 +404,23 @@ _end rts
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file writing functions (uses io channel 13) -----
; ----- 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()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(13, drivenumber, 1)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
void cbm.OPEN() ; open 13,8,1,"filename"
if_cc {
cbm.CHKOUT(13) ; use #13 as output channel
if_cc
return not cbm.READST()
}
f_close_w()
return false
}
@@ -418,7 +428,7 @@ _end rts
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
if num_bytes!=0 {
cbm.CHKOUT(13) ; use #13 as output channel again
reset_write_channel()
repeat num_bytes {
cbm.CHROUT(@(bufferpointer))
bufferpointer++
@@ -431,7 +441,7 @@ _end rts
sub f_close_w() {
; -- end an iterative file writing session (close channels).
cbm.CLRCHN()
cbm.CLOSE(13)
cbm.CLOSE(WRITE_IO_CHANNEL)
}
@@ -693,11 +703,11 @@ internal_vload:
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
@(cx16.r12)=0
cbm.SETNAM(3, "$=c")
cbm.SETLFS(12, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -734,7 +744,7 @@ internal_vload:
io_error:
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
return 0
if @(cx16.r12)==0 {
@@ -767,7 +777,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] = 12 ; f_open uses channel 12
command[1] = READ_IO_CHANNEL ; f_open uses the read io channel
command[2] = lsb(pos_loword)
command[3] = msb(pos_loword)
command[4] = lsb(pos_hiword)
@@ -777,13 +787,14 @@ io_error:
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
cbm.CLOSE(15)
reset_read_channel() ; back to the read io channel
}
; 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] = 13 ; f_open_w uses channel 13
; 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)
+58
View File
@@ -0,0 +1,58 @@
; Emulator debug interface.
; Docs: https://github.com/X16Community/x16-emulator#debug-io-registers
emudbg {
const uword EMU_BASE = $9fb0
&ubyte EMU_DBG_HOTKEY_ENABLED = EMU_BASE + 0
&ubyte EMU_LOG_VIDEO = EMU_BASE + 1
&ubyte EMU_LOG_KEYBOARD = EMU_BASE + 2
&ubyte EMU_ECHO_MODE = EMU_BASE + 3
&ubyte EMU_SAVE_ON_EXIT = EMU_BASE + 4
&ubyte EMU_RECORD_GIF = EMU_BASE + 5
&ubyte EMU_RECORD_WAV = EMU_BASE + 6
&ubyte EMU_CMDKEYS_DISABLED = EMU_BASE + 7
&ubyte EMU_CPUCLK_L = EMU_BASE + 8 ; write: reset cpu clock to 0
&ubyte EMU_CPUCLK_M = EMU_BASE + 9 ; write: outputs "User debug 1: $xx"
&ubyte EMU_CPUCLK_H = EMU_BASE + 10 ; write: outputs "User debug 2: $xx"
&ubyte EMU_CPUCLK_U = EMU_BASE + 11 ; write: outputs as character to console
; 12 is unused for now
&ubyte EMU_KEYMAP = EMU_BASE + 13
&ubyte EMU_EMU_DETECT1 = EMU_BASE + 14
&ubyte EMU_EMU_DETECT2 = EMU_BASE + 15
sub is_emulator() -> bool {
return EMU_EMU_DETECT1=='1' and EMU_EMU_DETECT2=='6'
}
asmsub console_write(str isoString @R0) clobbers(Y) {
%asm {{
ldy #0
- lda (cx16.r0),y
beq +
sta p8_EMU_CPUCLK_U
iny
bne -
+
}}
}
asmsub console_chrout(ubyte char @A) {
%asm {{
sta p8_EMU_CPUCLK_U
}}
}
asmsub console_value1(ubyte value @A) {
%asm {{
sta p8_EMU_CPUCLK_M
}}
}
asmsub console_value2(ubyte value @A) {
%asm {{
sta p8_EMU_CPUCLK_H
}}
}
}
+37 -460
View File
@@ -4,6 +4,7 @@
;
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
; Note: for identical routines for a monochrome 1 bpp screen, use the "monogfx" module instead.
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
; Note: this library implements code for various resolutions and color depths. This takes up memory.
; If you're memory constrained you should probably not use this built-in library,
@@ -12,12 +13,10 @@
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 monochrome
; mode 2 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
; 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 256c (like SCREEN $80 but using this api instead of kernal)
; mode 5 = bitmap 640 x 480 monochrome
; mode 6 = bitmap 640 x 480 x 4c
; 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
gfx2 {
@@ -29,24 +28,11 @@ gfx2 {
uword width = 0
uword height = 0
ubyte bpp = 0
bool monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
sub screen_mode(ubyte mode) {
cx16.VERA_CTRL=0
when mode {
1 -> {
; lores monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
bpp = 1
}
; TODO modes 2, 3
4 -> {
; lores 256c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
@@ -58,19 +44,7 @@ gfx2 {
height = 240
bpp = 8
}
5 -> {
; highres monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 1
}
6 -> {
2 -> {
; highres 4c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
@@ -96,43 +70,28 @@ gfx2 {
active_mode = mode
if bpp
clear_screen()
clear_screen(0)
}
sub clear_screen() {
monochrome_stipple(false)
sub clear_screen(ubyte color) {
position(0, 0)
when active_mode {
1 -> {
; lores monochrome
repeat 240/2/8
cs_innerloop640()
}
; TODO modes 2, 3
4 -> {
; lores 256c
repeat 240/2
cs_innerloop640()
cs_innerloop640(color)
}
5 -> {
; highres monochrome
repeat 480/8
cs_innerloop640()
}
6 -> {
2 -> {
; highres 4c
ubyte[] colors = [%00000000, %01010101, %10101010, %11111111]
color = colors[color&3]
repeat 480/4
cs_innerloop640()
cs_innerloop640(color)
}
; modes 7 and 8 not supported due to lack of VRAM
}
position(0, 0)
}
sub monochrome_stipple(bool enable) {
monochrome_dont_stipple_flag = not enable
}
sub rect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
if rwidth==0 or rheight==0
return
@@ -147,6 +106,8 @@ gfx2 {
}
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
; 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 {
@@ -156,87 +117,10 @@ gfx2 {
}
sub horizontal_line(uword xx, uword yy, uword length, ubyte color) {
ubyte[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111]
ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111]
if length==0
return
when active_mode {
1, 5 -> {
; monochrome modes, either resolution
ubyte separate_pixels = (8-lsb(xx)) & 7
if separate_pixels as uword > length
separate_pixels = lsb(length)
if separate_pixels {
if monochrome_dont_stipple_flag {
position(xx,yy)
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if color
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
xx += separate_pixels
} else {
repeat separate_pixels {
plot(xx, yy, color)
xx++
}
}
length -= separate_pixels
}
if length {
position(xx, yy)
separate_pixels = lsb(length) & 7
xx += length & $fff8
%asm {{
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda color
bne +
ldy #0 ; black
bra _loop
+ lda monochrome_dont_stipple_flag
beq _stipple
ldy #255 ; don't stipple
bra _loop
_stipple lda yy
and #1 ; determine stipple pattern to use
bne +
ldy #%01010101
bra _loop
+ ldy #%10101010
_loop lda length
ora length+1
beq _done
sty cx16.VERA_DATA0
lda length
bne +
dec length+1
+ dec length
bra _loop
_done
}}
if monochrome_dont_stipple_flag {
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if color
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
} else {
repeat separate_pixels {
plot(xx, yy, color)
xx++
}
}
}
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
4 -> {
1 -> {
; lores 256c
position(xx, yy)
%asm {{
@@ -257,8 +141,8 @@ _done
+
}}
}
6 -> {
; highres 4c ....also mostly usable for mode 2, lores 4c?
2 -> {
; highres 4c ....also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
@@ -307,60 +191,7 @@ _done
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
when active_mode {
1, 5 -> {
; monochrome, lo-res
cx16.r15L = gfx2.plot.maskbits[xx as ubyte & 7] ; bitmask
if color {
if monochrome_dont_stipple_flag {
; draw continuous line.
position2(xx,yy,true)
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
} else {
; draw stippled line.
if xx&1 {
yy++
lheight--
}
position2(xx,yy,true)
if active_mode==1
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
else
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
repeat lheight/2 {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
} else {
position2(xx,yy,true)
cx16.r15 = ~cx16.r15 ; erase pixels
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
}
4 -> {
1 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
position(xx,yy)
@@ -375,7 +206,7 @@ _done
+
}}
}
6 -> {
2 -> {
; highres 4c
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
if lheight==0
@@ -570,7 +401,6 @@ _done
}
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
ubyte[8] @shared maskbits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] @shared mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] @shared shift4c = [6,4,2,0]
ubyte[4] shiftedleft_4c_1 = [1<<6, 1<<4, 1<<2, 1<<0]
@@ -579,31 +409,6 @@ _done
when active_mode {
1 -> {
; lores monochrome
if color {
; solid color or perhaps stipple
%asm {{
lda xx
eor yy
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
mode_1_prepare()
%asm {{
tsb cx16.VERA_DATA0
}}
}
} else {
; only erase
mode_1_prepare()
%asm {{
trb cx16.VERA_DATA0
}}
}
}
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
@@ -619,32 +424,8 @@ _done
sta cx16.VERA_DATA0
}}
}
5 -> {
; highres monochrome
if color {
; solid color or perhaps stipple
%asm {{
lda xx
eor yy
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
mode_5_prepare()
%asm {{
tsb cx16.VERA_DATA0
}}
}
} else {
; only erase
mode_5_prepare()
%asm {{
trb cx16.VERA_DATA0
}}
}
}
6 -> {
; highres 4c ....also mostly usable for mode 2, lores 4c?
2 -> {
; highres 4c ....also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(xx) & 3 ; xbits
; color &= 3
@@ -670,76 +451,11 @@ _done
}}
}
}
sub mode_1_prepare() {
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
xx += yy*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda maskbits,y
}}
}
sub mode_5_prepare() {
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
xx += yy*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda maskbits,y
}}
}
}
sub pget(uword @zp xx, uword yy) -> ubyte {
when active_mode {
1 -> {
; lores monochrome
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
xx += yy*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.maskbits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
@@ -753,31 +469,7 @@ _done
lda cx16.VERA_DATA0
}}
}
5 -> {
; hires monochrome
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
xx += yy*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.maskbits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
6 -> {
2 -> {
; hires 4c
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
@@ -914,29 +606,16 @@ skip:
sub position(uword @zp xx, uword yy) {
when active_mode {
1 -> {
; lores monochrome
cx16.r0 = yy*(320/8) + xx/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
}
5 -> {
; highres monochrome
cx16.r0 = yy*(640/8) + xx/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
6 -> {
2 -> {
; highres 4c
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
}
}
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
}
sub position2(uword @zp xx, uword yy, bool also_port_1) {
@@ -948,7 +627,6 @@ skip:
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
%asm {{
sta cx16.VERA_DATA0
@@ -958,7 +636,6 @@ skip:
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, X, Y) {
; -- sets the next bunch of pixels from a prepared array of bytes.
; for 8 bpp screens this will plot 1 pixel per byte.
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
%asm {{
sta P8ZP_SCRATCH_W1
@@ -1019,92 +696,7 @@ skip:
ubyte[8] @shared char_bitmap_bytes_right
when active_mode {
1, 5 -> {
; monochrome mode, either resolution
cx16.r3 = sctextptr
while @(cx16.r3) {
chardataptr = charset_addr + @(cx16.r3) * $0008
; copy the character bitmap into RAM
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
%asm {{
; pre-shift the bits
lda text.xx
and #7
sta P8ZP_SCRATCH_B1
ldy #0
- lda cx16.VERA_DATA0
stz P8ZP_SCRATCH_REG
ldx P8ZP_SCRATCH_B1
cpx #0
beq +
- lsr a
ror P8ZP_SCRATCH_REG
dex
bne -
+ sta char_bitmap_bytes_left,y
lda P8ZP_SCRATCH_REG
sta char_bitmap_bytes_right,y
iny
cpy #8
bne --
}}
; left part of shifted char
position2(xx, yy, true)
set_autoincrs_mode1_or_5()
if color {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
; right part of shifted char
if lsb(xx) & 7 {
position2(xx+8, yy, true)
set_autoincrs_mode1_or_5()
if color {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
}
cx16.r3++
xx += 8
}
}
4 -> {
1 -> {
; lores 256c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
@@ -1131,7 +723,7 @@ skip:
sctextptr++
}
}
6 -> {
2 -> {
; hires 4c
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
cx16.r11L = color
@@ -1194,34 +786,19 @@ skip:
}
}
}
sub set_autoincrs_mode1_or_5() {
; set autoincrements to go to next pixel row (40 or 80 increment)
if active_mode==1 {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
} else {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
}
}
}
asmsub cs_innerloop640() clobbers(Y) {
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
%asm {{
ldy #80
- stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dey
bne -
rts
+2 -2
View File
@@ -5,7 +5,7 @@
; Wraps the graphics functions that are in ROM.
; Only lo-res 320x240 256 color mode for now.
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
; For other resolutions or other color modes, use the "gfx2" module instead. (which is Cx16-specific)
; 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.
@@ -29,7 +29,7 @@ graphics {
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
void cx16.screen_mode(0, false)
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
txt.clear_screen()
}
+789
View File
@@ -0,0 +1,789 @@
; Monochrome Bitmap pixel graphics routines for the CommanderX16
; Using the full-screen 640x480 and 320x240 screen modes, in 1 bpp mode (black/white).
;
; No text layer is currently shown, but text can be drawn as part of the bitmap itself.
; For color bitmap graphics, see the gfx2 library.
;
; NOTE: a lot of the code here is similar or the same to that in gfx2
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
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
clear_screen(0)
}
sub hires() {
; enable 640*480 bitmap mode
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
clear_screen(0)
}
sub textmode() {
; back to normal 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
}
sub clear_screen(ubyte color) {
stipple(false)
position(0, 0)
when width {
320 -> {
repeat 240/2/8
cs_innerloop640(color)
}
640 -> {
repeat 480/8
cs_innerloop640(color)
}
}
position(0, 0)
}
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[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111]
ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111]
if length==0
return
if length<=8 {
; just use 2 byte writes with shifted mask
position2(xx,yy,true)
%asm {{
ldy length
lda masked_ends,y
sta cx16.r0L ; save left byte
stz P8ZP_SCRATCH_B1
lda xx
and #7
beq +
tay
lda cx16.r0L
- lsr a
ror P8ZP_SCRATCH_B1
dey
bne -
sta cx16.r0L ; new left byte
+
lda dont_stipple_flag
bne _dontstipple
; determine stipple pattern
lda yy
and #1
beq +
lda #%10101010
bne ++
+ lda #%01010101
+ sta P8ZP_SCRATCH_REG
lda cx16.r0L
and P8ZP_SCRATCH_REG
sta cx16.r0L
lda P8ZP_SCRATCH_B1
and P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
_dontstipple
lda draw
beq _clear
lda cx16.r0L ; left byte
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
lda P8ZP_SCRATCH_B1 ; right byte
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
rts
_clear
lda cx16.r0L ; left byte
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
lda P8ZP_SCRATCH_B1 ; right byte
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
rts
}}
}
ubyte separate_pixels = (8-lsb(xx)) & 7
if separate_pixels {
if dont_stipple_flag {
position(xx,yy)
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
xx += separate_pixels
} else {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
}
}
length -= separate_pixels
}
if length {
position(xx, yy)
separate_pixels = lsb(length) & 7
xx += length & $fff8
%asm {{
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda draw
bne +
ldy #0 ; black
bra _loop
+ lda dont_stipple_flag
beq _stipple
ldy #255 ; don't stipple
bra _loop
_stipple lda yy
and #1 ; determine stipple pattern to use
bne +
ldy #%01010101
bra _loop
+ ldy #%10101010
_loop lda length
ora length+1
beq _done
sty cx16.VERA_DATA0
lda length
bne +
dec length+1
+ dec length
bra _loop
_done
}}
if dont_stipple_flag {
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
} else {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
}
}
}
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
cx16.r15L = monogfx.plot.maskbits[xx as ubyte & 7] ; bitmask
if draw {
if dont_stipple_flag {
; draw continuous line.
position2(xx,yy,true)
if width==320
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
} else {
; draw stippled line.
if (xx ^ yy)&1==0 {
yy++
lheight--
}
lheight++ ; because it is divided by 2 later, don't round off the last pixel
position2(xx,yy,true)
if width==320
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
else
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
repeat lheight/2 {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
} else {
position2(xx,yy,true)
cx16.r15 = ~cx16.r15 ; erase pixels
if width==320
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
sub set_both_strides(ubyte stride) {
stride <<= 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
}
}
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) {
; 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
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, draw)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += (yy as word -xx)*2+1
}
}
}
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; 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
decisionOver2 += (yy as word)*2+1
else {
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
ubyte[8] @shared maskbits = [128, 64, 32, 16, 8, 4, 2, 1]
if draw {
; solid color or perhaps stipple
%asm {{
lda xx
eor yy
ora dont_stipple_flag
and #1
}}
if_nz {
prepare()
%asm {{
tsb cx16.VERA_DATA0
}}
}
} else {
; only erase
prepare()
%asm {{
trb cx16.VERA_DATA0
}}
}
sub prepare() {
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
if width==320
xx += yy*(320/8)
else
xx += yy*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda maskbits,y
}}
}
}
sub pget(uword @zp xx, uword yy) -> ubyte {
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
if width==320
xx += yy*(320/8)
else
xx += yy*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.maskbits,y
and cx16.VERA_DATA0
beq +
lda #1
+ rts
}}
}
sub fill(word @zp xx, word @zp yy, 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
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++
%asm {{
ldy cx16.r12L
lda sxl
sta stack_xl_lsb,y
lda sxl+1
sta stack_xl_msb,y
lda sxr
sta stack_xr_lsb,y
lda sxr+1
sta stack_xr_msb,y
lda sy
sta stack_y_lsb,y
lda sy+1
sta stack_y_msb,y
ldy cx16.r12L
lda sdy
sta stack_dy,y
inc cx16.r12L
}}
}
}
sub pop_stack() {
;; cx16.r12L--
;; x1 = stack_xl[cx16.r12L]
;; x2 = stack_xr[cx16.r12L]
;; y = stack_y[cx16.r12L]
;; dy = stack_dy[cx16.r12L]
%asm {{
dec cx16.r12L
ldy cx16.r12L
lda stack_xl_lsb,y
sta x1
lda stack_xl_msb,y
sta x1+1
lda stack_xr_lsb,y
sta x2
lda stack_xr_msb,y
sta x2+1
lda stack_y_lsb,y
sta yy
lda stack_y_msb,y
sta yy+1
ldy cx16.r12L
lda stack_dy,y
sta dy
}}
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 and pget(xx as uword, yy as uword) == cx16.r11L {
plot(xx as uword, yy as uword, cx16.r10L)
xx--
}
if xx >= x1
goto skip
left = xx + 1
if left < x1
push_stack(left, x1 - 1, yy, -dy)
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)
xx++
}
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
xx++
left = xx
} until xx>x2
}
}
sub position(uword @zp xx, uword yy) {
if width==320
cx16.r0 = yy*(320/8)
else
cx16.r0 = yy*(640/8)
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
}
sub position2(uword @zp xx, uword yy, bool also_port_1) {
position(xx, yy)
if also_port_1
cx16.vaddr_clone(0)
}
const ubyte charset_bank = $1
const uword charset_addr = $f000 ; in bank 1, so $1f000
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.
cx16.screen_set_charset(charset, 0)
}
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.
uword chardataptr
ubyte[8] @shared char_bitmap_bytes_left
ubyte[8] @shared char_bitmap_bytes_right
cx16.r3 = sctextptr
while @(cx16.r3) {
chardataptr = charset_addr + @(cx16.r3) * $0008
; copy the character bitmap into RAM
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
%asm {{
; pre-shift the bits
lda text.xx
and #7
sta P8ZP_SCRATCH_B1
ldy #0
- lda cx16.VERA_DATA0
stz P8ZP_SCRATCH_REG
ldx P8ZP_SCRATCH_B1
cpx #0
beq +
- lsr a
ror P8ZP_SCRATCH_REG
dex
bne -
+ sta char_bitmap_bytes_left,y
lda P8ZP_SCRATCH_REG
sta char_bitmap_bytes_right,y
iny
cpy #8
bne --
}}
; left part of shifted char
position2(xx, yy, true)
set_autoincrs()
if draw {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
; right part of shifted char
if lsb(xx) & 7 {
position2(xx+8, yy, true)
set_autoincrs()
if draw {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
}
cx16.r3++
xx += 8
}
sub set_autoincrs() {
; set autoincrements to go to next pixel row (40 or 80 increment)
if width==320 {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
} else {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
}
}
}
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
%asm {{
cmp #0
beq +
lda #255
+ ldy #80
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dey
bne -
rts
}}
}
}
+110
View File
@@ -0,0 +1,110 @@
; Simple routines to control sprites.
; They're not written for high performance, but for simplicity.
; That's why they control 1 sprite at a time. The exception is pos_batch().
; which is quite efficient to update sprite positions of multiple sprites in one go.
; note: sprites z-order will be in front of all layers.
; note: collision mask is not supported here yet.
sprites {
const ubyte SIZE_8 = 0
const ubyte SIZE_16 = 1
const ubyte SIZE_32 = 2
const ubyte SIZE_64 = 3
const ubyte COLORS_16 = 0
const ubyte COLORS_256 = 128
const uword VERA_SPRITEREGS = $fc00 ; $1fc00
uword @zp sprite_reg
sub init(ubyte spritenum,
ubyte databank, uword dataaddr,
ubyte width_flag, ubyte height_flag,
ubyte colors_flag, ubyte palette_offset_idx) {
hide(spritenum)
cx16.VERA_DC_VIDEO |= %01000000 ; enable sprites globally
dataaddr >>= 5
dataaddr |= (databank as uword)<<11
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(dataaddr)) ; address 12:5
cx16.vpoke(1, sprite_reg+1, colors_flag | msb(dataaddr)) ; 4 bpp + address 16:13
cx16.vpoke(1, sprite_reg+6, %00001100) ; z depth %11 = in front of both layers, no flips
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset_idx>>4) ; 64x64 pixels, palette offset
}
sub data(ubyte spritenum, ubyte bank, uword addr) {
addr >>= 5
addr |= (bank as uword)<<11
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(addr)) ; address 12:5
cx16.vpoke_mask(1, sprite_reg+1, %11110000, msb(addr)) ; address 16:13
}
sub pos(ubyte spritenum, word xpos, word ypos) {
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(xpos))
cx16.vpoke(1, sprite_reg+1, msb(xpos))
cx16.vpoke(1, sprite_reg+2, lsb(ypos))
cx16.vpoke(1, sprite_reg+3, msb(ypos))
}
sub pos_batch(ubyte first_spritenum, ubyte num_sprites, uword xpositions_ptr, uword ypositions_ptr) {
; -- note: the x and y positions word arrays must be regular arrays, they cannot be @split arrays!
sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(xpositions_ptr)
xpositions_ptr ++
cx16.VERA_DATA1 = @(xpositions_ptr)
xpositions_ptr ++
}
sprite_reg += 2
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(ypositions_ptr)
ypositions_ptr ++
cx16.VERA_DATA1 = @(ypositions_ptr)
ypositions_ptr ++
}
}
sub setx(ubyte spritenum, word xpos) {
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(xpos))
cx16.vpoke(1, sprite_reg+1, msb(xpos))
}
sub sety(ubyte spritenum, word ypos) {
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(ypos))
cx16.vpoke(1, sprite_reg+1, msb(ypos))
}
sub getx(ubyte spritenum) -> word {
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
}
sub gety(ubyte spritenum) -> word {
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
}
sub hide(ubyte spritenum) {
pos(spritenum, -64, -64)
}
sub flipx(ubyte spritenum, bool flipped) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111110, flipped)
}
sub flipy(ubyte spritenum, bool flipped) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111101, flipped<<1)
}
sub set_palette_offset(ubyte spritenum, ubyte offset) {
sprite_reg = VERA_SPRITEREGS + 7 + spritenum*$0008
cx16.vpoke_mask(1, sprite_reg, %11110000, offset>>4)
}
}
+23 -10
View File
@@ -267,6 +267,17 @@ cx16 {
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; experimental Vera FX registers: (depends on particular value set in VERA_CTRL!!!)
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
&ubyte VERA_FX_MULT = VERA_BASE + $000C
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000A
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000B
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000C
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009 ; (DCSEL=6)
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
@@ -320,17 +331,16 @@ cx16 {
; spelling of the names is taken from the Commander X-16 rom sources
; supported C128 additions
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) clobbers(X, Y) -> ubyte @A, bool @Pc ; note: X,Y size result is not supported, use SCREEN or get_screen_mode routine for that
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
romsub $ff6e = jsrfar() ; following word = address to call, byte after that=rom/ram bank it is in
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7d = primm()
romsub $ff7d = PRIMM()
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
@@ -384,7 +394,8 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
@@ -395,6 +406,7 @@ romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
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 $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
@@ -403,10 +415,10 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
; Audio (rom bank 10)
romsub $C04B = psg_init() clobbers(A,X,Y)
romsub $C04B = psg_init() clobbers(A,X,Y) ; (re)init Vera PSG
romsub $C063 = ym_init() clobbers(A,X,Y) -> bool @Pc ; (re)init YM chip
romsub $C066 = ym_loaddefpatches() clobbers(A,X,Y) -> bool @Pc ; load default YM patches
romsub $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)initialize PSG and YM audio chips
romsub $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)initialize both vera PSG and YM audio chips
; TODO: add more of the audio routines?
@@ -515,7 +527,8 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
; note: inefficient when reading multiple sequential bytes!
%asm {{
pha
lda #1
lda cx16.VERA_CTRL
ora #1
sta cx16.VERA_CTRL
pla
and #1
+126
View File
@@ -0,0 +1,126 @@
; Experimental Vera FX support.
; Docs:
; https://github.com/X16Community/x16-docs/blob/master/VERA%20FX%20Reference.md
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg/edit
verafx {
%option no_symbol_prefixing
sub available() -> bool {
; returns true if Vera FX is available (Vera V0.3.1 or later), false if not.
cx16.r1L = 0
cx16.r0L = cx16.VERA_CTRL
cx16.VERA_CTRL = $7e
if cx16.VERA_DC_VER0 == $56 {
; Vera version number is valid.
; Vera fx is available on Vera version 0.3.1 and later,
; so no need to even check VERA_DC_VER1, which contains 0 (or higher)
cx16.r1L = mkword(cx16.VERA_DC_VER2, cx16.VERA_DC_VER3) >= $0301
}
cx16.VERA_CTRL = cx16.r0L
return cx16.r1L
}
sub clear(ubyte vbank, uword vaddr, ubyte data, uword amountof32bits) {
; use cached 4-byte write to quickly clear a portion of the video memory to a given byte value
; this routine is around 3 times faster as gfx2.clear_screen()
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = vbank | %00110000 ; 4-byte increment
cx16.VERA_ADDR_M = msb(vaddr)
cx16.VERA_ADDR_L = lsb(vaddr)
cx16.VERA_CTRL = 6<<1 ; dcsel = 6, fill the 32 bits cache
cx16.VERA_FX_CACHE_L = data
cx16.VERA_FX_CACHE_M = data
cx16.VERA_FX_CACHE_H = data
cx16.VERA_FX_CACHE_U = data
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
cx16.VERA_FX_MULT = 0
cx16.VERA_FX_CTRL = %01000000 ; cache write enable
if (amountof32bits & %1111110000000011) == 0 {
repeat lsb(amountof32bits >> 2)
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else if (amountof32bits & %1111111000000001) == 0 {
repeat lsb(amountof32bits >> 1)
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else if (lsb(amountof32bits) & 3) == 0 {
repeat amountof32bits >> 2
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else if (lsb(amountof32bits) & 1) == 0 {
repeat amountof32bits >> 1
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else {
repeat amountof32bits
cx16.VERA_DATA0=0 ; write 4 bytes at a time
}
cx16.VERA_FX_CTRL = 0 ; cache write disable
cx16.VERA_CTRL = 0
}
; 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 {
return muls(value1 as word, value2 as word) as uword
}
asmsub muls(word value1 @R0, word value2 @R1) -> word @AY {
%asm {{
lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25
stz cx16.VERA_FX_CTRL ; $9F29 (mainly to reset Addr1 Mode to 0)
lda #%00010000
sta cx16.VERA_FX_MULT ; $9F2C
lda #(6 << 1)
sta cx16.VERA_CTRL ; $9F25
lda cx16.r0
ldy cx16.r0+1
sta cx16.VERA_FX_CACHE_L ; $9F29
sty cx16.VERA_FX_CACHE_M ; $9F2A
lda cx16.r1
ldy cx16.r1+1
sta cx16.VERA_FX_CACHE_H ; $9F2B
sty cx16.VERA_FX_CACHE_U ; $9F2C
lda cx16.VERA_FX_ACCUM_RESET ; $9F29 (DCSEL=6)
; Set the ADDR0 pointer to $1f9bc and write our multiplication result there
; (these are the 4 bytes just before the PSG registers start)
lda #(2 << 1)
sta cx16.VERA_CTRL
lda #%01000000 ; Cache Write Enable
sta cx16.VERA_FX_CTRL
lda #$bc
sta cx16.VERA_ADDR_L
lda #$f9
sta cx16.VERA_ADDR_M
lda #$01
sta cx16.VERA_ADDR_H ; no increment
stz cx16.VERA_DATA0 ; multiply and write out result
lda #%00010001 ; $01 with Increment 1
sta cx16.VERA_ADDR_H ; so we can read out the result
lda cx16.VERA_DATA0
ldy cx16.VERA_DATA0
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
}}
}
sub transparency(bool enable) {
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
if enable
cx16.VERA_FX_CTRL |= %10000000
else
cx16.VERA_FX_CTRL &= %01111111
cx16.VERA_CTRL = 0
}
}
+36 -26
View File
@@ -7,22 +7,33 @@
diskio {
%option no_symbol_prefixing
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
ubyte drivenumber = 8
sub set_drive(ubyte number) {
drivenumber = number
}
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
}
sub reset_write_channel() {
cbm.CHKOUT(WRITE_IO_CHANNEL)
}
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -60,7 +71,7 @@ diskio {
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
@@ -76,12 +87,12 @@ io_error:
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte okay = false
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -108,7 +119,7 @@ io_error:
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if okay
return &list_filename
return 0
@@ -152,7 +163,7 @@ io_error:
return files_found
}
; ----- iterative file lister functions (uses io channel 12) -----
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
@@ -163,11 +174,11 @@ io_error:
iteration_in_progress = true
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -192,7 +203,7 @@ io_error:
return false
repeat {
void cbm.CHKIN(12) ; use #12 as input channel again
reset_read_channel() ; use the input io channel again
uword nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
@@ -253,13 +264,13 @@ close_end:
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file loader functions (uses io channel 12) -----
; ----- iterative file loader functions (uses the input io channel) -----
sub f_open(uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read
@@ -267,18 +278,18 @@ close_end:
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
void cbm.OPEN() ; open 12,8,12,"filename"
if_cc {
if cbm.READST()==0 {
iteration_in_progress = true
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cc {
void cbm.CHRIN() ; read first byte to test for file not found
if not cbm.READST() {
cbm.CLOSE(12) ; close file because we already consumed first byte
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
void cbm.OPEN() ; re-open the file
void cbm.CHKIN(12)
cbm.CLRCHN() ; reset default i/o channels
return true
}
}
@@ -294,6 +305,7 @@ close_end:
if not iteration_in_progress or not num_bytes
return 0
reset_read_channel()
list_blocks = 0 ; we reuse this variable for the total number of bytes read
%asm {{
@@ -328,6 +340,7 @@ m_in_buffer sta $ffff
if not iteration_in_progress
return 0
reset_read_channel()
uword total_read = 0
while not cbm.READST() {
cx16.r0 = f_read(bufferpointer, 256)
@@ -346,8 +359,7 @@ m_in_buffer sta $ffff
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #12
jsr cbm.CHKIN ; use channel 12 again for input
jsr reset_read_channel
ldy #0
_loop jsr cbm.CHRIN
sta (P8ZP_SCRATCH_W1),y
@@ -368,25 +380,23 @@ _end rts
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file writing functions (uses io channel 13) -----
; ----- 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()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(13, drivenumber, 1)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
void cbm.OPEN() ; open 13,8,1,"filename"
if_cc {
cbm.CHKOUT(13) ; use #13 as output channel
if_cc
return not cbm.READST()
}
f_close_w()
return false
}
@@ -394,7 +404,7 @@ _end rts
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
if num_bytes!=0 {
cbm.CHKOUT(13) ; use #13 as output channel again
reset_write_channel()
repeat num_bytes {
cbm.CHROUT(@(bufferpointer))
bufferpointer++
@@ -407,7 +417,7 @@ _end rts
sub f_close_w() {
; -- end an iterative file writing session (close channels).
cbm.CLRCHN()
cbm.CLOSE(13)
cbm.CLOSE(WRITE_IO_CHANNEL)
}
+12 -5
View File
@@ -56,10 +56,15 @@ _multiplier = P8ZP_SCRATCH_REG
multiply_words .proc
; -- multiply two 16-bit words into a 32-bit result (signed and unsigned)
; input: A/Y = first 16-bit number, cx16.R0 = second 16-bit number
; output: multiply_words.result == cx16.R0:R1, 4-bytes/32-bits product, LSB order (low-to-high) low 16 bits also in AY.
; input: A/Y = first 16-bit number, multiply_words.multiplier = second 16-bit number
; output: multiply_words.result, 4-bytes/32-bits product, LSB order (low-to-high) low 16 bits also in AY.
; NOTE: the result (which includes the multiplier parameter on entry) is a 4-byte array.
; this routine could be faster if we could stick that into zeropage,
; but there currently is no way to use 4 consecutive bytes in ZP (without disabling irq and saving/restoring them)...
; mult62.a
; from: https://github.com/TobyLobster/multiply_test/blob/main/tests/mult62.a
; based on Dr Jefyll, http://forum.6502.org/viewtopic.php?f=9&t=689&start=0#p19958
; - adjusted to use fixed zero page addresses
; - removed 'decrement to avoid clc' as this is slower on average
@@ -72,9 +77,8 @@ multiply_words .proc
; Average cycles:
; 93 bytes
_multiplicand = P8ZP_SCRATCH_W1 ; 2 bytes
_multiplier = cx16.r0 ; 2 bytes
result = cx16.r0 ; 4 bytes (note: shares memory with multiplier) so is r0 and ALSO r1.
_multiplicand = P8ZP_SCRATCH_W2 ; 2 bytes
multiplier = result
; 16 bit x 16 bit unsigned multiply, 32 bit result
;
@@ -174,6 +178,9 @@ _inner_loop2
lda result
ldy result+1
rts
result .byte 0,0,0,0
.pend
+56
View File
@@ -98,6 +98,62 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}}
}
asmsub log2(ubyte value @A) -> ubyte @Y {
%asm {{
sta P8ZP_SCRATCH_B1
lda #$80
ldy #7
- bit P8ZP_SCRATCH_B1
beq +
rts
+ dey
bne +
rts
+ lsr a
bne -
}}
}
asmsub log2w(uword value @AY) -> ubyte @Y {
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$8000
sta cx16.r0
lda #>$8000
sta cx16.r0+1
ldy #15
- lda P8ZP_SCRATCH_W1
and cx16.r0
sta P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_W1+1
and cx16.r0+1
ora P8ZP_SCRATCH_B1
beq +
rts
+ dey
bne +
rts
+ lsr cx16.r0+1
ror cx16.r0
jmp -
}}
}
asmsub mul16_last_upper() -> uword @AY {
; This routine peeks into the internal 32 bits multiplication result buffer of the
; 16*16 bits multiplication routine, to fetch the upper 16 bits of the last calculation.
; Notes:
; - to avoid interference it's best to fetch and store this value immediately after the multiplication expression.
; for instance, simply printing a number may already result in new multiplication calls being performed
; - not all multiplications in the source code result in an actual multiplication call:
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
%asm {{
lda multiply_words.result+2
ldy multiply_words.result+3
rts
}}
}
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
+90 -52
View File
@@ -183,60 +183,98 @@ math {
}}
}
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
ubyte angle = atan2(x1, y1, x2, y2) - 256/48
return 23-lsb(mkword(angle,0) / 2730)
}
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
; shift the points into the positive quadrant
ubyte px1
ubyte py1
ubyte px2
ubyte py2
if x1<0 or x2<0 {
px1 = x1 as ubyte + 128
px2 = x2 as ubyte + 128
} else {
px1 = x1 as ubyte
px2 = x2 as ubyte
}
if y1<0 or y2<0 {
py1 = y1 as ubyte + 128
py2 = y2 as ubyte + 128
} else {
py1 = y1 as ubyte
py2 = y2 as ubyte
sub log2(ubyte value) -> ubyte {
ubyte result = 7
ubyte compare = $80
repeat {
if value&compare
return result
result--
if_z
return 0
compare >>= 1
}
}
return direction(px1, py1, px2, py2)
}
sub log2w(uword value) -> ubyte {
ubyte result = 15
uword compare = $8000
repeat {
if value&compare
return result
result--
if_z
return 0
compare >>= 1
}
}
sub direction_qd(ubyte quadrant, ubyte xdelta, ubyte ydelta) -> ubyte {
; From a pair of X/Y deltas (both >=0), and quadrant 0-3, calculate discrete direction between 0 and 23.
when quadrant {
3 -> return direction(0, 0, xdelta, ydelta)
2 -> return direction(xdelta, 0, 0, ydelta)
1 -> return direction(0, ydelta, xdelta, 0)
else -> return direction(xdelta, ydelta, 0, 0)
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
ubyte angle = atan2(x1, y1, x2, y2) - 256/48
return 23-lsb(mkword(angle,0) / 2730)
}
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
; shift the points into the positive quadrant
ubyte px1
ubyte py1
ubyte px2
ubyte py2
if x1<0 or x2<0 {
px1 = x1 as ubyte + 128
px2 = x2 as ubyte + 128
} else {
px1 = x1 as ubyte
px2 = x2 as ubyte
}
if y1<0 or y2<0 {
py1 = y1 as ubyte + 128
py2 = y2 as ubyte + 128
} else {
py1 = y1 as ubyte
py2 = y2 as ubyte
}
return direction(px1, py1, px2, py2)
}
sub direction_qd(ubyte quadrant, ubyte xdelta, ubyte ydelta) -> ubyte {
; From a pair of X/Y deltas (both >=0), and quadrant 0-3, calculate discrete direction between 0 and 23.
when quadrant {
3 -> return direction(0, 0, xdelta, ydelta)
2 -> return direction(xdelta, 0, 0, ydelta)
1 -> return direction(0, ydelta, xdelta, 0)
else -> return direction(xdelta, ydelta, 0, 0)
}
}
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
%ir {{
loadm.b r65532,math.atan2.x1
loadm.b r65533,math.atan2.y1
loadm.b r65534,math.atan2.x2
loadm.b r65535,math.atan2.y2
syscall 44 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
returnr.b r0
}}
}
sub mul16_last_upper() -> uword {
; This routine peeks into the internal 32 bits multiplication result buffer of the
; 16*16 bits multiplication routine, to fetch the upper 16 bits of the last calculation.
; Notes:
; - to avoid interference it's best to fetch and store this value immediately after the multiplication expression.
; for instance, simply printing a number may already result in new multiplication calls being performed
; - not all multiplications in the source code result in an actual multiplication call:
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
%ir {{
syscall 46 (): r0.w
returnr.w r0
}}
}
}
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
%ir {{
loadm.b r65532,math.atan2.x1
loadm.b r65533,math.atan2.y1
loadm.b r65534,math.atan2.x2
loadm.b r65535,math.atan2.y2
syscall 44 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
returnr.b r0
}}
}
}
+1 -1
View File
@@ -42,7 +42,7 @@ private fun compileMain(args: Array<String>): Boolean {
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val warnSymbolShadowing by cli.option(ArgType.Boolean, fullName = "warnshadow", description="show assembler warnings about symbol shadowing")
@@ -5,11 +5,11 @@ package prog8.buildversion
*/
const val MAVEN_GROUP = "prog8"
const val MAVEN_NAME = "compiler"
const val VERSION = "9.3-SNAPSHOT"
const val GIT_REVISION = 4012
const val GIT_SHA = "9167ba499de99696aa7c6887d4867eae70eb5caf"
const val GIT_DATE = "2023-07-30T15:49:35Z"
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 GIT_BRANCH = "master"
const val BUILD_DATE = "2023-07-30T16:41:27Z"
const val BUILD_UNIX_TIME = 1690735287514L
const val BUILD_DATE = "2023-09-18T21:27:55Z"
const val BUILD_UNIX_TIME = 1695072475414L
const val DIRTY = 1
+5 -1
View File
@@ -109,7 +109,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
val intermediateAst = IntermediateAstMaker(program).transform()
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
@@ -254,6 +254,10 @@ fun parseMainModule(filepath: Path,
importer.importImplicitLibraryModule("math")
}
importer.importImplicitLibraryModule("prog8_lib")
if(program.allBlocks.any { it.options().any { option->option=="verafxmuls" } }) {
if(compTarget.name==Cx16Target.NAME)
importer.importImplicitLibraryModule("verafx")
}
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
@@ -7,6 +7,8 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException
@@ -76,14 +78,19 @@ internal class AstChecker(private val program: Program,
}
override fun visit(unrollLoop: UnrollLoop) {
if(unrollLoop.iterations<0 || unrollLoop.iterations>65535)
errors.err("invalid number of unrolls", unrollLoop.position)
unrollLoop.body.statements.forEach {
if(it !is InlineAssembly && it !is Assignment && it !is BuiltinFunctionCallStatement && it !is FunctionCallStatement && it !is PostIncrDecr)
errors.err("invalid statement in unroll loop", it.position)
}
if(unrollLoop.iterations * unrollLoop.body.statements.size > 256) {
errors.warn("large number of unrolls, potential code size issue", unrollLoop.position)
val iterations = unrollLoop.iterations.constValue(program)?.number?.toInt()
if(iterations==null) {
errors.err("unroll needs constant number of iterations", unrollLoop.position)
} else {
if (iterations < 0 || iterations > 65535)
errors.err("invalid number of unrolls", unrollLoop.position)
unrollLoop.body.statements.forEach {
if (it !is InlineAssembly && it !is Assignment && it !is BuiltinFunctionCallStatement && it !is FunctionCallStatement && it !is PostIncrDecr)
errors.err("invalid statement in unroll loop", it.position)
}
if (iterations * unrollLoop.body.statements.size > 256) {
errors.warn("large number of unrolls, potential code size issue", unrollLoop.position)
}
}
super.visit(unrollLoop)
}
@@ -306,7 +313,7 @@ internal class AstChecker(private val program: Program,
if(subroutine.name in BuiltinFunctions)
err("cannot redefine a built-in function")
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine)
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine && !subroutine.definingBlock.isInLibrary)
errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
@@ -817,8 +824,16 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing")}.any { !it })
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing", "verafxmuls")}.any { !it })
err("invalid option directive argument(s)")
if(directive.args.any {it.name=="align_word"} && directive.args.any { it.name=="align_page"})
err("conflicting alignment options")
if(directive.parent is Block) {
if(directive.args.any {it.name !in arrayOf("align_word", "align_page", "no_symbol_prefixing", "force_output", "merge", "splitarrays", "verafxmuls")})
err("using an option that is not valid for blocks")
}
if(directive.args.any { it.name=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
err("verafx option is only valid on cx16 target")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
}
@@ -1178,8 +1193,13 @@ internal class AstChecker(private val program: Program,
if(funcName[0] in InplaceModifyingBuiltinFunctions) {
// in-place modification, can't be done on literals
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
if(funcName[0]=="setlsb" || funcName[0]=="setmsb") {
val firstArg = functionCallStatement.args[0]
if(firstArg !is IdentifierReference && firstArg !is ArrayIndexedExpression)
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
} else {
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead })
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
}
}
@@ -5,10 +5,12 @@ import prog8.ast.Program
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import java.io.CharConversionException
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
@@ -63,13 +65,35 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
errors.err("compilation target doesn't support this text encoding", char.position)
return noModifications
}
return listOf(IAstModification.ReplaceNode(
char,
NumericLiteral(DataType.UBYTE, target.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
parent
))
return try {
val encoded = target.encodeString(char.value.toString(), char.encoding)
listOf(IAstModification.ReplaceNode(
char,
NumericLiteral(DataType.UBYTE, encoded[0].toDouble(), char.position),
parent
))
} catch (x: CharConversionException) {
errors.err(x.message ?: "can't encode character", char.position)
noModifications
}
}
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
// this only *checks* for errors for string encoding. The actual encoding is done much later
require(string.encoding != Encoding.DEFAULT)
if(string.encoding != Encoding.DEFAULT && string.encoding !in target.supportedEncodings) {
errors.err("compilation target doesn't support this text encoding", string.position)
return noModifications
}
try {
target.encodeString(string.value, string.encoding)
} catch (x: CharConversionException) {
errors.err(x.message ?: "can't encode string", string.position)
}
return noModifications
}
}
walker.visit(this)
walker.applyModifications()
}
@@ -78,6 +78,16 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
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"
@@ -56,7 +56,7 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
listOf(
IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
AddressOf(identifier, null, typecast.position),
parent
)
)
@@ -24,6 +24,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - replace peek() and poke() by direct memory accesses.
// - repeat-forever loops replaced by label+jump.
// - pointer[word] replaced by @(pointer+word)
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
@@ -211,4 +212,30 @@ _after:
return noModifications
}
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// for word variables:
// @(&var) --> lsb(var)
// @(&var+1) --> msb(var)
val addrOf = memread.addressExpression as? AddressOf
if(addrOf?.arrayIndex!=null)
return noModifications
if(addrOf!=null && addrOf.identifier.inferType(program).isWords) {
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), memread.position), mutableListOf(addrOf.identifier), memread.position)
return listOf(IAstModification.ReplaceNode(memread, lsb, parent))
}
val expr = memread.addressExpression as? BinaryExpression
if(expr!=null && expr.operator=="+") {
val addressOf = expr.left as? AddressOf
val offset = (expr.right as? NumericLiteral)?.number?.toInt()
if(addressOf!=null && offset==1) {
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(addressOf.identifier), memread.position)
return listOf(IAstModification.ReplaceNode(memread, msb, parent))
}
}
return noModifications
}
}
@@ -19,7 +19,7 @@ import kotlin.io.path.isRegularFile
/**
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
*/
class IntermediateAstMaker(private val program: Program) {
class IntermediateAstMaker(private val program: Program, private val errors: IErrorReporter) {
fun transform(): PtProgram {
val ptProgram = PtProgram(
program.name,
@@ -161,6 +161,7 @@ class IntermediateAstMaker(private val program: Program) {
private fun transform(srcBlock: Block): PtBlock {
var alignment = PtBlock.BlockAlignment.NONE
var forceOutput = false
var veraFxMuls = false
var noSymbolPrefixing = false
val directives = srcBlock.statements.filterIsInstance<Directive>()
for (directive in directives.filter { it.directive == "%option" }) {
@@ -169,15 +170,16 @@ class IntermediateAstMaker(private val program: Program) {
"align_word" -> alignment = PtBlock.BlockAlignment.WORD
"align_page" -> alignment = PtBlock.BlockAlignment.PAGE
"no_symbol_prefixing" -> noSymbolPrefixing = true
"force_output" -> forceOutput=true
"force_output" -> forceOutput = true
"merge", "splitarrays" -> { /* ignore this one */ }
"verafxmuls" -> veraFxMuls = true
else -> throw FatalAstException("weird directive option: ${arg.name}")
}
}
}
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val src = srcBlock.definingModule.source
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, noSymbolPrefixing, alignment, src, srcBlock.position)
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, noSymbolPrefixing, veraFxMuls, alignment, src, srcBlock.position)
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements)
block.add(transformStatement(stmt))
@@ -326,7 +328,7 @@ class IntermediateAstMaker(private val program: Program) {
private fun transform(srcUnroll: UnrollLoop): PtNodeGroup {
val result = PtNodeGroup()
repeat(srcUnroll.iterations) {
repeat(srcUnroll.iterations.constValue(program)!!.number.toInt()) {
srcUnroll.body.statements.forEach {
result.add(transformStatement(it))
}
@@ -398,13 +400,26 @@ class IntermediateAstMaker(private val program: Program) {
}
private fun transform(srcVar: VarDecl): PtNode {
return when(srcVar.type) {
when(srcVar.type) {
VarDeclType.VAR -> {
val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null
PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
if(srcVar.datatype in ArrayDatatypes) {
if(value==null) {
val blockOptions = srcVar.definingBlock.options()
if("align_page" in blockOptions || "align_word" in blockOptions) {
errors.warn("converting uninitialized array to explicit zeros because of block alignment option", srcVar.position)
val zeros = PtArray(srcVar.datatype, srcVar.position)
repeat(srcVar.arraysize!!.constIndex()!!) {
zeros.children.add(PtNumber(ArrayToElementTypes.getValue(srcVar.datatype), 0.0, srcVar.position))
}
return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, zeros, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
}
}
return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
VarDeclType.CONST -> return PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> return PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
}
@@ -440,6 +455,8 @@ class IntermediateAstMaker(private val program: Program) {
addr.add(PtIdentifier(name+"_lsb", dt, src.identifier.position)) // NOTE: assumes _lsb is first in memory! (immediately followed by _msb)
else
addr.add(transform(src.identifier))
if(src.arrayIndex!=null)
addr.add(transformExpression(src.arrayIndex!!.indexExpr))
return addr
}
@@ -1,16 +1,17 @@
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
import prog8.ast.statements.IfElse
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.PrefixExpression
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.code.core.DataType
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.core.IntegerDatatypes
internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
@@ -99,110 +100,4 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
}
return noModifications
}
override fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
return noModifications
val binExpr = ifElse.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in ComparisonOperators)
return noModifications
// Simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
val simplify = simplifyConditionalExpression(binExpr)
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(
ifElse,
simplify.rightVarAssignment,
parent as IStatementContainer
)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(
ifElse,
simplify.leftVarAssignment,
parent as IStatementContainer
)
}
return modifications
}
private class CondExprSimplificationResult(
val leftVarAssignment: Assignment?,
val leftOperandReplacement: Expression?,
val rightVarAssignment: Assignment?,
val rightOperandReplacement: Expression?
)
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
return CondExprSimplificationResult(null, null, null, null)
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(!leftDt.isInteger || !rightDt.isInteger) {
// we can't reasonably simplify non-integer expressions
return CondExprSimplificationResult(null, null, null, null)
}
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple
&& expr.left !is IFunctionCall
&& expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple
&& expr.right !is IFunctionCall
&& expr.right !is ContainmentCheck
if(separateLeftExpr) {
val name = getTempRegisterName(leftDt)
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.left.copy(),
AssignmentOrigin.ASMGEN, expr.position
)
}
if(separateRightExpr) {
val (tempVarName, _) = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
rightOperandReplacement = IdentifierReference(tempVarName, expr.position)
rightAssignment = Assignment(
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
expr.right.copy(),
AssignmentOrigin.ASMGEN, expr.position
)
}
return CondExprSimplificationResult(
leftAssignment, leftOperandReplacement,
rightAssignment, rightOperandReplacement
)
}
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
dt istype DataType.BOOL -> listOf("cx16", "r9L")
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
dt istype DataType.UWORD -> listOf("cx16", "r9")
dt istype DataType.WORD -> listOf("cx16", "r9s")
dt.isPassByReference -> listOf("cx16", "r9")
else -> throw FatalAstException("invalid dt $dt")
}
}
}
@@ -304,8 +304,8 @@ internal class StatementReorderer(
val eltsize = program.memsizer.memorySize(ArrayToElementTypes.getValue(sourceVar.datatype))
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
mutableListOf(
AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position),
AddressOf(sourceIdent, null, assign.position),
AddressOf(identifier, null, assign.position),
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
), false, assign.position
)
@@ -265,7 +265,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, arg.position),
AddressOf(identifier, null, arg.position),
call as Node)
}
} else if(arg is NumericLiteral) {
@@ -286,7 +286,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(arg is IdentifierReference && DataType.UWORD == param.type) {
modifications += IAstModification.ReplaceNode(
arg,
AddressOf(arg, arg.position),
AddressOf(arg, null, arg.position),
call as Node
)
}
@@ -312,7 +312,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(identifier, pair.second.position),
AddressOf(identifier, null, pair.second.position),
call as Node)
break
}
@@ -325,7 +325,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(pair.second is IdentifierReference && DataType.UWORD in pair.first.possibleDatatypes) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position),
AddressOf(pair.second as IdentifierReference, null, pair.second.position),
call as Node
)
}
+6 -5
View File
@@ -95,9 +95,12 @@ class TestCompilerOnExamplesCx16: FunSpec({
val onlyCx16 = cartesianProduct(
listOf(
"chunkedfile/demo",
"vtui/testvtui",
"pcmaudio/play-adpcm",
"pcmaudio/stream-wav",
"sprites/dragon",
"sprites/dragons",
"amiga",
"bdmusic",
"bobs",
@@ -107,21 +110,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
"colorbars",
"cube3d",
"cxlogo",
"datetime",
"diskspeed",
"fileseek",
"highresbitmap",
"kefrenbars",
"keyboardhandler",
"mandelbrot",
"mandelbrot-gfx-colors",
"multipalette",
"plasma",
"rasterbars",
"sincos",
"snow",
"spotlight",
"tehtriz",
"testgfx2",
"testmonogfx",
),
listOf(false, true)
)
@@ -142,7 +143,6 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
listOf(
"animals",
"balls",
"bsieve",
"cube3d",
"cube3d-float",
"cube3d-gfx",
@@ -156,6 +156,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"numbergame",
"primes",
"screencodes",
"sincos",
"sorting",
"swirl",
"swirl-float",
+15
View File
@@ -89,6 +89,17 @@ class TestStringEncodings: FunSpec({
PetsciiEncoding.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
}
test("testReturn") {
PetsciiEncoding.encodePetscii("\r", true) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.encodePetscii("\r", false) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.encodePetscii("\n", true) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.encodePetscii("\n", false) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.decodePetscii(listOf(13u), false) shouldBe Ok("\n")
PetsciiEncoding.decodePetscii(listOf(13u), true) shouldBe Ok("\n")
PetsciiEncoding.decodePetscii(listOf(0x8du), false) shouldBe Ok("\r")
PetsciiEncoding.decodePetscii(listOf(0x8du), true) shouldBe Ok("\r")
}
test("testSpecialReplacements") {
fun encodeP(c: Char, lower: Boolean) = PetsciiEncoding.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
fun encodeS(c: Char, lower: Boolean) = PetsciiEncoding.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
@@ -122,6 +133,10 @@ class TestStringEncodings: FunSpec({
encodeP('\\', true) shouldBe 205u
encodeS('\\', false) shouldBe 77u
encodeS('\\', true) shouldBe 77u
encodeP('\r', true) shouldBe 13u
encodeP('\r', false) shouldBe 13u
encodeP('\n', true) shouldBe 13u
encodeP('\n', false) shouldBe 13u
}
test("testBoxDrawingCharsEncoding") {
+24 -3
View File
@@ -82,7 +82,28 @@ class TestSymbolTable: FunSpec({
st.allMemMappedVariables.single().scopedName shouldBe "block1.sub1.v3"
st.allMemorySlabs.single().scopedName shouldBe "block1.sub1.slab1"
}
test("static vars") {
val node = PtIdentifier("dummy", DataType.UBYTE, Position.DUMMY)
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, 99.0, null, null, null, ZeropageWish.DONTCARE, node)
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, null, ZeropageWish.DONTCARE, node)
val arrayInitNonzero = listOf(StArrayElement(1.1, null), StArrayElement(2.2, null), StArrayElement(3.3, null))
val arrayInitAllzero = listOf(StArrayElement(0.0, null), StArrayElement(0.0, null), StArrayElement(0.0, null))
val stVar3 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitNonzero, 3, ZeropageWish.DONTCARE, node)
val stVar4 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitAllzero, 3, ZeropageWish.DONTCARE, node)
val stVar5 = StStaticVariable("uninitialized", DataType.ARRAY_UW, null, null, null, 3, ZeropageWish.DONTCARE, node)
stVar1.uninitialized shouldBe false
stVar1.length shouldBe null
stVar2.uninitialized shouldBe true
stVar2.length shouldBe null
stVar3.uninitialized shouldBe false
stVar3.length shouldBe 3
stVar4.uninitialized shouldBe false
stVar4.length shouldBe 3
stVar5.uninitialized shouldBe true
stVar5.length shouldBe 3
}
})
@@ -90,7 +111,7 @@ private fun makeSt(): SymbolTable {
// first build the AST
val astProgram = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val astBlock1 = PtBlock("block1", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
val astBlock1 = PtBlock("block1", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
val astConstant1 = PtConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY)
val astConstant2 = PtConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY)
astBlock1.add(astConstant1)
@@ -113,7 +134,7 @@ private fun makeSt(): SymbolTable {
astBlock1.add(astSub2)
val astBfunc = PtIdentifier("msb", DataType.UBYTE, Position.DUMMY)
astBlock1.add(astBfunc)
val astBlock2 = PtBlock("block2", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
val astBlock2 = PtBlock("block2", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
val astSub21 = PtSub("sub1", emptyList(), null, Position.DUMMY)
val astSub22 = PtSub("sub2", emptyList(), null, Position.DUMMY)
val astSub221 = PtSub("subsub", emptyList(), null, Position.DUMMY)
+6 -6
View File
@@ -118,11 +118,11 @@ class TestC64Zeropage: FunSpec({
test("testFreeSpacesBytes") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp1.availableBytes() shouldBe 18
zp1.availableBytes() shouldBe 17
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
zp2.availableBytes() shouldBe 88
zp2.availableBytes() shouldBe 87
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
zp3.availableBytes() shouldBe 97
zp3.availableBytes() shouldBe 96
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
zp4.availableBytes() shouldBe 207
zp4.allocate("test", DataType.UBYTE, null, null, errors)
@@ -162,7 +162,7 @@ class TestC64Zeropage: FunSpec({
test("testBasicsafeAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp.availableBytes() shouldBe 18
zp.availableBytes() shouldBe 17
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -215,18 +215,18 @@ class TestC64Zeropage: FunSpec({
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp.availableBytes() shouldBe 18
zp.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
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xa6u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
zp.availableBytes() shouldBe 0
}
+3 -1
View File
@@ -8,6 +8,7 @@ import prog8.code.ast.*
import prog8.code.core.DataType
import prog8.code.target.C64Target
import prog8.compiler.astprocessing.IntermediateAstMaker
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestIntermediateAst: FunSpec({
@@ -26,8 +27,9 @@ class TestIntermediateAst: FunSpec({
}
"""
val target = C64Target()
val errors = ErrorReporterForTests()
val result = compileText(target, false, text, writeAssembly = false)!!
val ast = IntermediateAstMaker(result.compilerAst).transform()
val ast = IntermediateAstMaker(result.compilerAst, errors).transform()
ast.name shouldBe result.compilerAst.name
ast.allBlocks().any() shouldBe true
val entry = ast.entrypoint() ?: fail("no main.start() found")
@@ -52,13 +52,13 @@ class TestAsmGenSymbols: StringSpec({
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, statements, Position.DUMMY)
@@ -75,7 +75,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 ptProgram = IntermediateAstMaker(program).transform()
val ptProgram = IntermediateAstMaker(program, errors).transform()
val st = SymbolTableMaker(ptProgram, options).make()
return AsmGen6502Internal(ptProgram, st, options, errors)
}
+2 -2
View File
@@ -24,7 +24,7 @@ compileTestKotlin {
dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.antlr:antlr4-runtime:4.12.0"
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation project(':parser')
}
@@ -36,7 +36,7 @@ configurations.all {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
}
}
@@ -429,6 +429,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
override fun visit(addressOf: AddressOf) {
output("&")
addressOf.identifier.accept(this)
addressOf.arrayIndex?.accept(this)
}
override fun visit(inlineAssembly: InlineAssembly) {
+19 -10
View File
@@ -317,9 +317,10 @@ private fun Prog8ANTLRParser.Assign_targetContext.toAst() : AssignTarget {
is MemoryTargetContext ->
AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()), toPosition())
is ArrayindexedTargetContext -> {
val arrayvar = scoped_identifier().toAst()
val index = arrayindex().toAst()
val arrayindexed = ArrayIndexedExpression(arrayvar, index, scoped_identifier().toPosition())
val ax = arrayindexed()
val arrayvar = ax.scoped_identifier().toAst()
val index = ax.arrayindex().toAst()
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
AssignTarget(null, arrayindexed, null, toPosition())
}
else -> throw FatalAstException("weird assign target node $this")
@@ -434,10 +435,11 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
}
}
if(arrayindex()!=null) {
val identifier = scoped_identifier().toAst()
val index = arrayindex().toAst()
return ArrayIndexedExpression(identifier, index, scoped_identifier().toPosition())
if(arrayindexed()!=null) {
val ax = arrayindexed()
val identifier = ax.scoped_identifier().toAst()
val index = ax.arrayindex().toAst()
return ArrayIndexedExpression(identifier, index, ax.toPosition())
}
if(scoped_identifier()!=null)
@@ -467,8 +469,15 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
if(addressof()!=null) {
val addressOf = addressof()
val identifier = addressOf.scoped_identifier()
val array = addressOf.arrayindexed()
return if(identifier!=null)
AddressOf(addressof().scoped_identifier().toAst(), null, toPosition())
else
AddressOf(array.scoped_identifier().toAst(), array.arrayindex().toAst(), toPosition())
}
throw FatalAstException(text)
}
@@ -581,7 +590,7 @@ private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
}
private fun Prog8ANTLRParser.UnrollloopContext.toAst(): UnrollLoop {
val iterations = integerliteral().toAst().number.toInt()
val iterations = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
@@ -49,7 +49,7 @@ sealed class Expression: Node {
(other is TypecastExpression && other.implicit==implicit && other.type==type && other.expression isSameAs expression)
}
is AddressOf -> {
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource)
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource && other.arrayIndex==arrayIndex)
}
is RangeExpression -> {
(other is RangeExpression && other.from==from && other.to==to && other.step==step)
@@ -369,25 +369,34 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
}
}
data class AddressOf(var identifier: IdentifierReference, override val position: Position) : Expression() {
data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayIndex?, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.parent=this
identifier.linkParents(this)
arrayIndex?.linkParents(this)
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is IdentifierReference && node===identifier)
identifier = replacement
replacement.parent = this
if(node===identifier) {
require(replacement is IdentifierReference)
identifier = replacement
replacement.parent = this
} else if(node===arrayIndex) {
require(replacement is ArrayIndex)
arrayIndex = replacement
replacement.parent = this
} else {
throw FatalAstException("invalid replace, no child node $node")
}
}
override fun copy() = AddressOf(identifier.copy(), position)
override fun copy() = AddressOf(identifier.copy(), arrayIndex?.copy(), position)
override fun constValue(program: Program): NumericLiteral? = null
override fun referencesIdentifier(nameInSource: List<String>) = identifier.nameInSource==nameInSource
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)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@@ -931,11 +931,13 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
iterations?.referencesIdentifier(nameInSource)==true || body.referencesIdentifier(nameInSource)
}
class UnrollLoop(val iterations: Int, var body: AnonymousScope, override val position: Position) : Statement() {
class UnrollLoop(var iterations: Expression, var body: AnonymousScope, override val position: Position) : Statement() {
// note: the iterations needs to evaluate to a constant number once parsed.
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
iterations.linkParents(this)
body.linkParents(this)
}
@@ -943,13 +945,14 @@ class UnrollLoop(val iterations: Int, var body: AnonymousScope, override val pos
override fun replaceChildNode(node: Node, replacement: Node) {
if (node===body) body = replacement as AnonymousScope
else if (node===iterations) iterations = replacement as Expression
else throw FatalAstException("invalid replace")
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = body.referencesIdentifier(nameInSource)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = iterations.referencesIdentifier(nameInSource) || body.referencesIdentifier(nameInSource)
}
class UntilLoop(var body: AnonymousScope,
@@ -398,6 +398,7 @@ abstract class AstWalker {
fun visit(unrollLoop: UnrollLoop, parent: Node) {
track(before(unrollLoop, parent), unrollLoop, parent)
unrollLoop.iterations.accept(this, unrollLoop)
unrollLoop.body.accept(this, unrollLoop)
track(after(unrollLoop, parent), unrollLoop, parent)
}
@@ -457,6 +458,7 @@ abstract class AstWalker {
fun visit(addressOf: AddressOf, parent: Node) {
track(before(addressOf, parent), addressOf, parent)
addressOf.identifier.accept(this, addressOf)
addressOf.arrayIndex?.accept(this)
track(after(addressOf, parent), addressOf, parent)
}
@@ -173,6 +173,7 @@ interface IAstVisitor {
fun visit(addressOf: AddressOf) {
addressOf.identifier.accept(this)
addressOf.arrayIndex?.accept(this)
}
fun visit(inlineAssembly: InlineAssembly) {
+5 -5
View File
@@ -28,9 +28,9 @@ dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.github.hypfvieh:dbus-java:3.3.2"
implementation "org.slf4j:slf4j-simple:2.0.7"
implementation "org.slf4j:slf4j-simple:2.0.9"
}
configurations.all {
@@ -41,15 +41,15 @@ configurations.all {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
srcDir "${project.projectDir}/test"
}
}
}
+8 -7
View File
@@ -52,7 +52,7 @@ Language features
- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
It generates a machine code program runnable on actual 8-bit 6502 hardware.
- Fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
- Fast execution speed due to compilation to native assembly code. It's possible to write games purely in Prog8, and even certain raster interrupt 'demoscene' effects.
- Provides a very convenient edit/compile/run cycle by being able to directly launch
the compiled program in an emulator and provide debugging information to this emulator.
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
@@ -62,19 +62,19 @@ Language features
and inline assembly to have full control when every register, cycle or byte matters
- Subroutines with parameters and return values
- Complex nested expressions are possible
- Variables are allocated statically
- Variables are all allocated statically
- Conditional branches to map directly on processor branch instructions
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Several powerful built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Floating point math also supported on select compiler targets (C64, Cx16 and virtual).
- Floating point math is supported on select compiler targets.
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- High-level code optimizations, such as const-folding (zero-allocation constants that are optimized away in expressions), expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``abs``, ``sqrt``, ``msb``, ``min``, ``max``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.
- If you only use standard Kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compilation target flag)!
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 as defined on the Commander X16, also on the other machines.
- Support for low level system features such as Vera Fx hardware word multiplication on the Commander X16
- If you only use standard Kernal and core prog8 library routines, it is sometimes possible to compile the *exact same program* for different machines (just change the compilation target flag)
Code example
@@ -202,6 +202,7 @@ Look in the `syntax-files <https://github.com/irmen/prog8/tree/master/syntax-fil
targetsystem.rst
technical.rst
portingguide.rst
software.rst
todo.rst
+81 -10
View File
@@ -326,10 +326,9 @@ Bitmap graphics routines:
- drawing individual pixels
- drawing lines, rectangles, filled rectangles, circles, discs
This library is available both on the C64 and the Cx16.
This library is available both on the C64 and the cx16.
It uses the ROM based graphics routines on the latter, and it is a very small library because of that.
That also means though that it is constrained to 320*200 resolution on the Cx16 as well.
Use the ``gfx2`` library if you want full-screen graphics or non-monochrome drawing (only on Cx16). See below for that one.
On the cx16 there's also the ``gfx2`` library if you want full-screen graphics or non-monochrome drawing. See below for that one.
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/c64/graphics.p8>`_
to see what's in there. (Note: slight variations for different compiler targets)
@@ -343,6 +342,11 @@ Various 8-bit integer trig functions that use lookup tables to quickly calculate
Usually a custom lookup table is the way to go if your application needs these,
but perhaps the provided ones can be of service too.
``log2 (ubyte v)``
Returns the 2-Log of the byte value v.
``log2w (uword v)``
Returns the 2-Log of the word value v.
``rnd ()``
Returns next random byte 0-255 from the pseudo-RNG sequence.
@@ -431,18 +435,46 @@ On the other targets, it only contains the definition of the 16 memory mapped vi
restore the values of all 16 virtual registers r0 - r15 from the buffer. Might be useful in an IRQ handler to avoid clobbering them.
emudbg (cx16 only)
-------------------
X16Emu Emulator debug routines, for Cx16 only.
Allows you to interface with the emulator's debug routines/registers.
There's stuff like ``is_emulator`` to detect if running in the emulator,
and ``console_write`` to write a (iso) string to the emulator's console (stdout) etc.
gfx2 (cx16 only)
-----------------
Full-screen multicolor bitmap graphics routines, available on the Cx16 machine only.
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/emudbg.p8>`_
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>`_.
- multiple full-screen resolutions: 640 * 480 monochrome, and 320 * 240 monochrome and 256 colors
- clearing screen, switching screen mode, also back to text mode is possible.
monogfx (cx16 only)
--------------------
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.
- lores 320*240 or hires 640*480 bitmap mode, monochrome
- clearing screen, switching screen mode, also back to text mode
- drawing and reading individual pixels
- drawing lines, rectangles, filled rectangles, circles, discs
- flood fill
- drawing text inside the bitmap
- can draw using a stipple pattern (alternate black/white pixels)
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/monogfx.p8>`_
to see what's in there.
gfx2 (cx16 only)
-----------------
Full-screen multicolor bitmap graphics routines, available on the Cx16 machine only.
Same interface as monogfx, but for color screens. For 1 bpp monochrome screens, use monogfx.
- multiple full-screen bitmap color resolutions
- clearing screen, switching screen mode, also back to text mode
- drawing and reading individual pixels
- drawing lines, rectangles, filled rectangles, circles, discs
- flood fill
- drawing text inside the bitmap
- in monochrome mode, it's possible to use a stippled drawing pattern to simulate a shade of gray.
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/gfx2.p8>`_
to see what's in there.
@@ -467,5 +499,44 @@ It includes an interrupt routine to handle simple Attack/Release envelopes as we
See the examples/cx16/bdmusic.p8 program for ideas how to use it.
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/psg.p8>`_
to see what's in there. (Note: slight variations for different compiler targets)
to see what's in there.
sprites (cx16 only)
--------------------
Available for the Cx16 target. Simple routines to manipulate sprites.
They're not written for high performance, but for simplicity.
That's why they control one sprite at a time. The exception is the ``pos_batch`` routine,
which is quite efficient to update sprite positions of multiple sprites in one go.
See the examples/cx16/sprites/dragon.p8 and dragons.p8 programs for ideas how to use it.
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/sprites.p8>`_
to see what's in there.
verafx (cx16 only)
-------------------
Available for the Cx16 target.
Experimental routines that use the new Vera FX logic (hopefully coming in the Vera in new X16 boards,
the emulators already support it).
``available``
Returns true if Vera FX is available, false if not (that would be an older Vera chip)
``mult`` , ``muls``
The hardware 16*16 multiplier is exposed via ``mult`` and ``muls`` routines (unsigned and signed respectively).
They are about 4 to 5 times faster as the default 6502 cpu routine for word multiplication.
But they depend on some Vera manipulation and 4 bytes in vram just below the PSG registers for storage.
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.
``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.
``transparency``
Enable or disable transparent writes (color 0 will be transparent if enabled).
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/verafx.p8>`_
to see what's in there.
+7 -1
View File
@@ -909,6 +909,12 @@ ror2 (x)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror2(@($5000))``
setlsb (x, value)
Sets the least significant byte of word variable x to a new value. Leaves the MSB untouched.
setmsb (x, value)
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
sizeof (name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
@@ -931,7 +937,7 @@ memory (name, size, alignment)
You can only treat it as a pointer or use it in inline assembly.
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
Calls an assembly routine in another bank on the Commander X16 (using its ``jsrfar`` routine)
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
The argumentword will be loaded into the A+Y registers before calling the routine.
The uword value that the routine returns in the A+Y registers, will be returned.
+43
View File
@@ -0,0 +1,43 @@
=========================
Software written in Prog8
=========================
Apart from the many `examples <https://github.com/irmen/prog8/tree/master/examples>`_ available in the source code repository,
there are also larger pieces of software written using Prog8. Here's a list.
`Assembler <https://github.com/irmen/cx16assem>`_
File-based assembler for the Commander X16.
`Chess <https://github.com/irmen/cx16chess>`_
Chess game for the Commander X16, with 2-player or computer opponent game modes.
`Image viewer <https://github.com/irmen/cx16imageviewer>`_
Multi-format image viewer for the Commander X16.
Can display C64 Koala, BMP, PCX and Amiga IFF images, including color cycling.
`Petaxian <https://github.com/cyborgar/Petaxian>`_
Galaga type shoot em up game using only petscii graphics. Runs on C64 and Commander X16.
`Rock Runner <https://github.com/irmen/cx16rockrunner>`_
Faithful Boulderdash clone, a well known arcade puzzle game from the 80's.
where you must collect all diamonds in a level while avoiding the hazards to reach the exit.
Can load the thousands of available fan made level files.
This game is for the Commander X16.
`Shell <https://github.com/irmen/cx16shell>`_
Unix like command shell for the Commander X16.
`Streaming Music Demo <https://github.com/irmen/cx16musicdemo>`_
Demoscene like "music demos" for the Commander X16.
They display graphics, animated song lyrics, and play a high quality sampled song streamed from disk.
Various things:
`GalaX16 and other programs <https://github.com/reltham/X16Prog8Code>`_
Beginnings of a Galaga game for the Commander X16.
`Prog8 code for ZSMkit <https://github.com/mooinglemur/zsmkit/tree/main/p8demo>`_
ZSMkit is an advanced music and sound effects engine for the Commander X16.
+6 -2
View File
@@ -134,12 +134,14 @@ Directives
- ``force_output`` (in a block) will force the block to be outputted in the final program.
Can be useful to make sure some data is generated that would otherwise be discarded because the compiler thinks it's not referenced (such as sprite data)
- ``align_word`` (in a block) will make the assembler align the start address of this block on a word boundary in memory (so, an even memory address).
Warning: if you use this to align array variables in the block, these have to be initialized with a value to make them stay in the block and get aligned properly. Otherwise they'll end up at a random spot in the BSS section and the alignment doesn't apply there.
- ``align_page`` (in a block) will make the assembler align the start address of this block on a page boundary in memory (so, the LSB of the address is 0).
Warning: if you use this to align array variables in the block, these have to be initialized with a value to make them stay in the block and get aligned properly. Otherwise they'll end up at a random spot in the BSS section and the alignment doesn't apply there.
- ``merge`` (in a block) will merge this block's contents into an already existing block with the same name. Useful in library scenarios.
- ``splitarrays`` (block or module) makes all word-arrays in this scope lsb/msb split arrays (as if they all have the @split tag). See Arrays.
- ``no_symbol_prefixing`` (block) makes the compiler *not* use symbol-prefixing when translating prog8 code into assembly.
Only use this if you know what you're doing because it could result in invalid assembly code being generated.
- ``verafxmuls`` (block, cx16 target only) uses Vera FX hardware word multiplication on the CommanderX16 for all word multiplications in this block. Warning: this may interfere with IRQs and other Vera operations, so use this only when you know what you're doing. It's safer to explicitly use ``verafx.muls()``.
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
@@ -450,7 +452,8 @@ from the starting value to (and including) the ending value::
<start> downto <end> [ step <step> ]
You an provide a step value if you need something else than the default increment which is one (or,
in case of downto, a decrement of one). Because a step of minus one is so common you can just use
in case of downto, a decrement of one). Unlike the start and end values, the step value must be a constant.
Because a step of minus one is so common you can just use
the downto variant to avoid having to specify the step as well::
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7
@@ -729,6 +732,7 @@ for loop
The loop variable must be a byte or word variable, and it must be defined separately first.
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
Remember that a step value in a range must be a constant value.
You can use a single statement, or a statement block like in the example below::
+3
View File
@@ -18,6 +18,9 @@ it won't actually increase the size of the resulting program binary.
Prog8 takes care of properly filling this memory area with zeros at program startup and then reinitializes
the subset of variables that have a nonzero initialization value.
Arrays with initialization values are not put into BSS but just occupy a sequence of bytes in
the program memory: their values are not reinitialized at program start.
It is possible to relocate the BSS section using a compiler option
so that more system ram is available for the program code itself.
+14 -6
View File
@@ -1,11 +1,8 @@
TODO
====
- prefix prog8 subroutines with p8s_ instead of p8_ to not let them clash with variables in the asm?
- allow 'chained' array indexing for expressions: value = ptrarray[0][0]
- allow 'chained' array indexing for assign targets: ptrarray[0][0] = 42 this is just evaluating the lhs as a uword pointer expression
- [on branch: shortcircuit] investigate McCarthy evaluation again? this may also reduce code size perhaps for things like if a>4 or a<2 ....
- IR: reduce the number of branch instructions such as BEQ, BEQR, etc (gradually), replace with CMP(I) + status branch instruction
- [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!
...
@@ -22,6 +19,10 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- Currently "320*240/8/8" gives integer overflow, so: allow constant integer subexpressions to contain out of range integers (>65535 etc) as long as the final constant value is within byte/word range.
- allow 'chained' array indexing for expressions: value = ptrarray[0][0]
- allow 'chained' array indexing for assign targets: ptrarray[0][0] = 42 this is just evaluating the lhs as a uword pointer expression
- [much work:] more support for (64tass) SEGMENTS ?
- (What, how, isn't current BSS support enough?)
- Add a mechanism to allocate variables into golden ram (or segments really) (see GoldenRam class)
@@ -32,12 +33,15 @@ Compiler:
- OR.... make all this more generic and use some %segment option to create real segments for 64tass?
- (need separate step in codegen and IR to write the "golden" variables)
- do we need (array)variable alignment tag instead of block alignment tag? You want to align the data, not the code in the block?
- ir: block alignment doesn't translate well to variables in the block (the actual stuff that needs to be aligned in memory) but: need variable alignment tag instead of block alignment tag, really
- ir: idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
global initialization values are simply a list of LOAD instructions.
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.
- ir: add more optimizations in IRPeepholeOptimizer
- ir: the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
- ir: for expressions with array indexes that occur multiple times, can we avoid loading them into new virtualregs everytime and just reuse a single virtualreg as indexer? (simple form of common subexpression elimination)
- ir: the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
maybe another representation is needed once actual codegeneration is done from the IR...?
- PtAst/IR: more complex common subexpression eliminations
- [problematic due to using 64tass:] better support for building library programs, where unused .proc shouldn't be deleted from the assembly?
Perhaps replace all uses of .proc/.pend/.endproc by .block/.bend will fix that with a compiler flag?
@@ -49,14 +53,18 @@ Compiler:
Libraries:
- gfx2 cs_innerloop640() extend number of bytes cleared, and use vera fx cached writes
- 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)
- 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)?
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
Optimizations:
- treat every scalar variable decl with initialization value, as const by default, unless the variable gets assigned to somewhere (or has its address taken, or is @shared)
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers, then the rest
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code,
+7 -6
View File
@@ -1,11 +1,12 @@
%import textio
%import floats
main {
sub crc16(uword data, uword length) -> uword {
uword crc = 0
repeat length {
crc ^= @(data) << $0008
crc ^= mkword(@(data), 0)
repeat 8 {
if crc & $8000
crc = (crc<<1)^$1021
@@ -18,13 +19,13 @@ main {
}
sub start() {
txt.print("calculating...")
txt.print("calculating (expecting $ffd0)...")
cbm.SETTIM(0,0,0)
uword crc = crc16($e000, $2000)
txt.print_uwhex(crc, true) ; should be $ffd0
txt.print_uwhex(crc, true)
txt.nl()
txt.print_uw(cbm.RDTIM16())
txt.print(" jiffies")
sys.wait(100)
floats.print_f(cbm.RDTIM16() / 60.0)
txt.print(" seconds")
sys.wait(9999)
}
}
+6 -5
View File
@@ -1,4 +1,5 @@
%import textio
%import floats
main {
sub crc32(uword data, uword length) {
@@ -7,7 +8,7 @@ main {
cx16.r0 = 0
cx16.r1 = 0
repeat length {
cx16.r0 ^= @(data) << $0008
cx16.r0 ^= mkword(@(data), 0)
repeat 8 {
if cx16.r0 & $8000 {
sys.clear_carry()
@@ -29,14 +30,14 @@ main {
}
sub start() {
txt.print("calculating...")
txt.print("calculating (expecting $e1fa84c6)...\n")
cbm.SETTIM(0,0,0)
crc32($e000, $2000)
txt.print_uwhex(cx16.r0, true)
txt.print_uwhex(cx16.r1, false)
txt.nl()
txt.print_uw(cbm.RDTIM16())
txt.print(" jiffies")
sys.wait(100)
floats.print_f(cbm.RDTIM16() / 60.0)
txt.print(" seconds")
sys.wait(9999)
}
}
+6 -5
View File
@@ -1,4 +1,5 @@
%import textio
%import floats
main {
@@ -18,13 +19,13 @@ main {
}
sub start() {
txt.print("calculating...")
txt.print("calculating (expecting $a2)...")
cbm.SETTIM(0,0,0)
ubyte crc = crc8($e000, $2000)
txt.print_ubhex(crc, true) ; should be $a2
txt.print_ubhex(crc, true)
txt.nl()
txt.print_uw(cbm.RDTIM16())
txt.print(" jiffies")
sys.wait(100)
floats.print_f(cbm.RDTIM16() / 60.0)
txt.print(" seconds")
sys.wait(9999)
}
}
+5 -5
View File
@@ -18,10 +18,10 @@ main {
}
sub start() {
txt.print("calculating...")
txt.print("calculating (expecting 3.614007e+12)...\n")
cbm.SETTIM(0,0,0)
float res
float res=0.0
uword i
ubyte j
for i in 0 to N_ITER-1 {
@@ -35,8 +35,8 @@ main {
floats.print_f(res)
txt.nl()
txt.print_uw(cbm.RDTIM16())
txt.print(" jiffies")
sys.wait(100)
floats.print_f(cbm.RDTIM16() / 60.0)
txt.print(" seconds")
sys.wait(9999)
}
}
+64
View File
@@ -0,0 +1,64 @@
%import textio
%import floats
main {
const ubyte N_ITER = 4
const uword SIZE = 16000
uword @zp flags_ptr = memory("flags", SIZE/8+1, $100)
ubyte[] bitv = [ $01, $02, $04, $08, $10, $20, $40, $80 ]
sub start() {
txt.print_ub(N_ITER)
txt.print(" iterations, calculating... (expecting 3431)\n")
cbm.SETTIM(0, 0, 0)
uword prime_count
repeat N_ITER {
prime_count = sieve()
}
txt.print_uw(prime_count)
txt.print(" primes\n")
float time = cbm.RDTIM16() as float / 60.0
floats.print_f(time)
txt.print(" sec total = ")
floats.print_f(time/N_ITER)
txt.print(" sec per iteration\n")
sys.wait(9999)
}
sub check_flag(uword idx) -> ubyte
{
return flags_ptr[idx/8] & bitv[lsb(idx)&7]
}
sub clear_flag(uword idx)
{
flags_ptr[idx/8] &= ~bitv[lsb(idx)&7]
}
sub sieve() -> uword {
uword prime
uword k
uword count=0
uword i
sys.memset(flags_ptr, SIZE/8+1, $ff)
for i in 0 to SIZE-1 {
if check_flag(i) {
prime = i*2 + 3
k = i + prime
while k < SIZE {
clear_flag(k)
k += prime
}
; txt.print_uw(prime)
; txt.spc()
count++
}
}
return count
}
}
@@ -1,7 +1,5 @@
%import textio
%import floats
%zeropage basicsafe
%option no_sysinit
; The "Byte Sieve" test. https://en.wikipedia.org/wiki/Byte_Sieve
; Note: this program can be compiled for multiple target systems.
@@ -9,7 +7,6 @@
main {
sub start() {
cbm.SETTIM(0, 0, 0)
const ubyte ITERS = 10
uword count
@@ -22,15 +19,17 @@ main {
txt.print_ub(ITERS)
txt.print(" iterations, calculating...\n")
cbm.SETTIM(0, 0, 0)
repeat ITERS {
sys.memset(flags_ptr, SIZEPL, 1)
count = 0
for i in 0 to SIZEPL-1 {
if @(flags_ptr+i) {
prime = i + i + 3
if flags_ptr[i] {
prime = i*2 + 3
k = i + prime
while k <= SIZEPL-1 {
@(flags_ptr + k) = false
while k < SIZEPL {
flags_ptr[k] = false
k += prime
}
; txt.print_uw(prime)
@@ -48,5 +47,6 @@ main {
txt.print(" sec total = ")
floats.print_f(time/ITERS)
txt.print(" sec per iteration\n")
sys.wait(9999)
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ main {
ubyte perform_scroll = false
sub start() {
c64.SPRPTR[0] = $0f00 / 64
c64.set_sprite_ptr(0, $0f00) ; alternatively, set directly: c64.SPRPTR[0] = $0f00 / 64
c64.SPENA = 1
c64.SP0COL = 14
c64.SPXY[0] = 80
-3
View File
@@ -13,10 +13,7 @@ main {
charset.make_custom_charset()
; activate the new charset in RAM
ubyte block = c64.CIA2PRA
const ubyte PAGE1 = ((cbm.Screen >> 6) & $F0) | ((charset.CHARSET >> 10) & $0E)
c64.CIA2PRA = (block & $FC) | (lsb(cbm.Screen >> 14) ^ $03)
c64.VMCSB = PAGE1
txt.print("\n @ @ @ @\n")
+1
View File
@@ -171,6 +171,7 @@ main {
c64.SPXYW[i] = mkword(sy, sx)
; because we want speed, we don't use the dynamic c64.set_sprite_ptr() here
if(zc < 30*128)
c64.SPRPTR[i] = $2000/64 +1 ; large ball
else
+43 -34
View File
@@ -1,5 +1,4 @@
%import syslib
%import test_stack
%import textio
%import math
@@ -7,8 +6,9 @@
; which is (w)2001 by groepaz/hitmen
;
; Cleanup and porting to C by Ullrich von Bassewitz.
; Converted to prog8 by Irmen de Jong.
; See https://github.com/cc65/cc65/tree/master/samples/cbm/plasma.c
;
; Optimized and Converted to prog8 by Irmen de Jong.
main {
@@ -22,27 +22,39 @@ main {
sub start() {
txt.color(1)
txt.clear_screen()
txt.print("creating charset...\n")
txt.print("creating charset...\n\nwhile running, press key to stop.\n\n")
makechar()
ubyte block = c64.CIA2PRA
; ubyte v = cbm.VMCSB
ubyte v = c64.VMCSB
c64.CIA2PRA = (block & $FC) | (lsb(SCREEN1 >> 14) ^ $03)
repeat {
uword frames = 0
cbm.SETTIM(0,0,0)
while cbm.GETIN()==0 {
doplasma(SCREEN1)
c64.VMCSB = PAGE1
doplasma(SCREEN2)
c64.VMCSB = PAGE2
frames += 2
}
; restore screen (if you want)
;c64.VMCSB = v
;c64.CIA2PRA = block
;txt.print("done!\n")
;test_stack.test()
;repeat {
;}
uword jiffies = cbm.RDTIM16()
; restore screen and displays stats
c64.VMCSB = v
c64.CIA2PRA = block
txt.print("time in jiffies: ")
txt.print_uw(jiffies)
txt.print("\nframes: ")
txt.print_uw(frames)
uword fps = (frames*60)/jiffies
txt.print("\nfps: ")
txt.print_uw(fps)
txt.print("\ndone!\n")
repeat {
}
}
; several variables outside of doplasma to make them retain their value
@@ -51,7 +63,7 @@ main {
ubyte c2A
ubyte c2B
sub doplasma(uword screen) {
sub doplasma(uword @zp screen) {
ubyte[40] xbuf
ubyte[25] ybuf
ubyte c1a = c1A
@@ -61,14 +73,14 @@ main {
ubyte @zp x
ubyte @zp y
for y in 24 downto 0 {
for y in 0 to 24 {
ybuf[y] = math.sin8u(c1a) + math.sin8u(c1b)
c1a += 4
c1b += 9
}
c1A += 3
c1B -= 5
for x in 39 downto 0 {
for x in 0 to 39 {
xbuf[x] = math.sin8u(c2a) + math.sin8u(c2b)
c2a += 3
c2b += 7
@@ -76,20 +88,18 @@ main {
c2A += 2
c2B -= 3
for y in 24 downto 0 {
for x in 39 downto 0 {
; using a temp var here to enable expression optimization that can't be done on a 'problematic' ROM/RAM memory location
ubyte @zp cc = xbuf[x] + ybuf[y]
@(screen+x) = cc
; this is the fastest way to do this inner part:
; %asm {{
; ldy y
; lda ybuf,y
; ldy x
; clc
; adc xbuf,y
; sta (screen),y
; }}
for y in 0 to 24 {
ubyte @zp @shared yvalue = ybuf[y]
for x in 0 to 39 {
; @(screen+x) = xbuf[x] + yvalue
; max optimized asm is this: (achieving ~21 fps on the C64):
%asm {{
lda p8_yvalue
ldy p8_x
clc
adc p8_xbuf,y
sta (p8_screen),y
}}
}
screen += 40
}
@@ -99,16 +109,15 @@ main {
ubyte[8] bittab = [ $01, $02, $04, $08, $10, $20, $40, $80 ]
ubyte c
for c in 0 to 255 {
ubyte @zp s = math.sin8u(c)
ubyte @zp s = math.sin8u(c) ; chance
ubyte i
; for all the pixels in the 8x8 character grid, determine (with a rnd chance) if they should be on or off
for i in 0 to 7 {
ubyte b=0
ubyte @zp ii
for ii in 0 to 7 {
; use 16 bit rng for a bit more randomness instead of the 8-bit rng
if math.rnd() > s {
if math.rnd() > s
b |= bittab[ii]
}
}
@(CHARSET + i + c*$0008) = b
}
+1 -1
View File
@@ -40,7 +40,7 @@ main {
ubyte @zp i
for i in 0 to 7 {
c64.SPRPTR[i] = $0a00 / 64
c64.set_sprite_ptr(i, $0a00) ; alternatively, set directly: c64.SPRPTR[i] = $0a00 / 64
c64.SPXY[i*2] = 50+25*i
c64.SPXY[i*2+1] = math.rnd()
}
+31 -36
View File
@@ -1,6 +1,5 @@
%import floats
%import graphics
%import test_stack
%zeropage floatsafe
@@ -9,9 +8,10 @@ main {
sub start() {
graphics.enable_bitmap_mode()
turtle.init()
; turtle.pu()
; turtle.pos(150, 110)
; turtle.pd()
turtle.pu()
turtle.pos(150, 110)
turtle.pd()
ubyte i
for i in 0 to 100 {
@@ -19,8 +19,6 @@ main {
turtle.rt(94)
}
; test_stack.test()
repeat {
}
}
@@ -32,15 +30,18 @@ turtle {
float angle
bool pendown
const uword SPRITE_MEMORY = $5800
sub init() {
xpos = 160.0
ypos = 100.0
angle = 0.0
pendown = true
c64.SPRPTR[0] = $0d00 / 64
c64.SPENA = 1
c64.SP0COL = 5
sys.memcopy(&turtlesprite, SPRITE_MEMORY, len(turtlesprite)) ; copy the sprite pixel data
c64.set_sprite_ptr(0, SPRITE_MEMORY) ; use dynamic setter because of changed vic memory layout
c64.SPENA = 1 ; sprites on
c64.SP0COL = 5 ; green sprite
update_turtle_sprite()
}
@@ -88,32 +89,26 @@ turtle {
sub pd() {
pendown = true
}
}
spritedata $0d00 {
; this memory block contains the sprite data
; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program
ubyte[] balloonsprite = [ %00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%01111110,%00000000,
%00000000,%11000011,%00000000,
%00000000,%11000011,%00000000,
%00000000,%11000011,%00000000,
%00000000,%01111110,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000 ]
ubyte[] turtlesprite = [ %00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%01111110,%00000000,
%00000000,%11000011,%00000000,
%00000000,%11000011,%00000000,
%00000000,%11000011,%00000000,
%00000000,%01111110,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000,
%00000000,%00000000,%00000000 ]
}
+1 -1
View File
@@ -35,7 +35,7 @@ main {
sub start() {
ubyte i
for i in 0 to 7 {
c64.SPRPTR[i] = $0a00/64
c64.set_sprite_ptr(i, $0a00) ; alternatively, set directly: c64.SPRPTR[i] = $0a00 / 64
}
c64.SPENA = 255 ; enable all sprites
sys.set_rasterirq(&irq.irqhandler, 230, true) ; enable animation
+1 -1
View File
@@ -8,7 +8,7 @@
main {
sub start() {
gfx2.screen_mode(6) ; select 640*480 mode, 4 colors
gfx2.screen_mode(2) ; select 640*480 mode, 4 colors
mouse.set_pointer_image()
cx16.mouse_config(-1, 640/8, 240/8)
uword[4] amigacolors = [$aaa, $000, $fff, $68c] ; gray, black, white, lightblue
+4 -4
View File
@@ -5,7 +5,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.
main {
sub start() {
@@ -104,7 +104,7 @@ main {
bitshift(lsb(blit_x) & 7)
; left column of the (shifted)sprite
; left column of the (shifted) "sprite" (bob)
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_L = lsb(vmem)
cx16.VERA_ADDR_M = msb(vmem)
@@ -117,7 +117,7 @@ main {
for ix in 0 to len(shifted_sprite)-1 step 3
cx16.VERA_DATA1 = cx16.VERA_DATA0 & shifted_mask[ix] | shifted_sprite[ix]
; middle column of the (shifted)sprite
; middle column of the (shifted) "sprite" (bob)
vmem++
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_L = lsb(vmem)
@@ -128,7 +128,7 @@ main {
for ix in 1 to len(shifted_sprite)-1 step 3
cx16.VERA_DATA1 = cx16.VERA_DATA0 & shifted_mask[ix] | shifted_sprite[ix]
; right column of the (shifted)sprite
; right column of the (shifted) "sprite" (bob)
vmem++
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_L = lsb(vmem)
+5
View File
@@ -0,0 +1,5 @@
testdata/*TITLESCREEN.*
*.mcf
*.asm
*.prg
*.vice-mon-list
Loaded 100 of 147 files, more files were not shown because too many files have changed in this diff. Show more