Compare commits

..
102 Commits
Author SHA1 Message Date
Irmen de Jong b19c282269 release 9.2 2023-07-28 01:40:14 +02:00
Irmen de Jong e520921746 todo 2023-07-26 23:16:43 +02:00
Irmen de Jong 970642244b optimized gfx2.text() for hires 4c mode 2023-07-26 04:17:44 +02:00
Irmen de Jong 3b90be2d9e gfx2.text() per-pixel positioning implemented for screen modes 1 and 5 2023-07-25 00:43:45 +02:00
Irmen de Jong 2f756f1e3a fix and optimize inplace invert and negate 2023-07-24 23:28:32 +02:00
Irmen de Jong 78e84182f0 todo 2023-07-24 22:36:17 +02:00
Irmen de Jong 65a7a8caf8 fix and optimize gfx2.position2(), added cx16.vaddr_clone() 2023-07-24 00:04:47 +02:00
Irmen de Jong 4c6a2f5df9 emphasize index value size on pointer var indexing 2023-07-23 00:11:18 +02:00
Irmen de Jong 3bbc00cc8c more caution notices about symbols in inlined asm 2023-07-22 23:22:06 +02:00
Irmen de Jong 0e781d18fa cx16: added cx16.vaddr_autoincr() and cx16.vaddr_autodecr() 2023-07-21 23:04:21 +02:00
Irmen de Jong 4575a8fffe cx16: added cx16.vaddr_autoincr() and cx16.vaddr_autodecr() 2023-07-21 22:40:07 +02:00
Irmen de Jong 10d0ff252b ignore buildversion changes 2023-07-21 00:14:06 +02:00
Irmen de Jong c7d54570cc IR: sXX, CONCAT instructions now use 3 register format 2023-07-21 00:07:56 +02:00
Irmen de Jong 7136b33f2e cx16: change reset_system() to use Reset SMC sequence instead of hard reboot 2023-07-20 01:59:20 +02:00
Irmen de Jong 5659742d97 fixed assigning byte to word not clearing msb sometimes 2023-07-16 23:16:32 +02:00
Irmen de Jong 450eaf7c4a fixed lsb() to uword problem 2023-07-16 20:05:59 +02:00
Irmen de Jong c1aa5d4e47 IR: optimized when statement translation 2023-07-16 12:10:46 +02:00
Irmen de Jong b3cb9b7fe2 added optimizer to remove needless pha/pla pairs 2023-07-15 22:19:48 +02:00
Irmen de Jong 9cb61fa34d tweaks 2023-07-15 20:46:14 +02:00
Irmen de Jong 7c219d235c fixed possible type mismatch error in when statements 2023-07-14 23:35:58 +02:00
Irmen de Jong 6938c79f88 IR: added CMPI instruction 2023-07-14 23:17:29 +02:00
Irmen de Jong b8284a147d allow boolean when conditions, optimize into a regular if 2023-07-11 21:33:29 +02:00
Irmen de Jong 15ee90e99c no error about missing target when -vm is used.
also version 9.1
2023-07-11 18:13:49 +02:00
Irmen de Jong 795f80b4ec fix forloop 6502 codegen in case of descending word values 2023-07-11 00:33:12 +02:00
Irmen de Jong 6b6427492d fix forloop 6502 codegen in case of descending word values 2023-07-10 23:10:16 +02:00
Irmen de Jong 6055b8c3dc IR: fix forloop codegen for steps != 1 2023-07-10 21:36:44 +02:00
Irmen de Jong a98cb50d55 Revert "ir: SCC now sets all bits to 1 (or 0)"
This reverts commit 7245aece4f.
2023-07-09 23:16:13 +02:00
Irmen de Jong e98bbc1c52 todo 2023-07-09 22:29:54 +02:00
Irmen de Jong 7245aece4f ir: SCC now sets all bits to 1 (or 0) 2023-07-08 23:16:01 +02:00
Irmen de Jong 60cbb02822 vm: actually fix EXT(S) in vm too 2023-07-08 23:05:03 +02:00
Irmen de Jong 4e863ecdac vm: fixed abs() and word-to-string conversion 2023-07-08 22:57:16 +02:00
Irmen de Jong 5037033fcf ir: EXT and EXTS opcodes now have 2 registers to avoid type clash 2023-07-08 22:42:11 +02:00
Irmen de Jong e6b158bc97 @(..) argument must be of type UWORD 2023-07-08 22:34:47 +02:00
Irmen de Jong 4cc0dfa10b comment 2023-07-08 11:42:29 +02:00
Irmen de Jong 4ced8889d3 cx16: fix signature return values of cx16.screen_mode(), add get_screen_mode() and set_screen_mode() convenience routines 2023-07-08 11:37:29 +02:00
Irmen de Jong d26967a87d ir doc 2023-07-07 22:35:05 +02:00
Irmen de Jong fc8955941b slight optimization for certain word multiplications 2023-07-07 21:30:37 +02:00
Irmen de Jong 071a80360f ir: fix some problem with comparison against zero 2023-07-07 21:17:28 +02:00
Irmen de Jong d2154f5f2e remove empty when choices, fixes ir compilation error on those 2023-07-07 20:34:24 +02:00
Irmen de Jong 334d382bfa ir: JUMPI instruction added to support indirect jumps 2023-07-07 19:10:39 +02:00
Irmen de Jong 90c4b00f74 ir: fix any() all() reverse() sort() on memory mapped arrays and on byte arrays 2023-07-07 17:25:32 +02:00
Irmen de Jong 71261525e8 fix containment check on memory mapped arrays 2023-07-07 17:07:34 +02:00
Irmen de Jong 3126959576 ir: several fixes 2023-07-07 16:53:32 +02:00
Irmen de Jong 02e51d8282 ir: fix initial chunk linking 2023-07-07 00:30:56 +02:00
Irmen de Jong ffb2027a19 repeat loop count now always rounded to integer 2023-07-06 23:58:02 +02:00
Irmen de Jong 70c9ab9074 upgrade libraries 2023-07-06 23:33:58 +02:00
Irmen de Jong 6d1fdf1ba6 upgrade to Kotlin 1.9.0 2023-07-06 23:03:47 +02:00
Irmen de Jong 1f7180d9a8 math.multiply_words returns lower 16 bits of the result also in AY (to avoid repeating some load instructions) 2023-07-06 22:54:13 +02:00
Irmen de Jong b4e94ae4dd optimizer: avoid symbol name clash when inlining subroutine 2023-07-05 23:15:04 +02:00
Irmen de Jong 07c606bfc9 optimizer: don't replace for loop with repeat loop (the loop variable might be used elsewhere!) 2023-07-05 21:16:17 +02:00
Irmen de Jong e705a8bd89 discord info 2023-07-04 23:50:16 +02:00
Irmen de Jong b3bdfb7f1f more info about building the compiler 2023-07-04 22:41:38 +02:00
Irmen de Jong 5af1aeb092 added block comment /* ...... */ 2023-07-04 00:46:29 +02:00
Irmen de Jong be64fa674a doc 2023-07-03 22:44:50 +02:00
Irmen de Jong 204f5591a9 todos 2023-07-03 21:57:32 +02:00
Irmen de Jong ee3e3a3a40 optimize text rendering in gfx2 2023-07-03 21:45:09 +02:00
Irmen de Jong f9200a2b75 fix IR loader for romsub calls (calls to an address) 2023-07-02 23:41:15 +02:00
Irmen de Jong f570b70827 fix type error with returning an array from a subroutine returning uword 2023-07-02 22:09:19 +02:00
Irmen de Jong 0db141eeac todo 2023-07-02 21:19:33 +02:00
Irmen de Jong acb2ee53bb Merge branch 'prefixing' 2023-07-02 21:15:30 +02:00
Irmen de Jong c544b7f5ba fixing up p8_ prefixing 2023-07-02 21:15:05 +02:00
Irmen de Jong c0024e97e5 fix doc version 2023-07-02 21:01:11 +02:00
Irmen de Jong bdf8aa9168 get rid of newexpr compiler option 2023-07-02 15:26:04 +02:00
Irmen de Jong de5ce0f515 tiny optimization and doc 2023-07-02 11:17:18 +02:00
Irmen de Jong bb95484c8a uniform symbol prefixing with p8_ 2023-07-02 06:15:09 +02:00
Irmen de Jong cad18b8a3a uniform symbol prefixing with p8_ 2023-07-02 06:15:02 +02:00
Irmen de Jong 0f6a98751a tiny optimization 2023-07-02 06:13:22 +02:00
Irmen de Jong aac5a4c27f optimize word repeat loop codegen 2023-07-02 04:51:22 +02:00
Irmen de Jong d3f6415387 vm: fix repeat 256 2023-07-02 02:38:35 +02:00
Irmen de Jong 04da44eb98 fix certain inefficient codegen when assigning a type casted value 2023-06-29 22:56:26 +02:00
Irmen de Jong 7649be97b1 add git hash to compiler header output 2023-06-29 21:01:02 +02:00
Irmen de Jong c9ef777e0f fix rest of possible temp variable conflicts 2023-06-28 23:24:48 +02:00
Irmen de Jong c0cb2438d5 1-letter symbols now also prefixed with 'p8p_'
to avoid assembly errors caused by confusing variable 'a' with register 'a' etc.
2023-06-28 23:17:59 +02:00
Irmen de Jong 30c531b39e attempting to fix array expression inplace assign 2023-06-28 00:38:08 +02:00
Irmen de Jong bf703a8a66 unittest 2023-06-27 23:43:35 +02:00
Irmen de Jong e7b631b087 allow comment lines inside array initializer value 2023-06-27 23:30:37 +02:00
Irmen de Jong a9f5dc036c fix cpu stack corruption in array assignment codegen 2023-06-27 18:49:49 +02:00
Irmen de Jong 0a83b51e00 allow more curly brace styles 2023-06-27 01:59:22 +02:00
Irmen de Jong eab63ecc6c allow curly brace on next line also after subroutine and when 2023-06-27 01:29:25 +02:00
Irmen de Jong b0794cf35e added hiram bank number to -varshigh 2023-06-27 00:27:34 +02:00
Irmen de Jong 5b9e71a27d docs 2023-06-25 21:35:30 +02:00
Irmen de Jong eae41de27d improve errors generated for undefined symbols 2023-06-25 15:19:51 +02:00
Irmen de Jong e9163aa3a7 added cx16.save_virtual_registers() and cx16.restore_virtual_registers() 2023-06-24 21:04:47 +02:00
Irmen de Jong 8c617515ba don't prefix 3-letter symbols too aggressively (could cause some compilation errors) 2023-06-23 23:36:59 +02:00
Irmen de Jong 04e4e71f2e uword == str is now possible (sugar for string.compare) 2023-06-22 00:20:30 +02:00
Irmen de Jong a587482edf optimize dangling else 2023-06-18 13:46:02 +02:00
Irmen de Jong 0aac9350d5 rename math.atan() to math.atan2() 2023-06-18 13:05:36 +02:00
Irmen de Jong f56c12ee4e cx16 spotlight example 2023-06-18 12:49:22 +02:00
Irmen de Jong 4bb9ae61f2 library source links 2023-06-18 02:31:45 +02:00
Irmen de Jong ff7f3484e4 atan 2023-06-17 23:01:47 +02:00
Irmen de Jong 5da3abe6b4 fix silent typecast on return statements that could lose data (word->byte) 2023-06-17 14:44:36 +02:00
Irmen de Jong c0b398e0ce add various math.atan() routines 2023-06-17 00:43:33 +02:00
Irmen de Jong 3de10adac2 bump required 64tass version 2023-06-16 23:24:31 +02:00
Irmen de Jong 1b573d6552 add note about lacking fp parse routine 2023-06-16 00:12:52 +02:00
Irmen de Jong 2a96f93919 vm: fix compiler error when dealing with label 2023-06-14 22:14:47 +02:00
Irmen de Jong c6b2639ca4 fix compiler crash due to missing 6502 codegen
(assigning a direct memory read byte to a cx16 virtual register)
2023-06-14 21:10:01 +02:00
Irmen de Jong b9abf37a7e fix invalid code when subroutines are defined in a repeat loop 2023-06-13 00:46:32 +02:00
Irmen de Jong 373cbb4144 gradle build error explained 2023-06-11 17:51:18 +02:00
Irmen de Jong a521982576 fix subroutine inline problem with strings 2023-06-09 21:45:05 +02:00
Irmen de Jong a77fde577c update GitHub action steps 2023-06-09 19:51:04 +02:00
Irmen de Jong ea6926e57d fix float expression crash: fl = abs/sqrt (fl)+0.5 2023-06-09 19:28:34 +02:00
Irmen de Jong ba25b7fee6 fix diskio.diskname(). cx16: add diskio.curdir() 2023-06-07 22:38:51 +02:00
146 changed files with 3281 additions and 1503 deletions

No files matched your search

+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: build and install recent 64tass
run: |
@@ -21,7 +21,7 @@ jobs:
sudo make install
- name: Set up JDK 11
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
java-version: 11
distribution: adopt
+3
View File
@@ -15,6 +15,7 @@ out/
parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
compiler/src/prog8/buildversion/*
*.py[cod]
*.egg
*.egg-info
@@ -29,6 +30,8 @@ parsetab.py
compiler/lib/
.gradle
**/BuildVersion.kt
/prog8compiler.jar
sd*.img
*.d64
+1 -1
View File
@@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.21-release-380" />
<option name="version" value="1.9.0-release-358" />
</component>
</project>
+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.0">
<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.0/antlr4-4.13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.0/antlr4-runtime-4.13.0.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="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.17" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.17/kotlin-result-jvm-1.1.17.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.18/kotlin-result-jvm-1.1.18.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
+15
View File
@@ -14,6 +14,21 @@ Documentation
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
https://prog8.readthedocs.io/
How to get it/build it
----------------------
- Download the latest [official release](https://github.com/irmen/prog8/releases) from github.
- Or, if you want/need a bleeding edge development version, you can:
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
Community
---------
Most of the development on Prog8 and the use of it is currently centered around
the [Commander X16](https://www.commanderx16.com/) retro computer. Their [discord server](https://discord.gg/nS2PqEC) contains a small channel
dedicated to Prog8. Other than that, use the issue tracker on github.
Software license
----------------
GNU GPL 3.0 (see file LICENSE), with exception for generated code:
+1 -1
View File
@@ -26,7 +26,7 @@ compileTestKotlin {
dependencies {
// should have no dependencies to other modules
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.17"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
}
sourceSets {
+10
View File
@@ -80,6 +80,16 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
}
override fun lookup(scopedName: String) = flat[scopedName]
fun getLength(name: String): Int? {
val node = flat[name]
return when(node) {
is StMemVar -> node.length
is StMemorySlab -> node.size.toInt()
is StStaticVariable -> node.length
else -> null
}
}
}
@@ -94,6 +94,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
initialString = null
numElements = node.arraySize?.toInt()
}
// if(node.type in SplitWordArrayTypes) {
// ... split array also add _lsb and _msb to symboltable?
// }
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
}
is PtBuiltinFunctionCall -> {
+14 -10
View File
@@ -37,16 +37,17 @@ class PtNodeGroup : PtNode(Position.DUMMY)
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
// Note that as an exception, the 'name' is not read-only
// but a var. This is to allow for cheap node renames.
val scopedName: String by lazy {
var namedParent: PtNode = this.parent
if(namedParent is PtProgram)
name
else {
while (namedParent !is PtNamedNode)
namedParent = namedParent.parent
namedParent.scopedName + "." + name
val scopedName: String
get() {
var namedParent: PtNode = this.parent
return if(namedParent is PtProgram)
name
else {
while (namedParent !is PtNamedNode)
namedParent = namedParent.parent
namedParent.scopedName + "." + name
}
}
}
}
@@ -63,7 +64,9 @@ class PtProgram(
children.asSequence().filterIsInstance<PtBlock>()
fun entrypoint(): PtSub? =
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start") } as PtSub?
allBlocks().firstOrNull { it.name == "main" || it.name=="p8_main" }
?.children
?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start" || it.name=="p8_start" || it.name=="p8_main.p8_start") } as PtSub?
}
@@ -71,6 +74,7 @@ class PtBlock(name: String,
val address: UInt?,
val library: Boolean,
val forceOutput: Boolean,
val noSymbolPrefixing: Boolean,
val alignment: BlockAlignment,
val source: SourceCode, // taken from the module the block is defined in.
position: Position
@@ -85,7 +85,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"abs__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
// normal functions follow:
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
"sizeof" to FSignature(true, listOf(FParam("object", DataType.entries.toTypedArray())), DataType.UBYTE),
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
"sqrt" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
@@ -19,8 +19,7 @@ class CompilationOptions(val output: OutputType,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHigh: Boolean = false,
var useNewExprCode: Boolean = false,
var varsHighBank: Int? = null,
var splitWordArrays: Boolean = false,
var evalStackBaseAddress: UInt? = null,
var outputDir: Path = Path(""),
@@ -3,6 +3,7 @@ package prog8.code.core
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun undefined(symbol: List<String>, position: Position)
fun noErrors(): Boolean
fun report()
fun finalizeNumErrors(numErrors: Int, numWarnings: Int) {
+1 -1
View File
@@ -28,7 +28,7 @@ dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.17"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
}
@@ -3,6 +3,7 @@ package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.code.StNodeType
import prog8.code.SymbolTable
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
@@ -14,18 +15,180 @@ import kotlin.io.path.writeLines
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
class AsmGen6502: ICodeGeneratorBackend {
class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
): IAssemblyProgram? {
val asmgen = AsmGen6502Internal(program, symbolTable, options, errors)
val st = if(prefixSymbols) prefixSymbols(program, options, symbolTable) else symbolTable
val asmgen = AsmGen6502Internal(program, st, options, errors)
return asmgen.compileToAssembly()
}
private fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
fun prefixNamedNode(node: PtNamedNode) {
node.name = "p8_${node.name}"
}
fun prefixSymbols(node: PtNode) {
when(node) {
is PtAsmSub -> {
prefixNamedNode(node)
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
}
is PtSub -> {
prefixNamedNode(node)
node.parameters.forEach { prefixNamedNode(it) }
}
is PtFunctionCall -> {
val stNode = st.lookup(node.name)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
functionCallsToPrefix += node.parent to index
}
}
is PtIdentifier -> {
var lookupName = node.name
if(node.type in SplitWordArrayTypes && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
is PtJump -> {
if(node.identifier!=null) {
val stNode = st.lookup(node.identifier!!.name)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
else if(node.generatedLabel!=null) {
val stNode = st.lookup(node.generatedLabel!!)!!
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
}
is PtBlock -> prefixNamedNode(node)
is PtConstant -> prefixNamedNode(node)
is PtLabel -> prefixNamedNode(node)
is PtMemMapped -> prefixNamedNode(node)
is PtSubroutineParameter -> prefixNamedNode(node)
is PtVariable -> {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
else -> { }
}
node.children.forEach { prefixSymbols(it) }
}
program.allBlocks().forEach { block ->
if (!block.noSymbolPrefixing) {
prefixSymbols(block)
}
}
nodesToPrefix.forEach { (parent, index) ->
val node = parent.children[index]
when(node) {
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
is PtJump -> parent.children[index] = node.prefix(parent, st)
is PtVariable -> parent.children[index] = node.prefix(st)
else -> throw AssemblyError("weird node to prefix $node")
}
}
// reversed so inner calls (such as arguments to a function call) get processed before the actual function call itself
functionCallsToPrefix.reversed().forEach { (parent, index) ->
val node = parent.children[index]
if(node is PtFunctionCall) {
parent.children[index] = node.prefix(parent)
} else {
throw AssemblyError("expected PtFunctionCall")
}
}
return SymbolTableMaker(program, options).make()
}
}
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
name = name.split('.').map {"p8_$it" }.joinToString(".")
if(value==null)
return this
val arrayValue = value as? PtArray
return if(arrayValue!=null && arrayValue.children.any { it !is PtNumber} ) {
val newValue = PtArray(arrayValue.type, arrayValue.position)
arrayValue.children.forEach { elt ->
when(elt) {
is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st))
is PtNumber -> newValue.add(elt)
is PtAddressOf -> {
if(elt.definingBlock()?.noSymbolPrefixing==true)
newValue.add(elt)
else {
val newAddr = PtAddressOf(elt.position)
newAddr.children.add(elt.identifier.prefix(newAddr, st))
newAddr.parent = arrayValue
newValue.add(newAddr)
}
}
else -> throw AssemblyError("weird array value element $elt")
}
}
PtVariable(name, type, zeropage, newValue, arraySize, position)
}
else this
}
private fun PtJump.prefix(parent: PtNode, st: SymbolTable): PtJump {
val jump = if(identifier!=null) {
val prefixedIdent = identifier!!.prefix(this, st)
PtJump(prefixedIdent, address, generatedLabel, position)
} else {
val prefixedLabel = generatedLabel!!.split('.').map {"p8_$it" }.joinToString(".")
PtJump(null, address, prefixedLabel, position)
}
jump.parent = parent
return jump
}
private fun PtFunctionCall.prefix(parent: PtNode): PtFunctionCall {
val newName = name.split('.').map {"p8_$it" }.joinToString(".")
val call = PtFunctionCall(newName, void, type, position)
call.children.addAll(children)
call.children.forEach { it.parent = call }
call.parent = parent
if(name.endsWith("concat_string"))
println("CONCAT ${this.position}")
return call
}
private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
val target = st.lookup(name)
if(target?.astNode?.definingBlock()?.noSymbolPrefixing==true)
return this
val newName = name.split('.').map { "p8_$it" }.joinToString(".")
val node = PtIdentifier(newName, type, position)
node.parent = parent
return node
}
class AsmGen6502Internal (
val program: PtProgram,
internal val symbolTable: SymbolTable,
@@ -513,7 +676,6 @@ class AsmGen6502Internal (
private fun translate(stmt: PtIfElse) {
val condition = stmt.condition as? PtBinaryExpression
if(condition!=null) {
require(!options.useNewExprCode)
requireComparisonExpression(condition) // IfStatement: condition must be of form 'x <comparison> <value>'
if (stmt.elseScope.children.isEmpty()) {
val jump = stmt.ifScope.children.singleOrNull()
@@ -641,65 +803,48 @@ class AsmGen6502Internal (
private fun repeatWordCount(count: Int, stmt: PtRepeatLoop) {
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
out("""
lda #<$count
ldy #>$count
sta $counterVar
sty $counterVar+1
val counterVar = createRepeatCounterVar(DataType.UWORD, isTargetCpu(CpuType.CPU65c02), stmt)
// the iny + double dec is microoptimization of the 16 bit loop
out("""
ldy #>$count
lda #<$count
beq +
iny
+ sta $counterVar
sty $counterVar+1
$repeatLabel""")
translate(stmt.statements)
out("""
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
lda $counterVar
ora $counterVar+1
bne $repeatLabel""")
} else {
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)
out("""
lda #<$count
ldy #>$count
sta $counterVar
sty $counterVar+1
$repeatLabel""")
translate(stmt.statements)
out("""
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
lda $counterVar
ora $counterVar+1
bne $repeatLabel""")
}
translate(stmt.statements)
out("""
dec $counterVar
bne $repeatLabel
dec $counterVar+1
bne $repeatLabel""")
}
private fun repeatWordCountInAY(endLabel: String, stmt: PtRepeatLoop) {
// note: A/Y must have been loaded with the number of iterations!
// no need to explicitly test for 0 iterations as this is done in the countdown logic below
// the iny + double dec is microoptimization of the 16 bit loop
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)
out("""
sta $counterVar
sty $counterVar+1
$repeatLabel lda $counterVar
bne +
lda $counterVar+1
beq $endLabel
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
""")
cmp #0
beq +
iny
+ sta $counterVar
sty $counterVar+1
ora $counterVar+1
beq $endLabel
$repeatLabel""")
translate(stmt.statements)
jmp(repeatLabel)
out("""
dec $counterVar
bne $repeatLabel
dec $counterVar+1
bne $repeatLabel""")
out(endLabel)
}
private fun repeatByteCount(count: Int, stmt: PtRepeatLoop) {
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
@@ -990,18 +1135,10 @@ $repeatLabel lda $counterVar
}
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: PtExpression): Pair<PtExpression, PtExpression>? {
val left: PtExpression
val right: PtExpression
val operator: String
if (pointerOffsetExpr is PtBinaryExpression) {
require(!options.useNewExprCode)
operator = pointerOffsetExpr.operator
left = pointerOffsetExpr.left
right = pointerOffsetExpr.right
}
else return null
if (pointerOffsetExpr !is PtBinaryExpression) return null
val operator = pointerOffsetExpr.operator
val left = pointerOffsetExpr.left
val right = pointerOffsetExpr.right
if (operator != "+") return null
val leftDt = left.type
val rightDt = right.type
@@ -1113,10 +1250,7 @@ $repeatLabel lda $counterVar
}
internal fun findSubroutineParameter(name: String, asmgen: AsmGen6502Internal): PtSubroutineParameter? {
val stScope = asmgen.symbolTable.lookup(name)
require(stScope!=null) {
"invalid name lookup $name"
}
val stScope = asmgen.symbolTable.lookup(name) ?: return null
val node = stScope.astNode
if(node is PtSubroutineParameter)
return node
@@ -2764,7 +2898,6 @@ $repeatLabel lda $counterVar
out(" sta P8ESTACK_LO,x | dex")
}
is PtBinaryExpression -> {
require(!options.useNewExprCode)
val addrExpr = expr.address as PtBinaryExpression
if(tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
if(pushResultOnEstack)
@@ -43,7 +43,14 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
mods = optimizeJsrRtsAndOtherCombinations(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods = optimizeUselessPushPopStack(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@@ -517,3 +524,40 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedVal
}
return mods
}
private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
val mods = mutableListOf<Modification>()
fun optimize(register: Char, lines: List<IndexedValue<String>>) {
if(lines[0].value.trimStart().startsWith("ph$register")) {
if(lines[2].value.trimStart().startsWith("pl$register")) {
val second = lines[1].value.trimStart().take(6).lowercase()
if(register!in second
&& !second.startsWith("jsr")
&& !second.startsWith("pl")
&& !second.startsWith("ph")) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[2].index, true, null))
}
}
else if (lines[3].value.trimStart().startsWith("pl$register")) {
val second = lines[1].value.trimStart().take(6).lowercase()
val third = lines[2].value.trimStart().take(6).lowercase()
if(register !in second && register !in third
&& !second.startsWith("jsr") && !third.startsWith("jsr")
&& !second.startsWith("pl") && !third.startsWith("pl")
&& !second.startsWith("ph") && !third.startsWith("ph")) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
}
}
for (lines in linesByFour) {
optimize('a', lines)
optimize('x', lines)
optimize('y', lines)
}
return mods
}
@@ -292,10 +292,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
if(resultToStack)
throw AssemblyError("no support for sqrt float onto stack")
assignAsmGen.assignFAC1float(AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.FLOAT, scope, fcall.position))
else {
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
}
}
@@ -688,7 +688,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
when (dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.abs_b_stack")
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_stack")
else -> throw AssemblyError("no support for abs onto stack for this dt")
else -> {
asmgen.out(" jsr floats.func_abs_f_into_FAC1")
assignAsmGen.assignFAC1float(AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.FLOAT, scope, fcall.position))
}
}
} else {
when (dt) {
@@ -748,7 +751,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
is PtBinaryExpression -> {
require(!asmgen.options.useNewExprCode)
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
val pointer = result?.first as? PtIdentifier
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
@@ -810,7 +812,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else fallback()
}
is PtBinaryExpression -> {
require(!asmgen.options.useNewExprCode)
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
val pointer = result?.first as? PtIdentifier
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
@@ -1159,9 +1160,27 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" tay | pla | cpy #0")
asmgen.out(" tay | cpy #0")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" ldy #0 | cmp #0")
}
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.out(" ldx #0 | cmp #0")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
asmgen.out(" ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args.single(), resultRegister)
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
zero.parent=fcall
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
asmgen.out(" lda cx16.r0L")
}
else -> throw AssemblyError("invalid reg")
}
@@ -248,8 +248,6 @@ internal class ExpressionsAsmGen(private val program: PtProgram,
}
private fun translateExpression(expr: PtBinaryExpression) {
require(!asmgen.options.useNewExprCode)
// Uses evalstack to evaluate the given expression. THIS IS SLOW AND SHOULD BE AVOIDED!
if(translateSomewhatOptimized(expr.left, expr.operator, expr.right))
return
@@ -249,23 +249,6 @@ $endLabel""")
$loopLabel""")
asmgen.translate(stmt.statements)
if(iterableDt==DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
sta $varname
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
$modifiedLabel cmp #0 ; modified
bcc $endLabel
bne $loopLabel
lda $varname
$modifiedLabel2 cmp #0 ; modified
bcs $loopLabel
$endLabel""")
} else {
asmgen.out("""
lda $varname
sec
@@ -283,7 +266,6 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
}
}
@@ -33,8 +33,9 @@ internal class ProgramAndVarsGen(
internal fun generate() {
header()
val allBlocks = program.allBlocks()
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
if(allBlocks.first().name != "p8_main" && allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main' or 'p8_main'")
if(errors.noErrors()) {
program.allBlocks().forEach { block2asm(it) }
@@ -139,24 +140,24 @@ internal class ProgramAndVarsGen(
"cx16" -> {
if(options.floats)
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
asmgen.out(" jsr main.start")
asmgen.out(" jsr p8_main.p8_start")
asmgen.out(" jmp sys.cleanup_at_exit")
}
"c64" -> {
asmgen.out(" jsr main.start | lda #31 | sta $01")
asmgen.out(" jsr p8_main.p8_start | lda #31 | sta $01")
if(!options.noSysInit)
asmgen.out(" jmp sys.cleanup_at_exit")
else
asmgen.out(" rts")
}
"c128" -> {
asmgen.out(" jsr main.start | lda #0 | sta ${"$"}ff00")
asmgen.out(" jsr p8_main.p8_start | lda #0 | sta ${"$"}ff00")
if(!options.noSysInit)
asmgen.out(" jmp sys.cleanup_at_exit")
else
asmgen.out(" rts")
}
else -> asmgen.jmp("main.start")
else -> asmgen.jmp("p8_main.p8_start")
}
}
@@ -175,7 +176,8 @@ internal class ProgramAndVarsGen(
private fun footer() {
asmgen.out("; bss sections")
if(options.varsHigh) {
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
if(options.varsHighBank!=null) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
}
@@ -311,7 +313,9 @@ internal class ProgramAndVarsGen(
asmgen.out("${sub.name}\t$asmStartScope")
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
val scope = symboltable.lookupOrElse(sub.scopedName) {
throw AssemblyError("lookup ${sub.scopedName}")
}
require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope)
@@ -331,7 +335,7 @@ internal class ProgramAndVarsGen(
asmsubs2asm(sub.children)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock()!!.name=="main")
if((sub.name=="start" || sub.name=="p8_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8_main"))
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
@@ -480,8 +484,8 @@ internal class ProgramAndVarsGen(
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
for (variable in vars) {
val scopedName = variable.key
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationStringValue!=null)
val svar = symboltable.lookup(scopedName) as? StStaticVariable
if(svar?.onetimeInitializationStringValue!=null)
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
}
return result
@@ -492,8 +496,8 @@ internal class ProgramAndVarsGen(
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
for (variable in vars) {
val scopedName = variable.key
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationArrayValue!=null)
val svar = symboltable.lookup(scopedName) as? StStaticVariable
if(svar?.onetimeInitializationArrayValue!=null)
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
}
return result
@@ -178,8 +178,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
}
is PtFunctionCall -> {
val symbol = asmgen.symbolTable.lookup(value.name)
val sub = symbol!!.astNode as IPtSubroutine
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
@@ -45,12 +45,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val variable = assign.source.asmVarname
when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
DataType.WORD -> assignVariableWord(assign.target, variable)
DataType.WORD -> assignVariableWord(assign.target, variable, assign.source.datatype)
DataType.UWORD -> {
if(assign.source.datatype in PassByReferenceDatatypes)
assignAddressOf(assign.target, variable)
else
assignVariableWord(assign.target, variable)
assignVariableWord(assign.target, variable, assign.source.datatype)
}
DataType.FLOAT -> assignVariableFloat(assign.target, variable)
DataType.STR -> assignVariableString(assign.target, variable)
@@ -158,7 +158,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignMemoryByte(assign.target, null, value.address as PtIdentifier)
}
is PtBinaryExpression -> {
require(!asmgen.options.useNewExprCode)
val addrExpr = value.address as PtBinaryExpression
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
assignRegisterByte(assign.target, CpuRegister.A, false)
@@ -289,20 +288,86 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtPrefix -> {
if(assign.target.array==null) {
// First assign the value to the target then apply the operator in place on the target.
// This saves a temporary variable
translateNormalAssignment(
AsmAssignment(
AsmAssignSource.fromAstSource(value.value, program, asmgen),
assign.target, program.memsizer, assign.position
), scope
)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign, true, scope)
"~" -> inplaceInvert(assign, scope)
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
if(assign.source.datatype==assign.target.datatype) {
if(assign.source.datatype in IntegerDatatypes) {
val signed = assign.source.datatype in SignedDatatypes
if(assign.source.datatype in ByteDatatypes) {
assignExpressionToRegister(value.value, RegisterOrPair.A, signed)
when(value.operator) {
"+" -> {}
"-" -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" eor #255 | ina")
else
asmgen.out(" eor #255 | clc | adc #1")
}
"~" -> asmgen.out(" eor #255")
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
assignRegisterByte(assign.target, CpuRegister.A, signed)
} else {
assignExpressionToRegister(value.value, RegisterOrPair.AY, signed)
when(value.operator) {
"+" -> {}
"-" -> {
asmgen.out("""
sec
eor #255
adc #0
pha
tya
eor #255
adc #0
tay
pla""")
}
"~" -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
} else {
// First assign the value to the target then apply the operator in place on the target.
// This saves a temporary variable
translateNormalAssignment(
AsmAssignment(
AsmAssignSource.fromAstSource(value.value, program, asmgen),
assign.target, program.memsizer, assign.position
), scope
)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign, true, scope)
"~" -> inplaceInvert(assign, scope)
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
}
} else {
// use a temporary variable
val tempvar = if(value.type in ByteDatatypes) "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_W1"
assignExpressionToVariable(value.value, tempvar, value.type)
when (value.operator) {
"+" -> {}
"-", "~" -> {
val assignTempvar = AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, value.type, variableAsmName = tempvar),
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, value.type, scope, assign.position, variableAsmName = tempvar),
program.memsizer, assign.position)
if(value.operator=="-")
inplaceNegate(assignTempvar, true, scope)
else
inplaceInvert(assignTempvar, scope)
}
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
if(value.type in ByteDatatypes)
assignVariableByte(assign.target, tempvar)
else
assignVariableWord(assign.target, tempvar, value.type)
}
} else {
assignPrefixedExpressionToArrayElt(assign, scope)
@@ -313,7 +378,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignRegisterByte(assign.target, CpuRegister.A, false)
}
is PtBinaryExpression -> {
require(!asmgen.options.useNewExprCode)
if(!attemptAssignOptimizedBinexpr(value, assign)) {
// All remaining binary expressions just evaluate via the stack for now.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
@@ -340,7 +404,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.translateNormalAssignment(assignToTempvar, scope)
when(assign.target.datatype) {
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
in WordDatatypes -> assignVariableWord(assign.target, tempvar, assign.source.datatype)
DataType.FLOAT -> assignVariableFloat(assign.target, tempvar)
else -> throw AssemblyError("weird dt")
}
@@ -359,7 +423,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun attemptAssignOptimizedBinexpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
require(!asmgen.options.useNewExprCode)
if(expr.operator in ComparisonOperators) {
if(expr.right.asConstInteger() == 0) {
if(expr.operator == "==" || expr.operator=="!=") {
@@ -748,10 +811,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
in WordDatatypes -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
jsr math.multiply_words
lda math.multiply_words.result
ldy math.multiply_words.result+1""")
asmgen.out(" jsr math.multiply_words")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
@@ -769,18 +829,14 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return true
}
in WordDatatypes -> {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type in SignedDatatypes)
if (value in asmgen.optimizedWordMultiplications)
if (value in asmgen.optimizedWordMultiplications) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type in SignedDatatypes)
asmgen.out(" jsr math.mul_word_${value}")
else
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
ldy math.multiply_words.result+1""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.multiply_words")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
@@ -837,7 +893,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
DataType.UWORD -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.divmod_uw_asm")
assignVariableWord(assign.target, "P8ZP_SCRATCH_W2")
assignVariableWord(assign.target, "P8ZP_SCRATCH_W2", DataType.UWORD)
return true
}
else -> return false
@@ -1155,7 +1211,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun attemptAssignToByteCompareZero(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
require(!asmgen.options.useNewExprCode)
when (expr.operator) {
"==" -> {
when(val dt = expr.left.type) {
@@ -1335,7 +1390,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignMemoryByteIntoWord(target, null, value.address as PtIdentifier)
}
is PtBinaryExpression -> {
require(!asmgen.options.useNewExprCode)
val addrExpr = value.address as PtBinaryExpression
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
asmgen.out(" ldy #0")
@@ -2061,19 +2115,29 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignVariableWord(target: AsmAssignTarget, sourceName: String) {
private fun assignVariableWord(target: AsmAssignTarget, sourceName: String, sourceDt: DataType) {
require(sourceDt in WordDatatypes || sourceDt==DataType.UBYTE)
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda $sourceName
ldy $sourceName+1
sta ${target.asmVarname}
sty ${target.asmVarname}+1""")
if(sourceDt==DataType.UBYTE) {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}")
else
asmgen.out(" lda #0 | sta ${target.asmVarname}")
}
else
asmgen.out("""
lda $sourceName
ldy $sourceName+1
sta ${target.asmVarname}
sty ${target.asmVarname}+1""")
}
TargetStorageKind.MEMORY -> {
throw AssemblyError("assign word to memory ${target.memory} should have gotten a typecast")
}
TargetStorageKind.ARRAY -> {
if(sourceDt==DataType.UBYTE) TODO("assign byte to word array")
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
@@ -2149,29 +2213,53 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" ldx $sourceName+1 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy $sourceName+1 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy $sourceName+1 | ldx $sourceName")
in Cx16VirtualRegisters -> {
asmgen.out(
"""
lda $sourceName
sta cx16.${target.register.toString().lowercase()}
lda $sourceName+1
sta cx16.${target.register.toString().lowercase()}+1
""")
if(sourceDt==DataType.UBYTE) {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
in Cx16VirtualRegisters -> {
asmgen.out(" lda $sourceName | sta cx16.${target.register.toString().lowercase()}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${target.register.toString().lowercase()}+1")
else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().lowercase()}+1")
}
else -> throw AssemblyError("can't load word in a single 8-bit register")
}
} else {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" ldx $sourceName+1 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy $sourceName+1 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy $sourceName+1 | ldx $sourceName")
in Cx16VirtualRegisters -> {
asmgen.out(
"""
lda $sourceName
sta cx16.${target.register.toString().lowercase()}
lda $sourceName+1
sta cx16.${target.register.toString().lowercase()}+1
""")
}
else -> throw AssemblyError("can't load word in a single 8-bit register")
}
else -> throw AssemblyError("can't load word in a single 8-bit register")
}
}
TargetStorageKind.STACK -> {
asmgen.out("""
lda $sourceName
sta P8ESTACK_LO,x
lda $sourceName+1
sta P8ESTACK_HI,x
dex""")
if(sourceDt==DataType.UBYTE) {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x")
}
else
asmgen.out("""
lda $sourceName
sta P8ESTACK_LO,x
lda $sourceName+1
sta P8ESTACK_HI,x
dex""")
}
}
}
@@ -3397,6 +3485,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda ${address.toHex()}")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda ${address.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldy ${address.toHex()}")
in Cx16VirtualRegisters -> {
asmgen.out(" lda ${address.toHex()} | sta cx16.${wordtarget.register.toString().lowercase()}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${wordtarget.register.toString().lowercase()}+1")
else
asmgen.out(" lda #0 | sta cx16.${wordtarget.register.toString().lowercase()}+1")
}
else -> throw AssemblyError("word regs can only be pair")
}
TargetStorageKind.STACK -> {
@@ -3429,6 +3524,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.AX -> asmgen.out(" ldx #0")
RegisterOrPair.AY -> asmgen.out(" ldy #0")
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
in Cx16VirtualRegisters -> {
asmgen.out(" sta cx16.${wordtarget.register.toString().lowercase()}")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${wordtarget.register.toString().lowercase()}+1")
else
asmgen.out(" lda #0 | sta cx16.${wordtarget.register.toString().lowercase()}+1")
}
else -> throw AssemblyError("word regs can only be pair")
}
}
@@ -3473,7 +3575,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.storeAIntoPointerVar(addressExpr)
}
addressExpr is PtBinaryExpression -> {
require(!asmgen.options.useNewExprCode)
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, addressExpr.operator, true))
storeViaExprEval()
}
@@ -3597,11 +3698,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
DataType.BYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda #0
sec
sbc ${target.asmVarname}
sta ${target.asmVarname}""")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
lda ${target.asmVarname}
eor #255
ina
sta ${target.asmVarname}""")
else
asmgen.out("""
lda #0
sec
sbc ${target.asmVarname}
sta ${target.asmVarname}""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
@@ -3610,7 +3718,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out(" eor #255 | ina")
else
asmgen.out(" eor #255 | clc | adc #1")
}
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
@@ -93,8 +93,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.ARRAY -> inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value.expression)
}
else {
@@ -178,7 +177,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.ARRAY -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value.expression)
} else {
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value.expression!!)
@@ -260,6 +258,10 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
indexVar!=null -> {
when (target.datatype) {
in ByteDatatypes -> {
if(value.kind==SourceStorageKind.EXPRESSION
&& value.expression is PtTypeCast
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
asmgen.out(" lda ${target.array.variable.name},y | sta P8ZP_SCRATCH_B1")
asmgen.saveRegisterLocal(CpuRegister.Y, target.scope!!)
@@ -271,8 +273,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.ARRAY -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
} else {
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
@@ -284,15 +284,19 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda P8ZP_SCRATCH_B1 | sta ${target.array.variable.name},y")
}
in WordDatatypes -> {
if(value.kind==SourceStorageKind.EXPRESSION
&& value.expression is PtTypeCast
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y)
if(target.array.splitWords) {
asmgen.out(" lda ${target.array.variable.name}_lsb,y | sta P8ZP_SCRATCH_W1")
asmgen.out(" lda ${target.array.variable.name}_msb,y | sta P8ZP_SCRATCH_W1+1")
} else {
asmgen.out(" lda ${target.array.variable.name},y | sta P8ZP_SCRATCH_W1")
asmgen.out(" iny | lda ${target.array.variable.name},y | sta P8ZP_SCRATCH_W1+1")
asmgen.out(" lda ${target.array.variable.name}+1,y | sta P8ZP_SCRATCH_W1+1")
}
asmgen.saveRegisterLocal(CpuRegister.Y, target.scope!!)
asmgen.saveRegisterStack(CpuRegister.Y, false)
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplaceModification_word_litval_to_variable("P8ZP_SCRATCH_W1", target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplaceModification_word_variable_to_variable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype)
@@ -301,8 +305,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.ARRAY -> inplaceModification_word_value_to_variable("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
return
inplaceModification_word_value_to_variable("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression)
} else {
inplaceModification_word_value_to_variable("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!)
@@ -310,13 +312,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
else -> throw AssemblyError("weird source type ${value.kind}")
}
asmgen.restoreRegisterLocal(CpuRegister.Y)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
if(target.array.splitWords) {
asmgen.out(" lda P8ZP_SCRATCH_W1 | sta ${target.array.variable.name}_lsb,y")
asmgen.out(" lda P8ZP_SCRATCH_W1+1 | sta ${target.array.variable.name}_msb,y")
} else {
asmgen.out(" lda P8ZP_SCRATCH_W1+1 | sta ${target.array.variable.name},y")
asmgen.out(" lda P8ZP_SCRATCH_W1 | dey | sta ${target.array.variable.name},y")
asmgen.out(" lda P8ZP_SCRATCH_W1 | sta ${target.array.variable.name},y")
asmgen.out(" lda P8ZP_SCRATCH_W1+1 | sta ${target.array.variable.name}+1,y")
}
}
DataType.FLOAT -> {
@@ -598,8 +600,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" clc | adc $name | sta $name")
}
"-" -> {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", dt)
asmgen.out(" lda $name | sec | sbc P8ZP_SCRATCH_B1 | sta $name")
val tmpByte = if(name!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.assignExpressionToVariable(value, tmpByte, dt)
asmgen.out(" lda $name | sec | sbc $tmpByte | sta $name")
}
"*" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
@@ -1076,11 +1079,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
"-" -> {
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
val tmpByte = if(name!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.out("""
sta P8ZP_SCRATCH_B1
sta $tmpByte
lda $name
sec
sbc P8ZP_SCRATCH_B1
sbc $tmpByte
sta $name""")
}
"|" -> {
@@ -1115,11 +1119,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
"-" -> {
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
val tmpByte = if(name!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.out("""
sta P8ZP_SCRATCH_B1
sta $tmpByte
lda $name
sec
sbc P8ZP_SCRATCH_B1
sbc $tmpByte
sta $name
bcc +
dec $name+1
@@ -1216,10 +1221,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
sty $name+1""")
}
}
"/" -> {
@@ -1677,10 +1680,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
sty $name+1""")
}
"/" -> {
if(dt==DataType.UWORD) {
@@ -1817,11 +1818,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1
""")
sty $name+1""")
}
"/" -> {
if(dt==DataType.WORD) {
@@ -1834,8 +1832,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
ldy $otherName+1
jsr math.divmod_w_asm
sta $name
sty $name+1
""")
sty $name+1""")
}
else {
asmgen.out("""
@@ -1847,8 +1844,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
ldy $otherName+1
jsr math.divmod_uw_asm
sta $name
sty $name+1
""")
sty $name+1""")
}
}
"%" -> {
@@ -2004,8 +2000,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: PtExpression) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
fun multiplyVarByWordInAY() {
asmgen.out("""
sta P8ZP_SCRATCH_W1
@@ -2013,10 +2007,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1
sty $name+1
""")
}
@@ -2217,8 +2209,10 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" clc | adc $name | sta $name | tya | adc $name+1 | sta $name+1")
}
"-" -> {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", valueDt)
asmgen.out(" lda $name | sec | sbc P8ZP_SCRATCH_W1 | sta $name | lda $name+1 | sbc P8ZP_SCRATCH_W1+1 | sta $name+1")
val tmpWord = if(name!="P8ZP_SCRATCH_W1") "P8ZP_SCRATCH_W1" else "P8ZP_SCRATCH_W2"
asmgen.assignExpressionToVariable(value, tmpWord, valueDt)
asmgen.out(" lda $name | sec | sbc $tmpWord | sta $name | lda $name+1 | sbc $tmpWord+1 | sta $name+1")
// TODO wrong signed word subtraction code??? ^^^
}
"*" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
+4
View File
@@ -45,6 +45,10 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
warnings.add(text)
}
override fun undefined(symbol: List<String>, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
+16 -4
View File
@@ -1,7 +1,9 @@
package prog8tests.codegencpu6502
import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
import io.kotest.matchers.shouldBe
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
@@ -40,9 +42,9 @@ class TestCodegen: FunSpec({
// xx += cx16.r0
// }
//}
val codegen = AsmGen6502()
val codegen = AsmGen6502(prefixSymbols = false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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))
@@ -92,7 +94,7 @@ class TestCodegen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
@@ -107,9 +109,19 @@ class TestCodegen: FunSpec({
test("64tass assembler available? - if this fails you need to install 64tass in the path") {
val command = mutableListOf("64tass", "--version")
shouldNotThrowAny {
val proc = ProcessBuilder(command).inheritIO().start()
val proc = ProcessBuilder(command).start()
val output = String(proc.inputStream.readBytes())
val result = proc.waitFor()
result.shouldBe(0)
val (_, version) = output.split('V')
val (major, minor, _) = version.split('.')
val majorNum = major.toInt()
val minorNum = minor.toInt()
withClue("64tass version should be 1.58 or newer") {
majorNum shouldBeGreaterThanOrEqual 1
if (majorNum == 1)
minorNum shouldBeGreaterThanOrEqual 58
}
}
}
})
+1 -1
View File
@@ -29,7 +29,7 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.17"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
}
@@ -26,6 +26,15 @@ class ExperiCodeGen: ICodeGeneratorBackend {
IRFileWriter(irProgram, null).write()
println("** experimental codegen stub: no assembly generated **")
return null
return EmptyProgram
}
}
}
private object EmptyProgram : IAssemblyProgram {
override val name = "<Empty Program>"
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
println("** nothing assembled **")
return true
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ dependencies {
implementation project(':intermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.17"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
}
@@ -1,6 +1,5 @@
package prog8.codegen.intermediate
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
@@ -105,27 +104,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
value.add(origAssign.value)
} else {
require(origAssign.operator.endsWith('='))
if(codeGen.options.useNewExprCode) {
// X += Y -> temp = X, temp += Y, X = temp
val tempvar = codeGen.getReusableTempvar(origAssign.definingSub()!!, origAssign.target.type)
val assign = PtAssignment(origAssign.position)
val target = PtAssignTarget(origAssign.position)
target.add(tempvar)
assign.add(target)
assign.add(origAssign.target.children.single())
val augAssign = PtAugmentedAssign(origAssign.operator, origAssign.position)
augAssign.add(target)
augAssign.add(origAssign.value)
val assignBack = PtAssignment(origAssign.position)
assignBack.add(origAssign.target)
assignBack.add(tempvar)
return translateRegularAssign(assign) + translate(augAssign) + translateRegularAssign(assignBack)
} else {
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
val left: PtExpression = origAssign.target.children.single() as PtExpression
value.add(left)
value.add(origAssign.value)
}
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
val left: PtExpression = origAssign.target.children.single() as PtExpression
value.add(left)
value.add(origAssign.value)
}
val normalAssign = PtAssignment(origAssign.position)
normalAssign.add(origAssign.target)
@@ -185,15 +167,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
} else false
if (assignment.value is PtMachineRegister) {
valueRegister = (assignment.value as PtMachineRegister).register
if(extendByteToWord)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister), null)
if(extendByteToWord) {
valueRegister = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtMachineRegister).register), null)
}
} else {
val tr = expressionEval.translateExpression(assignment.value)
valueRegister = tr.resultReg
addToResult(result, tr, valueRegister, -1)
if(extendByteToWord) {
valueRegister = codeGen.registers.nextFree()
val opcode = if(assignment.value.type in SignedDatatypes) Opcode.EXTS else Opcode.EXT
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1 = valueRegister), null)
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg), null)
}
}
}
@@ -236,8 +221,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
val fixedIndex = constIntValue(targetArray.index)
val iterable = codeGen.symbolTable.flat.getValue(targetArray.variable.name) as StStaticVariable
val arrayLength = iterable.length!!
val arrayLength = codeGen.symbolTable.getLength(targetArray.variable.name)
if(zero) {
if(fixedIndex!=null) {
val chunk = IRCodeChunk(null, null).also {
@@ -347,19 +331,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return Pair(result, tr.resultReg)
}
if(codeGen.options.useNewExprCode) {
val tr = expressionEval.translateExpression(array.index)
result += tr.chunks
addInstr(result, IRInstruction(Opcode.MUL, tr.dt, reg1=tr.resultReg, immediate = itemsize), null)
return Pair(result, tr.resultReg)
} else {
val mult: PtExpression
mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
val tr = expressionEval.translateExpression(mult)
addToResult(result, tr, tr.resultReg, -1)
return Pair(result, tr.resultReg)
}
val mult: PtExpression
mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
val tr = expressionEval.translateExpression(mult)
addToResult(result, tr, tr.resultReg, -1)
return Pair(result, tr.resultReg)
}
}
@@ -1,6 +1,5 @@
package prog8.codegen.intermediate
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
@@ -29,7 +28,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"rsavex",
"rrestore",
"rrestorex" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callfar" -> funcCallfar(call)
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
@@ -53,6 +52,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentwordTr = exprGen.translateExpression(call.args[2])
addToResult(result, bankTr, bankTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addToResult(result, argumentwordTr, argumentwordTr.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to argumentwordTr.resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
}
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val number = call.args[0]
@@ -107,9 +118,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val syscall =
when (array.dt) {
when (arrayName.type) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
DataType.ARRAY_UW,
@@ -121,16 +132,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val syscall =
when(array.dt) {
when(arrayName.type) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
DataType.ARRAY_UW,
@@ -142,7 +153,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
@@ -161,10 +172,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val compareReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, immediate = 0x80)
it += IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=compareReg, immediate = 0, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
@@ -174,8 +183,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val compareReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, immediate = 0x8000)
it += IRInstruction(Opcode.BEQ, IRDataType.WORD, reg1=compareReg, immediate = 0, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
@@ -275,9 +283,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val syscall =
when(array.dt) {
when(arrayName.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
@@ -288,16 +296,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val syscall =
when(array.dt) {
when(arrayName.type) {
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
@@ -311,7 +319,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -322,10 +330,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = lsbTr.resultReg, reg2 = msbTr.resultReg)
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg, reg3 = msbTr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.WORD, lsbTr.resultReg, -1)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -1,7 +1,6 @@
package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StStaticVariable
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.AssemblyError
@@ -45,9 +44,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
is PtIdentifier -> {
val vmDt = irType(expr.type)
val code = IRCodeChunk(null, null)
if (expr.type in PassByValueDatatypes) {
val vmDt = irType(expr.type)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
code += IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = expr.name)
@@ -60,6 +59,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
} else {
// for strings and arrays etc., load the *address* of the value instead
val vmDt = if(expr.type==DataType.UNDEFINED) IRDataType.WORD else irType(expr.type)
val resultRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
ExpressionCodeResult(code, vmDt, resultRegister, -1)
@@ -105,8 +105,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.name) as StStaticVariable
when(iterable.dt) {
when(check.iterable.type) {
DataType.STR -> {
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
@@ -121,7 +120,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val iterableTr = translateExpression(check.iterable)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null)
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
@@ -131,12 +131,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val iterableTr = translateExpression(check.iterable)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null)
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.name}")
else -> throw AssemblyError("weird iterable dt ${check.iterable.type} for ${check.iterable.name}")
}
}
@@ -163,16 +164,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(arrayIx.splitWords) {
require(vmDt==IRDataType.WORD)
val iterable = codeGen.symbolTable.flat.getValue(arrayIx.variable.name) as StStaticVariable
val arrayLength = iterable.length!!
val arrayLength = codeGen.symbolTable.getLength(arrayIx.variable.name)
resultRegister = codeGen.registers.nextFree()
val finalResultReg = codeGen.registers.nextFree()
if(arrayIx.index is PtNumber) {
val memOffset = (arrayIx.index as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
val tmpRegMsb = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultRegister, reg2=tmpRegMsb)
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
}
} else {
val tr = translateExpression(arrayIx.index)
@@ -181,10 +182,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tmpRegMsb = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultRegister, reg2=tmpRegMsb)
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
}
}
return ExpressionCodeResult(result, vmDt, resultRegister, -1)
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
}
var resultFpRegister = -1
@@ -274,13 +275,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when(cast.value.type) {
DataType.BYTE -> {
// byte -> uword: sign extend
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null)
}
DataType.UBYTE -> {
// ubyte -> uword: sign extend
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null)
}
DataType.WORD -> {
actualResultReg2 = tr.resultReg
@@ -296,13 +297,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when(cast.value.type) {
DataType.BYTE -> {
// byte -> word: sign extend
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
DataType.UBYTE -> {
// byte -> word: sign extend
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null)
}
DataType.UWORD -> {
actualResultReg2 = tr.resultReg
@@ -339,7 +340,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun translate(binExpr: PtBinaryExpression): ExpressionCodeResult {
require(!codeGen.options.useNewExprCode)
val vmDt = irType(binExpr.left.type)
val signed = binExpr.left.type in SignedDatatypes
return when(binExpr.operator) {
@@ -455,6 +455,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
greaterEquals: Boolean
): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val cmpResultReg = codeGen.registers.nextFree()
if(vmDt==IRDataType.FLOAT) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
@@ -469,10 +470,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (greaterEquals) Opcode.SGE else Opcode.SGT
}
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1=cmpResultReg, reg2 = resultRegister, reg3 = zeroRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultReg, -1)
} else {
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
} else {
val leftTr = translateExpression(binExpr.left)
@@ -484,8 +485,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (greaterEquals) Opcode.SGE else Opcode.SGT
}
addInstr(result, IRInstruction(ins, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
addInstr(result, IRInstruction(ins, vmDt, reg1=cmpResultReg, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultReg, -1)
}
}
}
@@ -497,6 +498,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
lessEquals: Boolean
): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val cmpResultRegister = codeGen.registers.nextFree()
if(vmDt==IRDataType.FLOAT) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
@@ -511,10 +513,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (lessEquals) Opcode.SLE else Opcode.SLT
}
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1=cmpResultRegister, reg2 = resultRegister, reg3 = zeroRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultRegister, -1)
} else {
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
} else {
val leftTr = translateExpression(binExpr.left)
@@ -526,8 +528,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (lessEquals) Opcode.SLE else Opcode.SLT
}
addInstr(result, IRInstruction(ins, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
addInstr(result, IRInstruction(ins, vmDt, reg1=cmpResultRegister, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultRegister, -1)
}
}
}
@@ -553,7 +555,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
result += IRCodeChunk(label, null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
} else {
return if(constValue(binExpr.right)==0.0) {
@@ -569,8 +571,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultReg, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
}
}
@@ -1240,6 +1243,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
): MutableList<IRCodeChunkBase> {
val result = mutableListOf<IRCodeChunkBase>()
val valueReg = codeGen.registers.nextFree()
val cmpResultReg = codeGen.registers.nextFree()
if(operand is PtNumber) {
val numberReg = codeGen.registers.nextFree()
if (knownAddress != null) {
@@ -1247,16 +1251,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, address = knownAddress)
it += IRInstruction(Opcode.LOAD, vmDt, reg1=numberReg, immediate = operand.number.toInt())
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, address = knownAddress)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = cmpResultReg, reg2 = valueReg, reg3 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, address = knownAddress)
}
} else {
// in-place modify a symbol (variable)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(Opcode.LOAD, vmDt, reg1=numberReg, immediate = operand.number.toInt())
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1=cmpResultReg, reg2 = valueReg, reg3 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = symbol)
}
}
} else {
@@ -1266,15 +1270,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// in-place modify a memory location
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, address = knownAddress)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, address = knownAddress)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1=cmpResultReg, reg2 = valueReg, reg3 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, address = knownAddress)
}
} else {
// in-place modify a symbol (variable)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1=cmpResultReg, reg2 = valueReg, reg3 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = symbol)
}
}
}
@@ -1293,6 +1297,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val zeroReg = codeGen.registers.nextFree()
if(operand is PtNumber) {
val numberReg = codeGen.registers.nextFreeFloat()
val cmpResultReg = codeGen.registers.nextFree()
if (knownAddress != null) {
// in-place modify a memory location
result += IRCodeChunk(null, null).also {
@@ -1300,8 +1305,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
}
} else {
@@ -1311,13 +1316,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
}
}
} else {
val tr = translateExpression(operand)
val cmpResultReg = codeGen.registers.nextFree()
addToResult(result, tr, -1, tr.resultFpReg)
if (knownAddress != null) {
// in-place modify a memory location
@@ -1325,8 +1331,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = tr.resultFpReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
}
} else {
@@ -1335,8 +1341,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = tr.resultFpReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
}
}
@@ -1,9 +1,6 @@
package prog8.codegen.intermediate
import prog8.code.StMemVar
import prog8.code.StNode
import prog8.code.StStaticVariable
import prog8.code.SymbolTable
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.*
import prog8.intermediate.*
@@ -102,14 +99,16 @@ class IRCodeGen(
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
irProg.blocks.forEach { block ->
block.children.firstOrNull { it is IRInlineAsmChunk }?.let { first->
first as IRInlineAsmChunk
if(first.label==null) {
val replacement = IRInlineAsmChunk(block.label, first.assembly, first.isIR, first.next)
block.children.removeAt(0)
block.children.add(0, replacement)
} else if(first.label != block.label) {
throw AssemblyError("first chunk in block has label that differs from block name")
if(block.isNotEmpty()) {
val firstAsm = block.children[0] as? IRInlineAsmChunk
if(firstAsm!=null) {
if(firstAsm.label==null) {
val replacement = IRInlineAsmChunk(block.label, firstAsm.assembly, firstAsm.isIR, firstAsm.next)
block.children.removeAt(0)
block.children.add(0, replacement)
} else if(firstAsm.label != block.label) {
throw AssemblyError("first chunk in block has label that differs from block name")
}
}
}
@@ -168,16 +167,22 @@ class IRCodeGen(
replacements.forEach {
val old = it.first.instructions[it.second]
val formats = instructionFormats.getValue(old.opcode)
val format = formats.getOrElse(old.type) { throw IllegalArgumentException("type ${old.type} invalid for ${old.opcode}") }
val immediateValue = if(format.immediate) it.third.toInt() else null
val addressValue = if(format.immediate) null else it.third.toInt()
it.first.instructions[it.second] = IRInstruction(
old.opcode,
old.type,
old.reg1,
old.reg2,
old.reg3,
old.fpReg1,
old.fpReg2,
immediate = immediateValue,
null,
null,
address = it.third.toInt(),
address = addressValue,
null,
null
)
@@ -395,42 +400,35 @@ class IRCodeGen(
}
private fun translate(whenStmt: PtWhen): IRCodeChunks {
if(whenStmt.choices.children.isEmpty())
return emptyList()
val result = mutableListOf<IRCodeChunkBase>()
val valueDt = irType(whenStmt.value.type)
val valueTr = expressionEval.translateExpression(whenStmt.value)
addToResult(result, valueTr, valueTr.resultReg, -1)
val choices = whenStmt.choices.children.map {it as PtWhenChoice }
val choices = mutableListOf<Pair<String, PtWhenChoice>>()
val endLabel = createLabelName()
for (choice in choices) {
whenStmt.choices.children.forEach {
val choice = it as PtWhenChoice
if(choice.isElse) {
result += translateNode(choice.statements)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
} else {
val skipLabel = createLabelName()
val values = choice.values.children.map {it as PtNumber}
if(values.size==1) {
val chunk = IRCodeChunk(null, null)
chunk += IRInstruction(Opcode.BNE, valueDt, reg1=valueTr.resultReg, immediate = values[0].number.toInt(), labelSymbol = skipLabel)
result += chunk
result += translateNode(choice.statements)
if(choice.statements.children.last() !is PtReturn)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
} else {
val matchLabel = createLabelName()
val chunk = IRCodeChunk(null, null)
for (value in values) {
chunk += IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = matchLabel)
}
chunk += IRInstruction(Opcode.JUMP, labelSymbol = skipLabel)
result += chunk
result += labelFirstChunk(translateNode(choice.statements), matchLabel)
if(choice.statements.children.last() !is PtReturn)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
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(skipLabel, null)
}
}
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
choices.forEach { (label, choice) ->
result += labelFirstChunk(translateNode(choice.statements), label)
val lastStatement = choice.statements.children.last()
if(lastStatement !is PtReturn && lastStatement !is PtJump)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
}
result += IRCodeChunk(endLabel, null)
return result
}
@@ -447,60 +445,64 @@ class IRCodeGen(
result += translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
val iterableVar = symbolTable.lookup(iterable.name) as StStaticVariable
require(forLoop.variable.name == loopvar.scopedName)
val iterableLength = symbolTable.getLength(iterable.name)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.nextFree()
val tmpReg = registers.nextFree()
val loopLabel = createLabelName()
val endLabel = createLabelName()
if(iterableVar.dt==DataType.STR) {
// iterate over a zero-terminated string
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.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
when (iterable.type) {
DataType.STR -> {
// iterate over a zero-terminated string
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.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
val jumpChunk = IRCodeChunk(null, null)
jumpChunk += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexReg)
jumpChunk += IRInstruction(Opcode.JUMP, labelSymbol = loopLabel)
result += jumpChunk
result += IRCodeChunk(endLabel, null)
}
result += translateNode(forLoop.statements)
val jumpChunk = IRCodeChunk(null, null)
jumpChunk += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexReg)
jumpChunk += IRInstruction(Opcode.JUMP, labelSymbol = loopLabel)
result += jumpChunk
result += IRCodeChunk(endLabel, null)
} else if(iterable.type in SplitWordArrayTypes) {
// iterate over lsb/msb split word array
val elementDt = ArrayToElementTypes.getValue(iterable.type)
if(elementDt !in WordDatatypes)
throw AssemblyError("weird dt")
val length = iterableVar.length!!
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
val tmpRegLsb = registers.nextFree()
val tmpRegMsb = registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = length, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, immediate = length, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=tmpRegLsb, reg2=tmpRegMsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpRegLsb, labelSymbol = loopvarSymbol)
in SplitWordArrayTypes -> {
// iterate over lsb/msb split word array
val elementDt = ArrayToElementTypes.getValue(iterable.type)
if(elementDt !in WordDatatypes)
throw AssemblyError("weird dt")
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
val tmpRegLsb = registers.nextFree()
val tmpRegMsb = registers.nextFree()
val concatReg = registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegLsb, reg3=tmpRegMsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
}
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)
}
}
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(length==256) 0 else length, labelSymbol = loopLabel)
else -> {
// iterate over regular array
val elementDt = ArrayToElementTypes.getValue(iterable.type)
val elementSize = program.memsizer.memorySize(elementDt)
val lengthBytes = iterableLength!! * elementSize
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
}
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)
}
} else {
// iterate over regular array
val elementDt = ArrayToElementTypes.getValue(iterable.type)
val elementSize = program.memsizer.memorySize(elementDt)
val lengthBytes = iterableVar.length!! * elementSize
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
}
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)
}
}
else -> throw AssemblyError("weird for iterable")
@@ -545,22 +547,24 @@ class IRCodeGen(
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol), null)
// if endvalue >= index, iterate loop
val branchInstr = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGESR, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol=loopLabel)
else
IRInstruction(Opcode.BGESR, loopvarDtIr, reg1=fromTr.resultReg, reg2=toTr.resultReg, labelSymbol=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 += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
if(step>0)
IRInstruction(Opcode.BGER, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol=loopLabel)
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
IRInstruction(Opcode.BGER, loopvarDtIr, reg1=fromTr.resultReg, reg2=toTr.resultReg, labelSymbol=loopLabel)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
addInstr(result, branchInstr, null)
result += IRCodeChunk(labelAfterFor, null)
return result
}
@@ -905,7 +909,6 @@ class IRCodeGen(
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
when (condition) {
is PtBinaryExpression -> {
require(!options.useNewExprCode)
if(condition.operator !in ComparisonOperators)
throw AssemblyError("if condition should only be a binary comparison expression")
@@ -919,7 +922,6 @@ class IRCodeGen(
}
else -> {
// if X --> meaning: if X!=0
require(options.useNewExprCode)
val irDt = irType(condition.type)
val signed = condition.type in SignedDatatypes
return if(goto!=null && ifElse.elseScope.children.isEmpty()) {
@@ -997,10 +999,10 @@ class IRCodeGen(
val opcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> if (signed) Opcode.BLTS else throw AssemblyError("unsigned < 0 shouldn't occur in codegen")
">" -> if (signed) Opcode.BGTS else throw AssemblyError("unsigned > 0 shouldn't occur in codegen")
"<=" -> if (signed) Opcode.BLES else throw AssemblyError("unsigned <= 0 shouldn't occur in codegen")
">=" -> if (signed) Opcode.BGES else throw AssemblyError("unsigned >= 0 shouldn't occur in codegen")
"<" -> 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)
@@ -1134,10 +1136,10 @@ class IRCodeGen(
elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if (signed) Opcode.BGES else throw AssemblyError("unsigned < 0 shouldn't occur in codegen")
">" -> if (signed) Opcode.BLES else throw AssemblyError("unsigned > 0 shouldn't occur in codegen")
"<=" -> if (signed) Opcode.BGTS else throw AssemblyError("unsigned <= 0 shouldn't occur in codegen")
">=" -> if (signed) Opcode.BLTS else throw AssemblyError("unsigned >= 0 shouldn't occur in codegen")
"<" -> 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
else -> throw AssemblyError("weird operator")
}
}
@@ -1364,7 +1366,6 @@ class IRCodeGen(
if(array?.splitWords==true) {
val variable = array.variable.name
val fixedIndex = constIntValue(array.index)
val iterable = symbolTable.flat.getValue(array.variable.name) as StStaticVariable
if(fixedIndex!=null) {
val skipLabel = createLabelName()
when(postIncrDecr.operator) {
@@ -1390,7 +1391,7 @@ class IRCodeGen(
else -> throw AssemblyError("weird operator")
}
} else {
val arrayLength = iterable.length!!
val arrayLength = symbolTable.getLength(array.variable.name)
val indexTr = expressionEval.translateExpression(array.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val incReg = registers.nextFree()
@@ -1500,7 +1501,10 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
val countTr = expressionEval.translateExpression(repeat.count)
addToResult(result, countTr, countTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.BEQ, irDt, reg1=countTr.resultReg, immediate = 0, labelSymbol = skipRepeatLabel), null)
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 += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg)
@@ -1512,17 +1516,26 @@ class IRCodeGen(
private fun translate(jump: PtJump): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val instr = if(jump.address!=null) {
IRInstruction(Opcode.JUMPA, address = jump.address!!.toInt())
val chunk = IRCodeChunk(null, null)
if(jump.address!=null) {
chunk += IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
} else {
if (jump.generatedLabel != null)
IRInstruction(Opcode.JUMP, labelSymbol = jump.generatedLabel!!)
else if (jump.identifier != null)
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.generatedLabel!!)
else if (jump.identifier != null) {
val symbol = symbolTable.lookup(jump.identifier!!.name)
if(symbol?.type==StNodeType.MEMVAR || symbol?.type==StNodeType.STATICVAR) {
val jumpReg = registers.nextFree()
chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = jumpReg, labelSymbol = jump.identifier!!.name)
chunk += IRInstruction(Opcode.JUMPI, reg1 = jumpReg)
} else {
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
}
}
else
throw AssemblyError("weird jump")
}
addInstr(result, instr, null)
result += chunk
return result
}
@@ -1610,7 +1623,7 @@ class IRCodeGen(
private fun translate(parameters: List<PtSubroutineParameter>) =
parameters.map {
val flattenedName = it.definingSub()!!.name + "." + it.name
val orig = symbolTable.flat.getValue(flattenedName) as StStaticVariable
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
IRSubroutine.IRParam(flattenedName, orig.dt)
}
@@ -37,8 +37,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
removeEmptyChunks(sub)
joinChunks(sub)
// TODO also do register optimization step here?
sub.chunks.withIndex().forEach { (index, chunk1) ->
// we don't optimize Inline Asm chunks here.
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
@@ -49,17 +47,19 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
val changed = removeNops(chunk1, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|| removeUselessArithmetic(chunk1, indexedInstructions)
|| removeNeedlessCompares(chunk1, indexedInstructions)
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
// TODO other optimizations:
// more complex optimizations such as unused registers
// TODO other optimizations
} while (changed)
}
}
removeEmptyChunks(sub)
}
// TODO also do register optimization step here at the end?
irprog.linkChunks() // re-link
}
@@ -267,6 +267,28 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed
}
private fun removeNeedlessCompares(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// a CMPI with 0, after an instruction like LOAD that already sets the status bits, can be removed.
// but only if the instruction after it is not using the Carry bit because that won't be set by a LOAD instruction etc.
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate==0) {
val previous = indexedInstructions[idx-1].value
if(previous.opcode in OpcodesThatSetStatusbitsIncludingCarry) {
chunk.instructions.removeAt(idx)
changed = true
} else if(previous.opcode in OpcodesThatSetStatusbitsButNotCarry) {
val next = indexedInstructions[idx+1].value
if(next.opcode !in arrayOf(Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTPOS, Opcode.BSTNEG)) {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
}
return changed
}
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
var changed = false
@@ -16,7 +16,7 @@ class IRUnusedCodeRemover(
irprog.blocks.reversed().forEach { block ->
if(block.isEmpty()) {
irprog.blocks.remove(block)
irprog.st.removeTree(block.label)
pruneSymboltable(block.label)
numRemoved++
}
}
@@ -24,6 +24,22 @@ class IRUnusedCodeRemover(
return numRemoved
}
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
val prefix = "$blockLabel."
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
blockVars.forEach { stVar ->
irprog. allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
if(ins.labelSymbol == stVar.name) {
return
}
}
}
}
irprog.st.removeTree(blockLabel)
}
private fun removeUnusedSubroutines(): Int {
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
irprog.foreachCodeChunk { chunk ->
@@ -119,9 +135,22 @@ class IRUnusedCodeRemover(
}
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
val entrypointSub = irprog.blocks.single { it.label=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
val entrypointSub = irprog.blocks.single { it.label=="main" }
.children.single { it is IRSubroutine && it.label=="main.start" }
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
// all chunks referenced in array initializer values are also 'reachable':
irprog.st.allVariables()
.filter { !it.uninitialized }
.forEach {
it.onetimeInitializationArrayValue?.let { array ->
array.forEach {elt ->
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
reachable.add(irprog.getChunkWithLabel(elt.addressOfSymbol!!))
}
}
}
fun grow() {
val new = mutableSetOf<IRCodeChunkBase>()
reachable.forEach {
@@ -150,6 +179,18 @@ class IRUnusedCodeRemover(
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
// all chunks referenced in array initializer values are linked as well!:
irprog.st.allVariables()
.filter { !it.uninitialized }
.forEach {
it.onetimeInitializationArrayValue?.let { array ->
array.forEach {elt ->
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
linkedChunks += irprog.getChunkWithLabel(elt.addressOfSymbol!!)
}
}
}
irprog.foreachCodeChunk { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach {
+4
View File
@@ -43,6 +43,10 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
warnings.add(text)
}
override fun undefined(symbol: List<String>, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
+9 -9
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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, 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)
@@ -3,16 +3,17 @@ package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.UndefinedSymbolError
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.ForLoop
import prog8.ast.statements.RepeatLoop
import prog8.ast.statements.VarDecl
import prog8.ast.statements.VarDeclType
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.DataType
import kotlin.math.floor
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
@@ -307,7 +308,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val rangeTo = iterableRange.to as? NumericLiteral
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: return noModifications
val stepLiteral = iterableRange.step as? NumericLiteral
when(loopvar.datatype) {
@@ -364,6 +365,16 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return noModifications
}
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val count = (repeatLoop.iterations as? NumericLiteral)?.number
if(count!=null && floor(count)!=count) {
val integer = NumericLiteral.optimalInteger(count.toInt(), repeatLoop.position)
repeatLoop.iterations = integer
integer.linkParents(repeatLoop)
}
return noModifications
}
private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
@@ -53,7 +53,8 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
}
if(to==null) {
if(!range.to.inferType(program).isInteger)
val toType = range.to.inferType(program)
if(toType.isKnown && !range.to.inferType(program).isInteger)
errors.err("range expression to value must be integer", range.to.position)
} else if(to-to.toInt()>0) {
errors.err("range expression to value must be integer", range.to.position)
@@ -18,7 +18,7 @@ import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has?
class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter,
@@ -43,9 +43,9 @@ fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget)
fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget
options: CompilationOptions
): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
val optimizer = StatementOptimizer(this, errors, functions, options)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
@@ -54,8 +54,8 @@ fun Program.optimizeStatements(errors: IErrorReporter,
return optimizationCount
}
fun Program.inlineSubroutines(): Int {
val inliner = Inliner(this)
fun Program.inlineSubroutines(options: CompilationOptions): Int {
val inliner = Inliner(this, options)
inliner.visit(this)
return inliner.applyModifications()
}
+17 -3
View File
@@ -8,7 +8,9 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.target.VMTarget
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.value==null
@@ -16,7 +18,7 @@ private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.va
// inliner potentially enables *ONE LINED* subroutines, wihtout to be inlined.
class Inliner(val program: Program): AstWalker() {
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
class DetermineInlineSubs(val program: Program): IAstVisitor {
private val modifications = mutableListOf<IAstModification>()
@@ -211,7 +213,7 @@ class Inliner(val program: Program): AstWalker() {
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val sub = functionCallStatement.target.targetStatement(program) as? Subroutine
return if(sub==null)
return if(sub==null || !canInline(sub, functionCallStatement))
noModifications
else
possibleInlineFcallStmt(sub, functionCallStatement, parent)
@@ -219,7 +221,7 @@ class Inliner(val program: Program): AstWalker() {
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
if(sub!=null && sub.inline && sub.parameters.isEmpty() && canInline(sub, functionCallExpr)) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
}
@@ -246,5 +248,17 @@ class Inliner(val program: Program): AstWalker() {
return noModifications
}
private fun canInline(sub: Subroutine, fcall: IFunctionCall): Boolean {
if(!sub.inline)
return false
if(options.compTarget.name!=VMTarget.NAME) {
val stmt = sub.statements.single()
if (stmt is IFunctionCall) {
val existing = (fcall as Node).definingScope.lookup(stmt.target.nameInSource.take(1))
return existing !is VarDecl
}
}
return true
}
}
@@ -13,7 +13,7 @@ import kotlin.math.floor
class StatementOptimizer(private val program: Program,
private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget
private val options: CompilationOptions
) : AstWalker() {
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
@@ -39,7 +39,7 @@ class StatementOptimizer(private val program: Program,
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = compTarget.encodeString(string.value, string.encoding)[0]
val firstCharEncoded = options.compTarget.encodeString(string.value, string.encoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
@@ -51,7 +51,7 @@ class StatementOptimizer(private val program: Program,
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
)
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.encoding)
val firstTwoCharsEncoded = options.compTarget.encodeString(string.value.take(2), string.encoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
@@ -108,6 +108,16 @@ class StatementOptimizer(private val program: Program,
}
}
// remove obvious dangling elses (else after a return)
if(ifElse.elsepart.isNotEmpty() && ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
if(options.slowCodegenWarnings)
errors.warn("else can be omitted", ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
return noModifications
}
@@ -141,7 +151,7 @@ class StatementOptimizer(private val program: Program,
val size = sv.value.length
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = compTarget.encodeString(sv.value, sv.encoding)[0]
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
@@ -166,16 +176,6 @@ class StatementOptimizer(private val program: Program,
}
}
val iterationCount = forLoop.constIterationCount(program)
if(iterationCount!=null) {
val loopName = forLoop.loopVar.nameInSource
if(!forLoop.iterable.referencesIdentifier(loopName) && !forLoop.body.referencesIdentifier(loopName)) {
errors.warn("for loop can be replaced with repeat loop", forLoop.position)
val repeat = RepeatLoop(NumericLiteral.optimalNumeric(iterationCount, forLoop.position), forLoop.body, forLoop.position)
return listOf(IAstModification.ReplaceNode(forLoop, repeat, parent))
}
}
return noModifications
}
@@ -326,7 +326,7 @@ class StatementOptimizer(private val program: Program,
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
@@ -340,7 +340,7 @@ class StatementOptimizer(private val program: Program,
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
@@ -391,4 +391,47 @@ class StatementOptimizer(private val program: Program,
else
noModifications
}
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
fun replaceWithIf(condition: Expression, trueBlock: AnonymousScope, elseBlock: AnonymousScope?): List<IAstModification> {
val ifStmt = IfElse(condition, trueBlock, elseBlock ?: AnonymousScope(mutableListOf(), whenStmt.position), whenStmt.position)
errors.warn("for boolean condition a normal if statement is preferred", whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
}
if(whenStmt.condition.inferType(program).isBool) {
if(whenStmt.choices.all { it.values?.size==1 }) {
if (whenStmt.choices.all { it.values!!.single().constValue(program)!!.number in arrayOf(0.0, 1.0) }) {
// it's a when statement on booleans that can just be replaced by an if or if..else.
if (whenStmt.choices.size == 1) {
return if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
replaceWithIf(whenStmt.condition, whenStmt.choices[0].statements, null)
} else {
val notCondition = BinaryExpression(whenStmt.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, whenStmt.condition.position), whenStmt.condition.position)
replaceWithIf(notCondition, whenStmt.choices[0].statements, null)
}
} else if (whenStmt.choices.size == 2) {
var trueBlock: AnonymousScope? = null
var elseBlock: AnonymousScope? = null
if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
trueBlock = whenStmt.choices[0].statements
} else {
elseBlock = whenStmt.choices[0].statements
}
if(whenStmt.choices[1].values!![0].constValue(program)!!.number==1.0) {
trueBlock = whenStmt.choices[1].statements
} else {
elseBlock = whenStmt.choices[1].statements
}
if(trueBlock!=null && elseBlock!=null) {
return replaceWithIf(whenStmt.condition, trueBlock, elseBlock)
}
}
}
}
}
return noModifications
}
}
@@ -75,7 +75,9 @@ class UnusedCodeRemover(private val program: Program,
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.warn("removing unused block '${block.name}'", block.position)
}
program.removeInternedStringsFromRemovedBlock(block)
if(!block.statements.any { it is Subroutine && it.hasBeenInlined }) {
program.removeInternedStringsFromRemovedBlock(block)
}
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
}
+18 -7
View File
@@ -1,9 +1,10 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id 'org.jetbrains.kotlin.jvm'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id "io.kotest" version "0.3.9"
id 'io.kotest' version '0.3.9'
id 'com.peterabeles.gversion' version '1.10.2'
}
java {
@@ -24,8 +25,6 @@ compileTestKotlin {
}
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':codeCore')
implementation project(':codeOptimizers')
@@ -34,11 +33,11 @@ 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.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.17"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
@@ -82,7 +81,7 @@ application {
shadowJar {
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
archiveVersion = version
// minimize()
}
@@ -100,4 +99,16 @@ test {
}
}
gversion {
srcDir = "src/" // path is relative to the sub-project by default
classPackage = "prog8.buildversion"
className = "BuildVersion"
language = "kotlin"
}
build.finalizedBy installDist, installShadowDist
compileKotlin.dependsOn createVersionFile // , failDirtyNotSnapshot
compileJava.dependsOn createVersionFile
+31 -1
View File
@@ -2,6 +2,7 @@
; Including memory registers, I/O registers, Basic and Kernal subroutines.
atari {
%option no_symbol_prefixing
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
@@ -9,10 +10,11 @@ atari {
}
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
asmsub init_system() {
@@ -216,6 +218,7 @@ _longcopy
}
cx16 {
%option no_symbol_prefixing
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the Atari as well but their location in memory is different
@@ -321,4 +324,31 @@ cx16 {
&byte r13sH = $1b1b
&byte r14sH = $1b1d
&byte r15sH = $1b1f
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda cx16.r0,y
sta _cx16_vreg_storage,y
dey
bpl -
rts
_cx16_vreg_storage
.word 0,0,0,0,0,0,0,0
.word 0,0,0,0,0,0,0,0
}}
}
asmsub restore_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda save_virtual_registers._cx16_vreg_storage,y
sta cx16.r0,y
dey
bpl -
rts
}}
}
}
+2
View File
@@ -6,6 +6,8 @@
txt {
%option no_symbol_prefixing
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 24
+2
View File
@@ -6,6 +6,8 @@
; 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
+31
View File
@@ -3,6 +3,7 @@
cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
@@ -140,6 +141,7 @@ asmsub RDTIM16() -> uword @AY {
c64 {
; C64 I/O registers (VIC, SID, CIA)
%option no_symbol_prefixing
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
@@ -294,6 +296,7 @@ c64 {
c128 {
; ---- C128 specific registers ----
%option no_symbol_prefixing
&ubyte VM1 = $0A2C ; shadow for VUC $d018 in text mode
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
@@ -330,6 +333,7 @@ asmsub disable_basic() clobbers(A) {
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
@@ -765,6 +769,7 @@ _longcopy
}
cx16 {
%option no_symbol_prefixing
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the C128 as well but their location in memory is different
@@ -874,4 +879,30 @@ cx16 {
&byte r13sH = $1b1b
&byte r14sH = $1b1d
&byte r15sH = $1b1f
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda cx16.r0,y
sta _cx16_vreg_storage,y
dey
bpl -
rts
_cx16_vreg_storage
.word 0,0,0,0,0,0,0,0
.word 0,0,0,0,0,0,0,0
}}
}
asmsub restore_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda save_virtual_registers._cx16_vreg_storage,y
sta cx16.r0,y
dey
bpl -
rts
}}
}
}
+1
View File
@@ -5,6 +5,7 @@
txt {
%option no_symbol_prefixing
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
+1
View File
@@ -5,6 +5,7 @@
floats {
; ---- this block contains C-64 floating point related functions ----
%option no_symbol_prefixing
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
+2
View File
@@ -5,6 +5,8 @@
; assumes bitmap screen memory is $2000-$3fff
graphics {
%option no_symbol_prefixing
const uword BITMAP_ADDRESS = $2000
const uword WIDTH = 320
const ubyte HEIGHT = 200
+35
View File
@@ -6,6 +6,8 @@ cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
@@ -139,6 +141,8 @@ asmsub RDTIM16() -> uword @AY {
c64 {
; C64 I/O registers (VIC, SID, CIA)
%option no_symbol_prefixing
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
@@ -293,6 +297,8 @@ c64 {
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
@@ -732,6 +738,8 @@ _longcopy
cx16 {
%option no_symbol_prefixing
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the C64 as well but their location in memory is different
; (because there's no room for them in the zeropage)
@@ -839,4 +847,31 @@ cx16 {
&byte r13sH = $cf1b
&byte r14sH = $cf1d
&byte r15sH = $cf1f
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda cx16.r0,y
sta _cx16_vreg_storage,y
dey
bpl -
rts
_cx16_vreg_storage
.word 0,0,0,0,0,0,0,0
.word 0,0,0,0,0,0,0,0
}}
}
asmsub restore_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda save_virtual_registers._cx16_vreg_storage,y
sta cx16.r0,y
dey
bpl -
rts
}}
}
}
+2
View File
@@ -6,6 +6,8 @@
txt {
%option no_symbol_prefixing
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
+3 -2
View File
@@ -2,6 +2,8 @@
conv {
%option no_symbol_prefixing
; ----- number conversions to decimal strings ----
str @shared string_out = "????????????????" ; result buffer for the string conversion routines
@@ -506,8 +508,7 @@ asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
jmp uword2decimal.hex_try200
}}
}
+90 -17
View File
@@ -6,6 +6,7 @@
%import syslib
diskio {
%option no_symbol_prefixing
ubyte drivenumber = 8
@@ -73,39 +74,42 @@ io_error:
}
sub diskname() -> uword {
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
ubyte okay = false
void cbm.OPEN() ; open 12,8,0,"$"
; returns disk label name or 0 if error
cbm.SETNAM(3, "$")
cbm.SETLFS(12, 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
if_cs
goto io_error
repeat 6 {
void cbm.CHRIN() ; skip the 6 prologue bytes
while cbm.CHRIN()!='"' {
; skip up to entry name
}
if cbm.READST()!=0
goto io_error
cx16.r0 = &list_filename
cx16.r0 = &diskio.list_filename
repeat {
@(cx16.r0) = cbm.CHRIN()
if @(cx16.r0)==0
if @(cx16.r0)=='"' {
@(cx16.r0) = ' '
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
@(cx16.r0) = 0
cx16.r0--
}
break
}
cx16.r0++
}
okay = true
status = cbm.READST()
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLRCHN()
cbm.CLOSE(12)
if okay
return &list_filename
return 0
if status and status & $40 == 0
return 0
return diskio.list_filename
}
; internal variables for the iterative file lister / loader
@@ -684,6 +688,75 @@ internal_vload:
send_command(list_filename)
}
sub curdir() -> uword {
; return current directory name or 0 if error
; special X16 dos command to only return the current path in the entry list (R42+)
const ubyte MAX_PATH_LEN=80
uword reversebuffer = memory("curdir_buffer", MAX_PATH_LEN, 0)
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
@(cx16.r12)=0
cbm.SETNAM(3, "$=c")
cbm.SETLFS(12, 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
if_cs
goto io_error
repeat 6 {
void cbm.CHRIN()
}
while cbm.CHRIN() {
; skip first line (drive label)
}
while cbm.CHRIN()!='"' {
; skip to first name
}
ubyte status = cbm.READST()
cx16.r10 = &list_filename
while status==0 {
repeat {
@(cx16.r10) = cbm.CHRIN()
if @(cx16.r10)==0
break
cx16.r10++
}
while @(cx16.r10)!='"' and cx16.r10>=&list_filename {
@(cx16.r10)=0
cx16.r10--
}
@(cx16.r10)=0
prepend(list_filename)
cx16.r10 = &list_filename
while cbm.CHRIN()!='"' and status==0 {
status = cbm.READST()
; skipping up to next entry name
}
}
io_error:
cbm.CLRCHN()
cbm.CLOSE(12)
if status and status & $40 == 0
return 0
if @(cx16.r12)==0 {
cx16.r12--
@(cx16.r12)='/'
}
return cx16.r12
sub prepend(str dir) {
if dir[0]=='/' and dir[1]==0
return
cx16.r9L = string.length(dir)
cx16.r12 -= cx16.r9L
sys.memcopy(dir, cx16.r12, cx16.r9L)
cx16.r12--
@(cx16.r12)='/'
}
}
sub relabel(str name) {
; -- change the disk label.
list_filename[0] = 'r'
+2
View File
@@ -6,6 +6,8 @@
floats {
; ---- this block contains C-64 compatible floating point related functions ----
%option no_symbol_prefixing
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
+163 -73
View File
@@ -13,16 +13,19 @@
; 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 (TODO not yet implemented)
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
; mode 2 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
; 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
; higher color dephts in highres are not supported due to lack of VRAM
; TODO remove the phx/plx pairs in non-stack compiler version
gfx2 {
%option no_symbol_prefixing
; read-only control variables:
ubyte active_mode = 0
uword width = 0
@@ -44,7 +47,7 @@ gfx2 {
height = 240
bpp = 1
}
; TODO modes 2, 3 not yet implemented
; TODO modes 2, 3
4 -> {
; lores 256c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
@@ -107,7 +110,7 @@ gfx2 {
repeat 240/2/8
cs_innerloop640()
}
; TODO mode 2, 3
; TODO modes 2, 3
4 -> {
; lores 256c
repeat 240/2
@@ -238,8 +241,7 @@ _done
}}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
; highres 4c ....also mostly usable for mode 2, lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
@@ -598,7 +600,7 @@ _done
}}
}
}
; TODO mode 2,3
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
@@ -650,8 +652,7 @@ _done
}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
; highres 4c ....also mostly usable for mode 2, lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(x) & 3 ; xbits
; color &= 3
@@ -705,7 +706,7 @@ _done
+
}}
}
; TODO mode 2 and 3
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
@@ -879,19 +880,18 @@ skip:
}
sub position(uword @zp x, uword y) {
ubyte bank
when active_mode {
1 -> {
; lores monochrome
cx16.r0 = y*(320/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
; TODO modes 2,3
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
}
5 -> {
; highres monochrome
@@ -901,24 +901,16 @@ skip:
6 -> {
; highres 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
}
}
}
sub position2(uword @zp x, uword y, bool also_port_1) {
position(x, y)
if also_port_1 {
when active_mode {
1, 5 -> cx16.vaddr(0, cx16.r0, 1, 1)
; TODO modes 2, 3
4, 6 -> {
ubyte bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 1, 1)
}
}
}
if also_port_1
cx16.vaddr_clone(0)
}
inline asmsub next_pixel(ubyte color @A) {
@@ -993,48 +985,96 @@ skip:
sub text(uword @zp x, uword y, ubyte color, 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.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
; TODO draw whole horizontal spans using vera auto increment if possible, instead of per-character columns
uword chardataptr
ubyte[8] @shared char_bitmap_bytes_left
ubyte[8] @shared char_bitmap_bytes_right
when active_mode {
1, 5 -> {
; monochrome mode, either resolution
cx16.r2 = 40
if active_mode==5
cx16.r2 = 80
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
position(x,y)
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 {{
lda cx16.VERA_ADDR_H
and #%111 ; don't auto-increment, we have to do that manually because of the ora
sta cx16.VERA_ADDR_H
lda color
; pre-shift the bits
phx ; TODO remove in non-stack version
lda text.x
and #7
sta P8ZP_SCRATCH_B1
ldy #8
- lda P8ZP_SCRATCH_B1
bne + ; white color, plot normally
lda cx16.VERA_DATA1
eor #255 ; black color, keep only the other pixels
and cx16.VERA_DATA0
bra ++
+ lda cx16.VERA_DATA0
ora cx16.VERA_DATA1
+ sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r2
sta cx16.VERA_ADDR_L
bcc +
inc cx16.VERA_ADDR_M
+ inc x
bne +
inc x+1
+ dey
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 --
plx ; TODO remove in non-stack version
}}
sctextptr++
; left part of shifted char
position2(x, y, 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(x) & 7 {
position2(x+8, y, 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++
x += 8
}
}
4 -> {
@@ -1046,7 +1086,7 @@ skip:
position(x,y)
y++
%asm {{
phx
phx ; TODO remove in non-stack version
ldx color
lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1
@@ -1058,7 +1098,7 @@ skip:
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
+ dey
bne -
plx
plx ; TODO remove in non-stack version
}}
}
x+=8
@@ -1070,30 +1110,80 @@ skip:
; 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
cx16.r8 = y
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, true) ; for reading the chardata from Vera data channel 1
position(x, y) ; only calculated once, we update vera address in the loop instead
cx16.VERA_ADDR_H &= $0f ; no auto increment
repeat 8 {
; TODO rewrite this inner loop partly in assembly
; requires expanding the charbits to 2-bits per pixel (based on color)
; also it's way more efficient to draw whole horizontal spans instead of per-character
cx16.r9L = cx16.vpeek(charset_bank, chardataptr) ; get the 8 horizontal character bits
cx16.r10L = cx16.VERA_DATA1 ; get the next 8 horizontal character bits
cx16.r7 = x
repeat 8 {
cx16.r9L <<= 1
if_cs
plot(cx16.r7, cx16.r8, cx16.r11L)
cx16.r10L <<= 1
if_cs {
cx16.r2L = cx16.r7L & 3 ; xbits
when cx16.r11L & 3 {
1 -> cx16.r12L = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
2 -> cx16.r12L = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
3 -> cx16.r12L = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
else -> cx16.r12L = 0
}
cx16.VERA_DATA0 = cx16.VERA_DATA0 & gfx2.plot.mask4c[cx16.r2L] | cx16.r12L
}
cx16.r7++
if (cx16.r7 & 3) == 0 {
; increment the pixel address by one
%asm {{
stz cx16.VERA_CTRL
clc
lda cx16.VERA_ADDR_L
adc #1
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
lda cx16.VERA_ADDR_H
adc #0
sta cx16.VERA_ADDR_H
}}
}
}
chardataptr++
cx16.r8++
; increment pixel address to the next line
%asm {{
stz cx16.VERA_CTRL
clc
lda cx16.VERA_ADDR_L
adc #(640-8)/4
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
lda cx16.VERA_ADDR_H
adc #0
sta cx16.VERA_ADDR_H
}}
}
x+=8
cx16.r8-=8
sctextptr++
}
}
}
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) {
+2
View File
@@ -10,6 +10,8 @@
graphics {
%option no_symbol_prefixing
const uword WIDTH = 320
const ubyte HEIGHT = 240
+18 -17
View File
@@ -2,9 +2,10 @@
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
palette {
%option no_symbol_prefixing
uword vera_palette_ptr
ubyte c
ubyte cc
sub set_color(ubyte index, uword color) {
vera_palette_ptr = $fa00+(index as uword * 2)
@@ -79,13 +80,13 @@ palette {
sub set_grayscale() {
vera_palette_ptr = $fa00
repeat 16 {
c=0
cc=0
repeat 16 {
cx16.vpoke(1, vera_palette_ptr, c)
cx16.vpoke(1, vera_palette_ptr, cc)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, c)
cx16.vpoke(1, vera_palette_ptr, cc)
vera_palette_ptr++
c += $11
cc += $11
}
}
}
@@ -150,11 +151,11 @@ palette {
sub set_c64pepto() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_pepto[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
for cc in 0 to 15 {
uword ccp = C64_colorpalette_pepto[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
}
@@ -163,11 +164,11 @@ palette {
sub set_c64light() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_light[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
for cc in 0 to 15 {
uword ccp = C64_colorpalette_light[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
}
@@ -176,11 +177,11 @@ palette {
sub set_c64dark() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_dark[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
for cc in 0 to 15 {
uword ccp = C64_colorpalette_dark[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
}
+2
View File
@@ -1,6 +1,8 @@
%import syslib
psg {
%option no_symbol_prefixing
; $1F9C0 - $1F9FF 16 blocks of 4 PSG registers (16 voices)
; 00 frequency word LSB
; 01 frequency word MSB. freqword = HERZ / 0.3725290298461914
+161 -5
View File
@@ -5,6 +5,8 @@ cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
; STROUT --> use txt.print
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.home or txt.plot
@@ -88,6 +90,8 @@ asmsub RDTIM16() -> uword @AY {
cx16 {
%option no_symbol_prefixing
; irq, system and hardware vectors:
&uword IERROR = $0300
&uword IMAIN = $0302
@@ -324,7 +328,7 @@ cx16 {
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(A, X, Y) -> bool @Pc
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
@@ -403,7 +407,7 @@ romsub $ff53 = joystick_scan() clobbers(A, X, Y)
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 (bank 10)
; Audio (rom bank 10)
romsub $C04B = psg_init() clobbers(A,X,Y)
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
@@ -411,6 +415,25 @@ romsub $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)ini
; TODO: add more of the audio routines?
asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) -> bool @Pc {
; -- convenience wrapper for screen_mode() to just set a new mode (and return success)
%asm {{
clc
jmp screen_mode
}}
}
asmsub get_screen_mode() -> byte @A, byte @X, byte @Y {
; -- convenience wrapper for screen_mode() to just get the current mode in A, and size in characters in X+Y
; this does need a piece of inlined asm to call it ans store the result values if you call this from prog8 code
; Note: you can also just do the SEC yourself and simply call screen_mode() directly,
; or use the existing SCREEN kernal routine for just getting the size in characters.
%asm {{
sec
jmp screen_mode
}}
}
asmsub kbdbuf_clear() {
; -- convenience helper routine to clear the keyboard buffer
%asm {{
@@ -516,6 +539,8 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) {
; -- setup the VERA's data address register 0 or 1
; with optional auto increment or decrement of 1.
; Note that the vaddr_autoincr() and vaddr_autodecr() routines allow to set all possible strides, not just 1.
%asm {{
and #1
pha
@@ -539,6 +564,105 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
}}
}
asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
; -- clones Vera addresses from the given source port to the other one.
; leaves CTRL on the destination port.
%asm {{
sta VERA_CTRL
ldx VERA_ADDR_L
ldy VERA_ADDR_H
phy
ldy VERA_ADDR_M
eor #1
sta VERA_CTRL
stx VERA_ADDR_L
sty VERA_ADDR_M
ply
sty VERA_ADDR_H
rts
}}
}
asmsub vaddr_autoincr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword autoIncrAmount @R2) clobbers(A,Y) {
; -- setup the VERA's data address register 0 or 1
; including setting up optional auto increment amount.
; Specifiying an unsupported amount results in amount of zero. See the Vera docs about what amounts are possible.
%asm {{
jsr _setup
lda cx16.r2H
ora cx16.r2L
beq +
jsr _determine_incr_bits
+ ora P8ZP_SCRATCH_REG
sta cx16.VERA_ADDR_H
rts
_setup and #1
sta P8ZP_SCRATCH_REG
lda cx16.r1
and #1
sta cx16.VERA_CTRL
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
rts
_determine_incr_bits
lda cx16.r2H
bne _large
lda cx16.r2L
ldy #13
- cmp _strides_lsb,y
beq +
dey
bpl -
+ tya
asl a
asl a
asl a
asl a
rts
_large ora cx16.r2L
cmp #1 ; 256
bne +
lda #9<<4
rts
+ cmp #2 ; 512
bne +
lda #10<<4
rts
+ cmp #65 ; 320
bne +
lda #14<<4
rts
+ cmp #130 ; 640
bne +
lda #15<<4
rts
+ lda #0
rts
_strides_lsb .byte 0,1,2,4,8,16,32,64,128,255,255,40,80,160,255,255
}}
}
asmsub vaddr_autodecr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword autoDecrAmount @R2) clobbers(A,Y) {
; -- setup the VERA's data address register 0 or 1
; including setting up optional auto decrement amount.
; Specifiying an unsupported amount results in amount of zero. See the Vera docs about what amounts are possible.
%asm {{
jsr vaddr_autoincr._setup
lda cx16.r2H
ora cx16.r2L
beq +
jsr vaddr_autoincr._determine_incr_bits
ora #%00001000 ; autodecrement
+ ora P8ZP_SCRATCH_REG
sta cx16.VERA_ADDR_H
rts
}}
}
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- write a single byte to VERA's video memory
; note: inefficient when writing multiple sequential bytes!
@@ -629,6 +753,33 @@ asmsub vpoke_mask(ubyte bank @A, uword address @R0, ubyte mask @X, ubyte value @
}}
}
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda cx16.r0,y
sta _cx16_vreg_storage,y
dey
bpl -
rts
_cx16_vreg_storage
.word 0,0,0,0,0,0,0,0
.word 0,0,0,0,0,0,0,0
}}
}
asmsub restore_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31
- lda save_virtual_registers._cx16_vreg_storage,y
sta cx16.r0,y
dey
bpl -
rts
}}
}
asmsub save_vera_context() clobbers(A) {
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
%asm {{
@@ -683,6 +834,8 @@ asmsub restore_vera_context() clobbers(A) {
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 16 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
asmsub init_system() {
@@ -712,12 +865,13 @@ asmsub init_system() {
lda #$90 ; black
jsr cbm.CHROUT
lda #1
sta $00 ; select ram bank 1
jsr cbm.CHROUT ; swap fg/bg
lda #$9e ; yellow
jsr cbm.CHROUT
lda #147 ; clear screen
jsr cbm.CHROUT
lda #PROG8_VARSHIGH_RAMBANK
sta $00 ; select ram bank
lda #0
tax
tay
@@ -735,6 +889,8 @@ asmsub init_system_phase2() {
sta restore_irq._orig_irqvec
lda cx16.CINV+1
sta restore_irq._orig_irqvec+1
lda #PROG8_VARSHIGH_RAMBANK
sta $00 ; select ram bank
cli
rts
}}
@@ -906,8 +1062,8 @@ asmsub set_rasterline(uword line @AY) {
%asm {{
sei
ldx #$42
ldy #1
tya
ldy #2
lda #0
jsr cx16.i2c_write_byte
bra *
}}
+3 -1
View File
@@ -6,6 +6,8 @@
txt {
%option no_symbol_prefixing
const ubyte DEFAULT_WIDTH = 80
const ubyte DEFAULT_HEIGHT = 60
@@ -236,7 +238,7 @@ _lx ldx #0 ; modified
}}
}
asmsub scroll_right() clobbers(A) {
asmsub scroll_right() clobbers(A,Y) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
%asm {{
+2
View File
@@ -3,6 +3,8 @@
%import textio
cx16logo {
%option no_symbol_prefixing
sub logo_at(ubyte column, ubyte row) {
uword strptr
for strptr in logo_lines {
+10 -3
View File
@@ -5,6 +5,7 @@
%import syslib
diskio {
%option no_symbol_prefixing
ubyte drivenumber = 8
@@ -84,8 +85,8 @@ io_error:
if_cs
goto io_error
repeat 6 {
void cbm.CHRIN() ; skip the 6 prologue bytes
while cbm.CHRIN()!='"' {
; skip up to entry name
}
if cbm.READST()!=0
goto io_error
@@ -93,8 +94,14 @@ io_error:
cx16.r0 = &list_filename
repeat {
@(cx16.r0) = cbm.CHRIN()
if @(cx16.r0)==0
if @(cx16.r0)=='"' {
@(cx16.r0) = ' '
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
@(cx16.r0) = 0
cx16.r0--
}
break
}
cx16.r0++
}
okay = true
+1 -1
View File
@@ -1,6 +1,6 @@
floats {
; the floating point functions shared across compiler targets
%option merge
%option merge, no_symbol_prefixing
sub print_f(float value) {
; ---- prints the floating point value (without a newline).
+3 -2
View File
@@ -51,8 +51,7 @@ multiply_bytes_into_word .proc
multiply_words .proc
; -- multiply two 16-bit words into a 32-bit result (signed and unsigned)
; input: A/Y = first 16-bit number, P8ZP_SCRATCH_W1 in ZP = second 16-bit number
; output: multiply_words.result 4-bytes/32-bits product, LSB order (low-to-high)
; clobbers: A
; output: multiply_words.result 4-bytes/32-bits product, LSB order (low-to-high) low 16 bits also in AY.
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
@@ -79,6 +78,8 @@ mult16 lda #0
dex
bne -
ldx P8ZP_SCRATCH_REG
lda result
ldy result+1
rts
result .byte 0,0,0,0
+294
View File
@@ -1,6 +1,8 @@
; Internal Math library routines - always included by the compiler
math {
%option no_symbol_prefixing
%asminclude "library:math.asm"
asmsub sin8u(ubyte angle @A) clobbers(Y) -> ubyte @A {
@@ -95,4 +97,296 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
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.
cx16.r0L = 3 ; quadrant
cx16.r1sL = x2-x1 ; xdelta
if_neg {
cx16.r0L--
cx16.r1sL = -cx16.r1sL
}
cx16.r2sL = y2-y1 ; ydelta
if_neg {
cx16.r0L-=2
cx16.r2sL = -cx16.r2sL
}
return direction_qd(cx16.r0L, cx16.r1L, cx16.r2L)
}
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.
cx16.r0L = 3 ; quadrant
if x2>=x1 {
cx16.r1L = x2-x1
} else {
cx16.r1L = x1-x2
cx16.r0L--
}
if y2>=y1 {
cx16.r2L = y2-y1
} else {
cx16.r2L = y1-y2
cx16.r0L -= 2
}
return direction_qd(cx16.r0L, cx16.r1L, cx16.r2L)
}
asmsub direction_qd(ubyte quadrant @A, ubyte xdelta @X, ubyte ydelta @Y) -> ubyte @A {
;Arctan https://github.com/dustmop/arctan24
; From a pair of X/Y deltas (both >=0), and quadrant 0-3, calculate discrete direction between 0 and 23 into A.
; .reg:a @in quadrant Number 0 to 3.
; .reg:x @in x_delta Delta for x direction.
; .reg:y @in y_delta Delta for y direction.
; Returns A as the direction (0-23).
%asm {{
x_delta = cx16.r0L
y_delta = cx16.r1L
quadrant = cx16.r2L
half_value = cx16.r3L
region_number = cx16.r4L
small = cx16.r5L
large = cx16.r5H
sta quadrant
sty y_delta
stx x_delta
cpx y_delta
bcs _XGreaterOrEqualY
_XLessY:
lda #16
sta region_number
stx small
sty large
bne _DetermineRegion
_XGreaterOrEqualY:
lda #0
sta region_number
stx large
sty small
_DetermineRegion:
; set A = small * 2.5
lda small
lsr a
sta half_value
lda small
asl a
bcs _SmallerQuotient
clc
adc half_value
bcs _SmallerQuotient
cmp large
bcc _LargerQuotient
; S * 2.5 > L
_SmallerQuotient:
; set A = S * 1.25
lsr half_value
lda small
clc
adc half_value
cmp large
bcc _Region1 ; if S * 1.25 < L then goto Region1 (L / S > 1.25)
bcs _Region0 ; (L / S < 1.25)
; S * 2.5 < L
_LargerQuotient:
; set A = S * 7.5
lda small
asl a
asl a
asl a
bcs _Region2
sec
sbc half_value
cmp large
bcc _Region3 ; if S * 7.5 < L then goto Region3 (L / S > 7.5)
jmp _Region2 ; (L / S < 7.5)
_Region0:
; L / S < 1.25. d=3,9,15,21
jmp _LookupResult
_Region1:
; 1.25 < L / S < 2.5. d=2,4,8,10,14,16,20,22
lda region_number
clc
adc #4
sta region_number
bpl _LookupResult
_Region2:
; 2.5 < L / S < 7.5. d=1,5,7,11,13,17,19,23
lda region_number
clc
adc #8
sta region_number
bpl _LookupResult
_Region3:
; 7.5 < L / S. d=0,6,12,18
lda region_number
clc
adc #12
sta region_number
_LookupResult:
lda quadrant
clc
adc region_number
tax
lda _quadrant_region_to_direction,x
rts
_quadrant_region_to_direction:
.byte 9, 3,15,21
.byte 10, 2,14,22
.byte 11, 1,13,23
.byte 12, 0,12, 0
.byte 9, 3,15,21
.byte 8, 4,16,20
.byte 7, 5,17,19
.byte 6, 6,18,18
}}
}
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
;; 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!
;; https://www.codebase64.org/doku.php?id=base:8bit_atan2_8-bit_angle
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
%asm {{
x1 = cx16.r0L
y1 = cx16.r1L
x2 = cx16.r2L
y2 = cx16.r3L
octant = cx16.r4L ;; temporary zeropage variable
lda x1
sec
sbc x2
bcs *+4
eor #$ff
tax
rol octant
lda y1
sec
sbc y2
bcs *+4
eor #$ff
tay
rol octant
lda log2_tab,x
sec
sbc log2_tab,y
bcc *+4
eor #$ff
tax
lda octant
rol a
and #%111
tay
lda atan_tab,x
eor octant_adjust,y
rts
octant_adjust
.byte %00111111 ;; x+,y+,|x|>|y|
.byte %00000000 ;; x+,y+,|x|<|y|
.byte %11000000 ;; x+,y-,|x|>|y|
.byte %11111111 ;; x+,y-,|x|<|y|
.byte %01000000 ;; x-,y+,|x|>|y|
.byte %01111111 ;; x-,y+,|x|<|y|
.byte %10111111 ;; x-,y-,|x|>|y|
.byte %10000000 ;; x-,y-,|x|<|y|
;;;;;;;; atan(2^(x/32))*128/pi ;;;;;;;;
atan_tab
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$01,$01,$01
.byte $01,$01,$01,$01,$01,$01,$01,$01
.byte $01,$01,$01,$01,$01,$01,$01,$01
.byte $01,$01,$01,$01,$01,$01,$01,$01
.byte $01,$01,$01,$01,$01,$02,$02,$02
.byte $02,$02,$02,$02,$02,$02,$02,$02
.byte $02,$02,$02,$02,$02,$02,$02,$02
.byte $03,$03,$03,$03,$03,$03,$03,$03
.byte $03,$03,$03,$03,$03,$04,$04,$04
.byte $04,$04,$04,$04,$04,$04,$04,$04
.byte $05,$05,$05,$05,$05,$05,$05,$05
.byte $06,$06,$06,$06,$06,$06,$06,$06
.byte $07,$07,$07,$07,$07,$07,$08,$08
.byte $08,$08,$08,$08,$09,$09,$09,$09
.byte $09,$0a,$0a,$0a,$0a,$0b,$0b,$0b
.byte $0b,$0c,$0c,$0c,$0c,$0d,$0d,$0d
.byte $0d,$0e,$0e,$0e,$0e,$0f,$0f,$0f
.byte $10,$10,$10,$11,$11,$11,$12,$12
.byte $12,$13,$13,$13,$14,$14,$15,$15
.byte $15,$16,$16,$17,$17,$17,$18,$18
.byte $19,$19,$19,$1a,$1a,$1b,$1b,$1c
.byte $1c,$1c,$1d,$1d,$1e,$1e,$1f,$1f
;;;;;;;; log2(x)*32 ;;;;;;;;
log2_tab
.byte $00,$00,$20,$32,$40,$4a,$52,$59
.byte $60,$65,$6a,$6e,$72,$76,$79,$7d
.byte $80,$82,$85,$87,$8a,$8c,$8e,$90
.byte $92,$94,$96,$98,$99,$9b,$9d,$9e
.byte $a0,$a1,$a2,$a4,$a5,$a6,$a7,$a9
.byte $aa,$ab,$ac,$ad,$ae,$af,$b0,$b1
.byte $b2,$b3,$b4,$b5,$b6,$b7,$b8,$b9
.byte $b9,$ba,$bb,$bc,$bd,$bd,$be,$bf
.byte $c0,$c0,$c1,$c2,$c2,$c3,$c4,$c4
.byte $c5,$c6,$c6,$c7,$c7,$c8,$c9,$c9
.byte $ca,$ca,$cb,$cc,$cc,$cd,$cd,$ce
.byte $ce,$cf,$cf,$d0,$d0,$d1,$d1,$d2
.byte $d2,$d3,$d3,$d4,$d4,$d5,$d5,$d5
.byte $d6,$d6,$d7,$d7,$d8,$d8,$d9,$d9
.byte $d9,$da,$da,$db,$db,$db,$dc,$dc
.byte $dd,$dd,$dd,$de,$de,$de,$df,$df
.byte $df,$e0,$e0,$e1,$e1,$e1,$e2,$e2
.byte $e2,$e3,$e3,$e3,$e4,$e4,$e4,$e5
.byte $e5,$e5,$e6,$e6,$e6,$e7,$e7,$e7
.byte $e7,$e8,$e8,$e8,$e9,$e9,$e9,$ea
.byte $ea,$ea,$ea,$eb,$eb,$eb,$ec,$ec
.byte $ec,$ec,$ed,$ed,$ed,$ed,$ee,$ee
.byte $ee,$ee,$ef,$ef,$ef,$ef,$f0,$f0
.byte $f0,$f1,$f1,$f1,$f1,$f1,$f2,$f2
.byte $f2,$f2,$f3,$f3,$f3,$f3,$f4,$f4
.byte $f4,$f4,$f5,$f5,$f5,$f5,$f5,$f6
.byte $f6,$f6,$f6,$f7,$f7,$f7,$f7,$f7
.byte $f8,$f8,$f8,$f8,$f9,$f9,$f9,$f9
.byte $f9,$fa,$fa,$fa,$fa,$fa,$fb,$fb
.byte $fb,$fb,$fb,$fc,$fc,$fc,$fc,$fc
.byte $fd,$fd,$fd,$fd,$fd,$fd,$fe,$fe
.byte $fe,$fe,$fe,$ff,$ff,$ff,$ff,$ff
}}
}
}
+1 -2
View File
@@ -167,9 +167,8 @@ mul_word .proc
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.multiply_words
lda math.multiply_words.result
sta P8ESTACK_LO+1,x
lda math.multiply_words.result+1
tya
sta P8ESTACK_HI+1,x
rts
.pend
+2
View File
@@ -1,6 +1,8 @@
; Internal library routines - always included by the compiler
prog8_lib {
%option no_symbol_prefixing
%asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm"
}
+1
View File
@@ -1,6 +1,7 @@
; 0-terminated string manipulation routines.
string {
%option no_symbol_prefixing
asmsub length(uword string @AY) clobbers(A) -> ubyte @Y {
; Returns the number of bytes in the string.
+1
View File
@@ -3,6 +3,7 @@
%import textio
test_stack {
%option no_symbol_prefixing
asmsub test() {
%asm {{
+22 -18
View File
@@ -106,19 +106,21 @@ sub str_uwhex (uword value) {
sub str_uw0 (uword value) {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
ubyte tenthousands = (value / 10000) as ubyte
value -= 10000*tenthousands
ubyte thousands = (value / 1000) as ubyte
value -= 1000*thousands
ubyte hundreds = (value / 100) as ubyte
value -= 100 as uword * hundreds
ubyte tens = (value / 10) as ubyte
value -= 10*tens
uword value2 = value/10
ubyte digits = value-value2*10 as ubyte
uword value3 = value2/10
ubyte tens = value2-value3*10 as ubyte
uword value4 = value3/10
ubyte hundreds = value3-value4*10 as ubyte
uword value5 = value4/10
ubyte thousands = value4-value5*10 as ubyte
uword value6 = value5/10
ubyte tenthousands = value5-value6*10 as ubyte
string_out[0] = tenthousands+'0'
string_out[1] = thousands+'0'
string_out[2] = hundreds+'0'
string_out[3] = tens+'0'
string_out[4] = value as ubyte + '0'
string_out[4] = digits+'0'
string_out[5] = 0
}
@@ -139,14 +141,16 @@ sub str_w (word value) {
}
sub internal_str_uw(uword value, uword out_ptr) {
ubyte tenthousands = (value / 10000) as ubyte
value -= 10000*tenthousands
ubyte thousands = (value / 1000) as ubyte
value -= 1000*thousands
ubyte hundreds = (value / 100) as ubyte
value -= 100 as uword * hundreds
ubyte tens = (value / 10) as ubyte
value -= 10*tens
uword value2 = value/10
ubyte digits = value-value2*10 as ubyte
uword value3 = value2/10
ubyte tens = value2-value3*10 as ubyte
uword value4 = value3/10
ubyte hundreds = value3-value4*10 as ubyte
uword value5 = value4/10
ubyte thousands = value4-value5*10 as ubyte
uword value6 = value5/10
ubyte tenthousands = value5-value6*10 as ubyte
if tenthousands
goto output_tenthousands
if thousands
@@ -169,7 +173,7 @@ output_tens:
@(out_ptr) = tens+'0'
out_ptr++
output_ones:
@(out_ptr) = value as ubyte + '0'
@(out_ptr) = digits+'0'
out_ptr++
@(out_ptr) = 0
}
+1
View File
@@ -0,0 +1 @@
; there is no diskio yet for the VM target.
+57
View File
@@ -182,4 +182,61 @@ math {
return
}}
}
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
}}
}
}
+39
View File
@@ -218,4 +218,43 @@ cx16 {
&byte r13sH = $ff1d
&byte r14sH = $ff1f
&byte r15sH = $ff21
sub save_virtual_registers() {
uword[32] storage
storage[0] = r0
storage[1] = r1
storage[2] = r2
storage[3] = r3
storage[4] = r4
storage[5] = r5
storage[6] = r6
storage[7] = r7
storage[8] = r8
storage[9] = r9
storage[10] = r10
storage[11] = r11
storage[12] = r12
storage[13] = r13
storage[14] = r14
storage[15] = r15
}
sub restore_virtual_registers() {
r0 = cx16.save_virtual_registers.storage[0]
r1 = cx16.save_virtual_registers.storage[1]
r2 = cx16.save_virtual_registers.storage[2]
r3 = cx16.save_virtual_registers.storage[3]
r4 = cx16.save_virtual_registers.storage[4]
r5 = cx16.save_virtual_registers.storage[5]
r6 = cx16.save_virtual_registers.storage[6]
r7 = cx16.save_virtual_registers.storage[7]
r8 = cx16.save_virtual_registers.storage[8]
r9 = cx16.save_virtual_registers.storage[9]
r10 = cx16.save_virtual_registers.storage[10]
r11 = cx16.save_virtual_registers.storage[11]
r12 = cx16.save_virtual_registers.storage[12]
r13 = cx16.save_virtual_registers.storage[13]
r14 = cx16.save_virtual_registers.storage[14]
r15 = cx16.save_virtual_registers.storage[15]
}
}
-1
View File
@@ -1 +0,0 @@
9.0
+28 -13
View File
@@ -20,8 +20,10 @@ import kotlin.system.exitProcess
fun main(args: Array<String>) {
val buildVersion = object {}.javaClass.getResource("/version.txt")?.readText()?.trim()
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("\nProg8 compiler v${prog8.buildversion.VERSION} by Irmen de Jong (irmen@razorvine.net)")
if('-' in prog8.buildversion.VERSION) {
println("Prerelease version from git commit ${prog8.buildversion.GIT_SHA.take(8)} in branch ${prog8.buildversion.GIT_BRANCH}")
}
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
val succes = compileMain(args)
@@ -48,11 +50,10 @@ private fun compileMain(args: Array<String>): Boolean {
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").required()
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}') (required)")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
val varsHigh by cli.option(ArgType.Boolean, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program")
val useNewExprCode by cli.option(ArgType.Boolean, fullName = "newexpr", description = "use new expression code-gen (experimental)")
val varsHighBank by cli.option(ArgType.Int, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program. On the cx16 target the value specifies the HiRAM bank (0=keep active), on other systems it is ignored.")
val splitWordArrays by cli.option(ArgType.Boolean, fullName = "splitarrays", description = "treat all word arrays as tagged with @split to make them lsb/msb split in memory")
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
@@ -79,8 +80,20 @@ private fun compileMain(args: Array<String>): Boolean {
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME, VMTarget.NAME)) {
System.err.println("Invalid compilation target: $compilationTarget")
if(startVm==null) {
if(compilationTarget==null) {
System.err.println("No compilation target specified")
return false
}
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME, VMTarget.NAME)) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
}
if(varsHighBank==0 && compilationTarget==Cx16Target.NAME) {
System.err.println("On the Commander X16, HiRAM bank 0 is used by the kernal and can't be used.")
return false
}
@@ -127,9 +140,8 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
varsHigh == true,
useNewExprCode == true,
compilationTarget,
varsHighBank,
compilationTarget!!,
evalStackAddr,
splitWordArrays == true,
processedSymbols,
@@ -180,6 +192,10 @@ private fun compileMain(args: Array<String>): Boolean {
}
} else {
if((startEmulator1==true || startEmulator2==true) && moduleFiles.size>1) {
System.err.println("can't start emulator when multiple module files are specified")
return false
}
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
@@ -193,9 +209,8 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
varsHigh == true,
useNewExprCode == true,
compilationTarget,
varsHighBank,
compilationTarget!!,
evalStackAddr,
splitWordArrays == true,
processedSymbols,
@@ -0,0 +1,15 @@
package prog8.buildversion
/**
* Automatically generated file containing build version information.
*/
const val MAVEN_GROUP = "prog8"
const val MAVEN_NAME = "compiler"
const val VERSION = "9.2-SNAPSHOT"
const val GIT_REVISION = 3964
const val GIT_SHA = "aaa30e4a583fa7c8da61998cf7cbb2a60b52afb0"
const val GIT_DATE = "2023-07-16T21:16:18Z"
const val GIT_BRANCH = "master"
const val BUILD_DATE = "2023-07-16T21:16:23Z"
const val BUILD_UNIX_TIME = 1689542183583L
const val DIRTY = 1
+6 -205
View File
@@ -4,12 +4,11 @@ import com.github.michaelbull.result.onFailure
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive
import prog8.code.SymbolTableMaker
import prog8.code.ast.*
import prog8.code.ast.PtProgram
import prog8.code.core.*
import prog8.code.target.*
import prog8.codegen.vm.VmCodeGen
@@ -36,8 +35,7 @@ class CompilerArguments(val filepath: Path,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val experimentalCodegen: Boolean,
val varsHigh: Boolean,
val useNewExprCode: Boolean,
val varsHighBank: Int?,
val compilationTarget: String,
val evalStackBaseAddress: UInt?,
val splitWordArrays: Boolean,
@@ -78,8 +76,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen
varsHigh = args.varsHigh
useNewExprCode = args.useNewExprCode
varsHighBank = args.varsHighBank
evalStackBaseAddress = args.evalStackBaseAddress
splitWordArrays = args.splitWordArrays
outputDir = args.outputDir.normalize()
@@ -371,8 +368,8 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(errors, compTarget)
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
val optsDone4 = program.inlineSubroutines()
val optsDone3 = program.optimizeStatements(errors, functions, compilerOptions)
val optsDone4 = program.inlineSubroutines(compilerOptions)
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 + optsDone4 == 0)
@@ -406,23 +403,12 @@ private fun createAssemblyAndAssemble(program: PtProgram,
val asmgen = if(compilerOptions.experimentalCodegen)
prog8.codegen.experimental.ExperiCodeGen()
else if (compilerOptions.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
prog8.codegen.cpu6502.AsmGen6502()
prog8.codegen.cpu6502.AsmGen6502(prefixSymbols = true)
else if (compilerOptions.compTarget.name == VMTarget.NAME)
VmCodeGen()
else
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}")
if(compilerOptions.useNewExprCode) {
if(compilerOptions.compTarget.machine.cpu !in arrayOf(CpuType.CPU6502, CpuType.CPU65c02)) {
// the IR code gen backend has its own, better, version of dealing with binary expressions.
throw IllegalArgumentException("'newexpr' expression rewrite should not be used with compilation target ${compilerOptions.compTarget.name}")
}
transformNewExpressions(program)
}
// printAst(program, true) { println(it) }
val stMaker = SymbolTableMaker(program, compilerOptions)
val symbolTable = stMaker.make()
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
@@ -434,188 +420,3 @@ private fun createAssemblyAndAssemble(program: PtProgram,
false
}
}
private fun transformNewExpressions(program: PtProgram) {
val newVariables = mutableMapOf<PtSub, MutableList<PtVariable>>()
var countByteVars = 0
var countWordVars = 0
var countFloatVars = 0
// TODO: find a reliable way to reuse more temp vars across expressions
fun getExprVar(type: DataType, pos: Position, scope: PtSub): PtIdentifier {
val count = when(type) {
in ByteDatatypes -> {
countByteVars++
countByteVars
}
in WordDatatypes -> {
countWordVars++
countWordVars
}
DataType.FLOAT -> {
countFloatVars++
countFloatVars
}
else -> throw FatalAstException("weird dt")
}
val name = "p8p_exprvar_${count}_${type.toString().lowercase()}"
var subVars = newVariables[scope]
if(subVars==null) {
subVars = mutableListOf()
newVariables[scope] = subVars
}
if(subVars.all { it.name!=name }) {
subVars.add(PtVariable(name, type, ZeropageWish.DONTCARE, null, null, pos))
}
return PtIdentifier("${scope.scopedName}.$name", type, pos)
}
fun transformExpr(expr: PtBinaryExpression): Pair<PtExpression, List<IPtAssignment>> {
// depth first process the expression tree
val scope = expr.definingSub()!!
val assignments = mutableListOf<IPtAssignment>()
fun transformOperand(node: PtExpression): PtNode {
return when(node) {
is PtNumber, is PtIdentifier, is PtArray, is PtString, is PtMachineRegister -> node
is PtBinaryExpression -> {
val (replacement, subAssigns) = transformExpr(node)
assignments.addAll(subAssigns)
replacement
}
else -> {
val variable = getExprVar(node.type, node.position, scope)
val assign = PtAssignment(node.position)
val target = PtAssignTarget(variable.position)
target.add(variable)
assign.add(target)
assign.add(node)
assignments.add(assign)
variable
}
}
}
val newLeft = transformOperand(expr.left)
val newRight = transformOperand(expr.right)
// process the binexpr
val resultVar =
if(expr.type == expr.left.type) {
getExprVar(expr.type, expr.position, scope)
} else {
if(expr.operator in ComparisonOperators && expr.type in ByteDatatypes) {
// this is very common and should be dealth with correctly; byte==0, word>42
val varType = if(expr.left.type in PassByReferenceDatatypes) DataType.UWORD else expr.left.type
getExprVar(varType, expr.position, scope)
}
else if(expr.left.type in PassByReferenceDatatypes && expr.type==DataType.UBYTE) {
// this is common and should be dealth with correctly; for instance "name"=="john"
val varType = if (expr.left.type in PassByReferenceDatatypes) DataType.UWORD else expr.left.type
getExprVar(varType, expr.position, scope)
} else if(expr.left.type equalsSize expr.type) {
getExprVar(expr.type, expr.position, scope)
} else {
TODO("expression type differs from left operand type! got ${expr.left.type} expected ${expr.type} ${expr.position}")
}
}
if(resultVar.name!=(newLeft as? PtIdentifier)?.name) {
// resultvar = left
val assign1 = PtAssignment(newLeft.position)
val target1 = PtAssignTarget(resultVar.position)
target1.add(resultVar)
assign1.add(target1)
assign1.add(newLeft)
assignments.add(assign1)
}
// resultvar {oper}= right
val operator = if(expr.operator in ComparisonOperators) expr.operator else expr.operator+'='
val assign2 = PtAugmentedAssign(operator, newRight.position)
val target2 = PtAssignTarget(resultVar.position)
target2.add(resultVar.copy())
assign2.add(target2)
assign2.add(newRight)
assignments.add(assign2)
return Pair(resultVar, assignments)
}
fun isProperStatement(node: PtNode): Boolean {
return when(node) {
is PtAssignment -> true
is PtAugmentedAssign -> true
is PtBreakpoint -> true
is PtConditionalBranch -> true
is PtForLoop -> true
is PtIfElse -> true
is PtIncludeBinary -> true
is PtInlineAssembly -> true
is PtJump -> true
is PtAsmSub -> true
is PtLabel -> true
is PtSub -> true
is PtVariable -> true
is PtNop -> true
is PtPostIncrDecr -> true
is PtRepeatLoop -> true
is PtReturn -> true
is PtWhen -> true
is PtBuiltinFunctionCall -> node.void
is PtFunctionCall -> node.void
else -> false
}
}
fun transform(node: PtNode, parent: PtNode) {
if(node is PtBinaryExpression) {
node.children.toTypedArray().forEach {
transform(it, node)
}
val (rep, assignments) = transformExpr(node)
var replacement = rep
if(!(rep.type equalsSize node.type)) {
if(rep.type in NumericDatatypes && node.type in ByteDatatypes) {
replacement = PtTypeCast(node.type, node.position)
replacement.add(rep)
} else
TODO("cast replacement type ${rep.type} -> ${node.type}")
}
var idx = parent.children.indexOf(node)
parent.children[idx] = replacement
replacement.parent = parent
// find the statement above which we should insert the assignments
var stmt = node
while(!isProperStatement(stmt))
stmt = stmt.parent
idx = stmt.parent.children.indexOf(stmt)
assignments.reversed().forEach {
stmt.parent.add(idx, it as PtNode)
}
} else {
node.children.toTypedArray().forEach { child -> transform(child, node) }
}
}
program.allBlocks().forEach { block ->
block.children.toTypedArray().forEach {
transform(it, block)
}
}
// add the new variables
newVariables.forEach { (sub, vars) ->
vars.forEach {
sub.add(0, it)
}
}
// extra check to see that all PtBinaryExpressions have been transformed
fun binExprCheck(node: PtNode) {
if(node is PtBinaryExpression)
throw IllegalArgumentException("still got binexpr $node ${node.position}")
node.children.forEach { binExprCheck(it) }
}
binExprCheck(program)
}
@@ -21,6 +21,10 @@ internal class ErrorReporter: IErrorReporter {
messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
}
override fun undefined(symbol: List<String>, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun report() {
var numErrors = 0
var numWarnings = 0
@@ -1,49 +0,0 @@
package prog8.compiler.astprocessing
import prog8.ast.statements.Block
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
import prog8.code.core.ICompilationTarget
import prog8.code.target.VMTarget
class AsmInstructionNamesFinder(val target: ICompilationTarget): IAstVisitor {
val blocks = mutableSetOf<Block>()
val variables = mutableSetOf<VarDecl>()
val labels = mutableSetOf<Label>()
val subroutines = mutableSetOf<Subroutine>()
private fun isPossibleInstructionName(name: String) = name.length==3 && name.all { it.isLetter() }
fun foundAny(): Boolean = blocks.isNotEmpty() || variables.isNotEmpty() || subroutines.isNotEmpty() || labels.isNotEmpty()
override fun visit(block: Block) {
if(target.name!=VMTarget.NAME && !block.isInLibrary && isPossibleInstructionName(block.name)) {
blocks += block
}
super.visit(block)
}
override fun visit(decl: VarDecl) {
if(target.name!=VMTarget.NAME && !decl.definingModule.isLibrary && isPossibleInstructionName(decl.name)) {
variables += decl
}
super.visit(decl)
}
override fun visit(label: Label) {
if(target.name!=VMTarget.NAME && !label.definingModule.isLibrary && isPossibleInstructionName(label.name)) {
labels += label
}
super.visit(label)
}
override fun visit(subroutine: Subroutine) {
if(target.name!=VMTarget.NAME && !subroutine.definingModule.isLibrary && isPossibleInstructionName(subroutine.name)) {
subroutines += subroutine
}
super.visit(subroutine)
}
}
@@ -1,101 +0,0 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AsmInstructionNamesReplacer(
val blocks: Set<Block>,
val subroutines: Set<Subroutine>,
val variables: Set<VarDecl>,
val labels: Set<Label>): AstWalker() {
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
val newName = identifier.nameInSource.map { ident ->
if(ident.length==3 && !identifier.definingModule.isLibrary) {
val blockTarget = blocks.firstOrNull { it.name==ident }
val subTarget = subroutines.firstOrNull {it.name==ident }
val varTarget = variables.firstOrNull { it.name==ident }
val labelTarget = labels.firstOrNull { it.name==ident}
if(blockTarget!=null || subTarget!=null || varTarget!=null || labelTarget!=null) {
"p8p_$ident"
} else
ident
} else
ident
}
return if(newName!=identifier.nameInSource)
listOf(IAstModification.ReplaceNode(identifier, IdentifierReference(newName, identifier.position), parent))
else
noModifications
}
override fun after(label: Label, parent: Node): Iterable<IAstModification> {
return if(label in labels)
listOf(IAstModification.ReplaceNode(label, Label("p8p_${label.name}", label.position), parent))
else
noModifications
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
return if(block in blocks)
listOf(IAstModification.ReplaceNode(block, Block("p8p_${block.name}", block.address, block.statements, block.isInLibrary, block.position), parent))
else
noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
return if(decl in variables)
listOf(IAstModification.ReplaceNode(decl, decl.renamed("p8p_${decl.name}"), parent))
else
noModifications
}
private val subsWithParamRefsToFix = mutableListOf<Subroutine>()
override fun applyModifications(): Int {
var count = super.applyModifications()
subsWithParamRefsToFix.forEach { subroutine ->
subroutine.statements.withIndex().reversed().forEach { (index,stmt) ->
if(stmt is VarDecl && stmt.origin==VarDeclOrigin.SUBROUTINEPARAM) {
val param = subroutine.parameters.single { it.name == stmt.name}
val decl = VarDecl.fromParameter(param)
subroutine.statements[index] = decl
decl.linkParents(subroutine)
count++
}
}
}
return count
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val changedParams = mutableListOf<Pair<Int, SubroutineParameter>>()
subroutine.parameters.withIndex().forEach { (index, param) ->
if(param.name.length==3 && param.name.all { it.isLetter() } && !param.definingModule.isLibrary) {
changedParams.add(index to SubroutineParameter("p8p_${param.name}", param.type, param.position))
}
}
changedParams.forEach { (index, newParam) -> subroutine.parameters[index] = newParam }
val newName = if(subroutine in subroutines) "p8p_${subroutine.name}" else subroutine.name
return if(newName!=subroutine.name || changedParams.isNotEmpty()) {
val newSub = Subroutine(newName, subroutine.parameters, subroutine.returntypes,
subroutine.asmParameterRegisters, subroutine.asmReturnvaluesRegisters, subroutine.asmClobbers, subroutine.asmAddress, subroutine.isAsmSubroutine,
subroutine.inline, false, subroutine.statements, subroutine.position)
if(changedParams.isNotEmpty())
subsWithParamRefsToFix += newSub
listOf(IAstModification.ReplaceNode(subroutine, newSub, parent))
} else {
if(changedParams.isNotEmpty())
subsWithParamRefsToFix += subroutine
noModifications
}
}
}
@@ -12,6 +12,7 @@ import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException
import java.io.File
import kotlin.io.path.Path
import kotlin.math.floor
/**
* Semantic analysis.
@@ -62,7 +63,7 @@ internal class AstChecker(private val program: Program,
override fun visit(identifier: IdentifierReference) {
val stmt = identifier.targetStatement(program)
if(stmt==null)
errors.err("undefined symbol: ${identifier.nameInSource.joinToString(".")}", identifier.position)
errors.undefined(identifier.nameInSource, identifier.position)
else {
val target = stmt as? VarDecl
if (target != null && target.origin == VarDeclOrigin.SUBROUTINEPARAM) {
@@ -114,6 +115,8 @@ internal class AstChecker(private val program: Program,
if (returnValue == null || returnValue.type != DataType.UBYTE || (returnValue.number!=0.0 && returnValue.number!=1.0)) {
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}",returnStmt.value!!.position)
}
} else if(valueDt.isIterable && expectedReturnValues[0]==DataType.UWORD) {
// you can return a string or array when a uword (pointer) is returned
}
else {
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}",returnStmt.value!!.position)
@@ -126,8 +129,11 @@ internal class AstChecker(private val program: Program,
override fun visit(ifElse: IfElse) {
val dt = ifElse.condition.inferType(program)
if(!dt.isInteger && !dt.istype(DataType.BOOL))
errors.err("condition value should be an integer type or bool", ifElse.condition.position)
if(!dt.isInteger && !dt.istype(DataType.BOOL)) {
val identifier = ifElse.condition as? IdentifierReference
if(identifier==null || identifier.targetStatement(program)!=null)
errors.err("condition value should be an integer type or bool", ifElse.condition.position)
}
super.visit(ifElse)
}
@@ -443,22 +449,30 @@ internal class AstChecker(private val program: Program,
override fun visit(untilLoop: UntilLoop) {
val dt = untilLoop.condition.inferType(program)
if(!dt.isInteger && !dt.istype(DataType.BOOL))
errors.err("condition value should be an integer type or bool", untilLoop.condition.position)
if(!dt.isInteger && !dt.istype(DataType.BOOL)) {
val identifier = untilLoop.condition as? IdentifierReference
if(identifier==null || identifier.targetStatement(program)!=null)
errors.err("condition value should be an integer type or bool", untilLoop.condition.position)
}
super.visit(untilLoop)
}
override fun visit(whileLoop: WhileLoop) {
val dt = whileLoop.condition.inferType(program)
if(!dt.isInteger && !dt.istype(DataType.BOOL))
errors.err("condition value should be an integer type or bool", whileLoop.condition.position)
if(!dt.isInteger && !dt.istype(DataType.BOOL)) {
val identifier = whileLoop.condition as? IdentifierReference
if(identifier==null || identifier.targetStatement(program)!=null)
errors.err("condition value should be an integer type or bool", whileLoop.condition.position)
}
super.visit(whileLoop)
}
override fun visit(repeatLoop: RepeatLoop) {
val iterations = repeatLoop.iterations?.constValue(program)
if(iterations != null && iterations.number.toInt() > 65535)
errors.err("repeat cannot go over 65535 iterations", iterations.position)
if (iterations != null) {
require(floor(iterations.number)==iterations.number)
if (iterations.number.toInt() > 65535) errors.err("repeat cannot go over 65535 iterations", iterations.position)
}
val ident = repeatLoop.iterations as? IdentifierReference
if(ident!=null) {
@@ -522,7 +536,7 @@ internal class AstChecker(private val program: Program,
val targetName = targetIdentifier.nameInSource
when (val targetSymbol = assignment.definingScope.lookup(targetName)) {
null -> {
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
errors.undefined(targetIdentifier.nameInSource, targetIdentifier.position)
return
}
!is VarDecl -> {
@@ -732,13 +746,13 @@ internal class AstChecker(private val program: Program,
"%output" -> {
if(directive.parent !is Module)
err("this directive may only occur at module level")
if(directive.args.size!=1 || directive.args[0].name !in OutputType.values().map {it.name.lowercase()})
if(directive.args.size!=1 || directive.args[0].name !in OutputType.entries.map {it.name.lowercase()})
err("invalid output directive type")
}
"%launcher" -> {
if(directive.parent !is Module)
err("this directive may only occur at module level")
if(directive.args.size!=1 || directive.args[0].name !in CbmPrgLauncherType.values().map{it.name.lowercase()})
if(directive.args.size!=1 || directive.args[0].name !in CbmPrgLauncherType.entries.map{it.name.lowercase()})
err("invalid launcher directive type")
}
"%zeropage" -> {
@@ -803,7 +817,7 @@ 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")}.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")}.any { !it })
err("invalid option directive argument(s)")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
@@ -966,6 +980,8 @@ internal class AstChecker(private val program: Program,
// expression with one side BOOL other side (U)BYTE is allowed; bool==byte
} else if((expr.operator == "<<" || expr.operator == ">>") && (leftDt in WordDatatypes && rightDt in ByteDatatypes)) {
// exception allowed: shifting a word by a byte
} else if((leftDt==DataType.UWORD && rightDt==DataType.STR) || (leftDt==DataType.STR && rightDt==DataType.UWORD)) {
// exception allowed: comparing uword (pointer) with string
} else {
errors.err("left and right operands aren't the same type", expr.left.position)
}
@@ -1235,7 +1251,7 @@ internal class AstChecker(private val program: Program,
val target = postIncrDecr.definingScope.lookup(targetName)
if(target==null) {
val symbol = postIncrDecr.target.identifier!!
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
errors.undefined(symbol.nameInSource, symbol.position)
} else {
if(target !is VarDecl || target.type== VarDeclType.CONST) {
errors.err("can only increment or decrement a variable", postIncrDecr.position)
@@ -1325,8 +1341,14 @@ internal class AstChecker(private val program: Program,
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
conditionType isnot constvalue.type -> {
if(conditionType.isKnown)
errors.err("choice value datatype differs from condition value", whenChoice.position)
if(conditionType.isKnown) {
if(conditionType.istype(DataType.BOOL)) {
if(constvalue.number!=0.0 && constvalue.number!=1.0)
errors.err("choice value datatype differs from condition value", whenChoice.position)
} else {
errors.err("choice value datatype differs from condition value", whenChoice.position)
}
}
}
}
}
@@ -1368,6 +1390,32 @@ internal class AstChecker(private val program: Program,
super.visit(containment)
}
override fun visit(memread: DirectMemoryRead) {
if(!memread.addressExpression.inferType(program).istype(DataType.UWORD)) {
errors.err("address for memory access isn't uword", memread.position)
}
val tc = memread.addressExpression as? TypecastExpression
if(tc!=null && tc.implicit) {
if(!tc.expression.inferType(program).istype(DataType.UWORD)) {
errors.err("address for memory access isn't uword", memread.position)
}
}
super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
if(!memwrite.addressExpression.inferType(program).istype(DataType.UWORD)) {
errors.err("address for memory access isn't uword", memwrite.position)
}
val tc = memwrite.addressExpression as? TypecastExpression
if(tc!=null && tc.implicit) {
if(!tc.expression.inferType(program).istype(DataType.UWORD)) {
errors.err("address for memory access isn't uword", memwrite.position)
}
}
super.visit(memwrite)
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
when (val targetStatement = target.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
@@ -1381,7 +1429,7 @@ internal class AstChecker(private val program: Program,
else
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
}
null -> errors.err("undefined symbol: ${target.nameInSource.joinToString(".")}", target.position)
null -> errors.undefined(target.nameInSource, target.position)
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
}
return null
@@ -12,7 +12,6 @@ import prog8.ast.statements.VarDeclOrigin
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
@@ -28,20 +27,6 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
boolRemover.visit(this)
boolRemover.applyModifications()
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
val finder = AsmInstructionNamesFinder(compilerOptions.compTarget)
finder.visit(this)
if(finder.foundAny()) {
val replacer = AsmInstructionNamesReplacer(
finder.blocks,
finder.subroutines,
finder.variables,
finder.labels)
replacer.visit(this)
replacer.applyModifications()
}
}
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
fixer.visit(this)
while (errors.noErrors() && fixer.applyModifications() > 0) {
@@ -36,19 +36,6 @@ internal class BeforeAsmAstChanger(val program: Program,
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator in ComparisonOperators && expr.left.inferType(program) istype DataType.STR && expr.right.inferType(program) istype DataType.STR) {
// replace string comparison expressions with calls to string.compare()
val stringCompare = BuiltinFunctionCall(
IdentifierReference(listOf("prog8_lib_stringcompare"), expr.position),
mutableListOf(expr.left.copy(), expr.right.copy()), expr.position)
val zero = NumericLiteral.optimalInteger(0, expr.position)
val comparison = BinaryExpression(stringCompare, expr.operator, zero, expr.position)
return listOf(IAstModification.ReplaceNode(expr, comparison, parent))
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
@@ -5,6 +5,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.ComparisonOperators
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
@@ -125,13 +126,21 @@ _after:
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
if(repeatLoop.iterations==null) {
// replace with a jump at the end, but make sure the jump is inserted *before* any subroutines that may occur inside this block
val subroutineMovements = mutableListOf<IAstModification>()
val subroutines = repeatLoop.body.statements.filterIsInstance<Subroutine>()
subroutines.forEach { sub ->
subroutineMovements += IAstModification.Remove(sub, sub.parent as IStatementContainer)
subroutineMovements += IAstModification.InsertLast(sub, sub.parent as IStatementContainer)
}
val label = program.makeLabel("repeat", repeatLoop.position)
val jump = program.jumpLabel(label)
return listOf(
IAstModification.InsertFirst(label, repeatLoop.body),
IAstModification.InsertLast(jump, repeatLoop.body),
IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent)
)
) + subroutineMovements
}
return noModifications
}
@@ -165,10 +174,32 @@ _after:
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
fun isStringComparison(leftDt: InferredTypes.InferredType, rightDt: InferredTypes.InferredType): Boolean =
if(leftDt istype DataType.STR && rightDt istype DataType.STR)
true
else
leftDt istype DataType.UWORD && rightDt istype DataType.STR || leftDt istype DataType.STR && rightDt istype DataType.UWORD
if(expr.operator=="in") {
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
}
if(expr.operator in ComparisonOperators) {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(isStringComparison(leftDt, rightDt)) {
// replace string comparison expressions with calls to string.compare()
val stringCompare = BuiltinFunctionCall(
IdentifierReference(listOf("prog8_lib_stringcompare"), expr.position),
mutableListOf(expr.left.copy(), expr.right.copy()), expr.position)
val zero = NumericLiteral.optimalInteger(0, expr.position)
val comparison = BinaryExpression(stringCompare, expr.operator, zero, expr.position)
return listOf(IAstModification.ReplaceNode(expr, comparison, parent))
}
}
return noModifications
}
}
@@ -161,21 +161,23 @@ class IntermediateAstMaker(private val program: Program, private val options: Co
private fun transform(srcBlock: Block): PtBlock {
var alignment = PtBlock.BlockAlignment.NONE
var forceOutput = false
var noSymbolPrefixing = false
val directives = srcBlock.statements.filterIsInstance<Directive>()
for (directive in directives.filter { it.directive == "%option" }) {
for (arg in directive.args) {
when (arg.name) {
"align_word" -> alignment = PtBlock.BlockAlignment.WORD
"align_page" -> alignment = PtBlock.BlockAlignment.PAGE
"no_symbol_prefixing" -> noSymbolPrefixing = true
"force_output" -> forceOutput=true
"merge", "splitarrays" -> { /* ignore this one */ }
"merge", "splitarrays" -> { /* ignore this one */ }
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, alignment, src, srcBlock.position)
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, noSymbolPrefixing, alignment, src, srcBlock.position)
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements)
block.add(transformStatement(stmt))
@@ -434,11 +436,10 @@ class IntermediateAstMaker(private val program: Program, private val options: Co
private fun transform(src: AddressOf): PtAddressOf {
val addr = PtAddressOf(src.position)
val (name, dt) = src.identifier.targetNameAndType(program)
if(dt in SplitWordArrayTypes) {
addr.add(PtIdentifier(name+"_lsb", dt, src.identifier.position))
} else {
if(dt in SplitWordArrayTypes)
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))
}
return addr
}
@@ -380,12 +380,14 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
// add a typecast to the return type if it doesn't match the subroutine's signature
// but only if no data loss occurs
val returnValue = returnStmt.value
if(returnValue!=null) {
val subroutine = returnStmt.definingSubroutine!!
if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program) istype subReturnType)
val returnDt = returnValue.inferType(program)
if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType))
return noModifications
if (returnValue is NumericLiteral) {
val cast = returnValue.cast(subroutine.returntypes.single())
@@ -402,17 +404,35 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
override fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
val conditionDt = (whenChoice.parent as When).condition.inferType(program)
if((parent as When).condition.inferType(program).isWords) {
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val num = value.constValue(program)
if(num!=null && num.type in ByteDatatypes) {
val wordNum = NumericLiteral(if(num.type==DataType.UBYTE) DataType.UWORD else DataType.WORD, num.number, num.position)
wordNum.parent = num.parent
values[index] = wordNum
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
val castedValue = (value as NumericLiteral).cast(conditionDt.getOr(DataType.UNDEFINED))
if(castedValue.isValid) {
values[index] = castedValue.valueOrZero()
} else {
errors.err("choice value datatype differs from condition value", value.position)
}
}
}
} else {
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
val castedValue = (value as NumericLiteral).cast(conditionDt.getOr(DataType.UNDEFINED))
if(castedValue.isValid) {
values[index] = castedValue.valueOrZero()
} else {
errors.err("choice value datatype differs from condition value", value.position)
}
}
}
}
return noModifications
}
@@ -194,8 +194,10 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
else
noModifications
}
else
else if(variable.value is ArrayLiteral) {
checkArray((variable.value as ArrayLiteral).value)
}
else noModifications
}
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
@@ -214,14 +216,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return checkArray(array)
}
is IdentifierReference -> {
val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)!!
when(variable.datatype) {
val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)
when(variable?.datatype) {
DataType.STR -> {
val stringVal = (variable.value as StringLiteral)
return checkString(stringVal)
}
in ArrayDatatypes -> {
return checkArray(variable)
return checkArray(variable!!)
}
else -> {}
}
@@ -260,5 +262,15 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
return noModifications
}
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
val removals = mutableListOf<Int>()
whenStmt.choices.withIndex().forEach { (index, choice) ->
if(choice.statements.isEmpty())
removals.add(index)
}
removals.reversed().forEach { whenStmt.choices.removeAt(it) }
return noModifications
}
}
@@ -67,8 +67,13 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
fun checkTypes(call: IFunctionCall, program: Program): Pair<String, Position>? {
val argITypes = call.args.map { it.inferType(program) }
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0)
return Pair("argument ${firstUnknownDt+1} invalid argument type", call.args[firstUnknownDt].position)
if(firstUnknownDt>=0) {
val identifier = call.args[0] as? IdentifierReference
return if(identifier==null || identifier.targetStatement(program)!=null)
Pair("argument ${firstUnknownDt + 1} invalid argument type", call.args[firstUnknownDt].position)
else
null
}
val argtypes = argITypes.map { it.getOr(DataType.UNDEFINED) }
val target = call.target.targetStatement(program)
if (target is Subroutine) {
+4 -4
View File
@@ -151,7 +151,7 @@ class TestModuleImporter: FunSpec({
shouldThrow<ParseError> { act() }.let {
it.position.file shouldBe SourceCode.relative(srcPath).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
@@ -171,7 +171,7 @@ class TestModuleImporter: FunSpec({
shouldThrow<ParseError> { act() }.let {
it.position.file shouldBe SourceCode.relative(imported).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
@@ -231,7 +231,7 @@ class TestModuleImporter: FunSpec({
importer.importImplicitLibraryModule(srcPath.nameWithoutExtension) }.let {
it.position.file shouldBe SourceCode.relative(srcPath).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
@@ -253,7 +253,7 @@ class TestModuleImporter: FunSpec({
act() }.let {
it.position.file shouldBe SourceCode.relative(imported).toString()
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 }
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
}
}
+2 -2
View File
@@ -32,8 +32,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
varsHigh = false,
useNewExprCode = false,
varsHighBank = null,
compilationTarget = target.name,
evalStackBaseAddress = null,
splitWordArrays = false,
@@ -121,6 +120,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"rasterbars",
"sincos",
"snow",
"spotlight",
"tehtriz",
"testgfx2",
),
+1 -2
View File
@@ -49,8 +49,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
varsHigh = false,
useNewExprCode = false,
varsHighBank = null,
compilationTarget = Cx16Target.NAME,
evalStackBaseAddress = null,
splitWordArrays = false,
+2 -2
View File
@@ -90,7 +90,7 @@ private fun makeSt(): SymbolTable {
// first build the AST
val astProgram = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val astBlock1 = PtBlock("block1", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
val astBlock1 = PtBlock("block1", null, 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 +113,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, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
val astBlock2 = PtBlock("block2", null, 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)
+32 -1
View File
@@ -1003,7 +1003,7 @@ main {
}
test("byte when choices silently converted to word for convenience") {
var text="""
val text="""
main {
sub start() {
uword z = 3
@@ -1016,4 +1016,35 @@ main {
}"""
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
}
test("returning smaller dt than returndt is ok") {
val text="""
main {
sub start() {
void test()
}
sub test() -> uword {
return cx16.r0L
}
}"""
compileText(C64Target(), false, text, writeAssembly = false) shouldNotBe null
}
test("returning bigger dt than returndt is not ok") {
val text="""
main {
sub start() {
void test()
}
sub test() -> ubyte {
return cx16.r0
}
}"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = false, errors=errors) shouldBe null
errors.errors.single() shouldContain "doesn't match"
}
})
-1
View File
@@ -66,7 +66,6 @@ class TestIntermediateAst: FunSpec({
val fcall = (entry.children[4] as PtAssignment).value as PtFunctionCall
fcall.void shouldBe false
fcall.type shouldBe DataType.UBYTE
printAst(ast, false, ::println)
}
})
+22 -1
View File
@@ -274,7 +274,7 @@ class TestProg8Parser: FunSpec( {
val path = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
val e = shouldThrow<ParseError> { parseModule(SourceCode.File(path)) }
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 4)
}
test("of Module parsed from a string") {
@@ -963,4 +963,25 @@ class TestProg8Parser: FunSpec( {
var6.sharedWithAsm shouldBe false
var6.zeropage shouldBe ZeropageWish.DONTCARE
}
test("line comment in array literal is ok") {
val src="""
%import textio
%zeropage basicsafe
main {
sub start() {
byte[] foo = [ 1, 2, ; this comment is ok
; line comment also ok
3,
4]
foo[1] ++
}
}"""
compileText(C64Target(), false, src, writeAssembly = false) shouldNotBe null
}
})
+110 -16
View File
@@ -13,6 +13,7 @@ import prog8.ast.statements.VarDecl
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@@ -70,25 +71,42 @@ main {
compileText(C64Target(), false, text, writeAssembly = false) shouldBe null
}
test("simple string comparison still works") {
test("string comparisons") {
val src="""
main {
sub start() {
ubyte @shared value
str thing = "????"
if thing=="name" {
value++
}
if thing!="name" {
value++
}
}
}"""
main {
sub start() {
str name = "name"
uword nameptr = &name
cx16.r0L= name=="foo"
cx16.r1L= name!="foo"
cx16.r2L= name<"foo"
cx16.r3L= name>"foo"
cx16.r0L= nameptr=="foo"
cx16.r1L= nameptr!="foo"
cx16.r2L= nameptr<"foo"
cx16.r3L= nameptr>"foo"
void compare(name, "foo")
void compare(name, "name")
void compare(nameptr, "foo")
void compare(nameptr, "name")
}
sub compare(str s1, str s2) -> ubyte {
if s1==s2
return 42
return 0
}
}"""
val result = compileText(C64Target(), optimize=false, src, writeAssembly=true)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
stmts.size shouldBe 16
val result2 = compileText(VMTarget(), optimize=false, src, writeAssembly=true)!!
val stmts2 = result2.compilerAst.entrypoint.statements
stmts2.size shouldBe 16
}
test("string concatenation and repeats") {
@@ -257,5 +275,81 @@ main {
errors.errors[0] shouldContain "invalid statement in unroll loop"
errors.errors[1] shouldContain "invalid statement in unroll loop"
}
test("various curly brace styles") {
val src="""
main
{
sub start()
{
ubyte variable=55
when variable
{
33 -> cx16.r0++
else -> cx16.r1++
}
if variable {
cx16.r0++
} else {
cx16.r1++
}
if variable { cx16.r0++ }
else { cx16.r1++ }
if variable
{
cx16.r0++
}
else
{
cx16.r1++
}
other.othersub()
}
}
other {
sub othersub() {
cx16.r0++
}
}"""
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("returning array as uword") {
val src = """
main {
sub start() {
cx16.r0 = getarray()
}
sub getarray() -> uword {
return [11,22,33]
}
}"""
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("when on booleans") {
val src = """
main
{
sub start()
{
bool choiceVariable=true
when choiceVariable {
false -> cx16.r0++
true -> cx16.r1++
}
}
}"""
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
})
@@ -121,11 +121,11 @@ main {
sub start() {
%asm {{
lda normal
lda uw_lsb
lda uw_msb
lda sw_lsb
lda sw_msb
lda p8_normal
lda p8_uw_lsb
lda p8_uw_msb
lda p8_sw_lsb
lda p8_sw_msb
}}
}
}"""
@@ -222,4 +222,27 @@ main {
result2 shouldNotBe null
}
"3 letter names not prefixed too aggressively" {
val text = """
%import math
main {
sub start() {
ubyte lda = getrandom()
lda++
cx16.r0 = (math.rnd() % 20) * ${'$'}0010
lda = math.rnd() % 5
lda++
}
sub getrandom() -> ubyte {
return cx16.r0L
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)
result shouldNotBe null
val result2 = compileText(VMTarget(), false, text, writeAssembly = true)
result2 shouldNotBe null
}
})
Loaded 100 of 146 files, more files were not shown because too many files have changed in this diff. Show more