mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b19c282269 | ||
|
|
e520921746 | ||
|
|
970642244b | ||
|
|
3b90be2d9e | ||
|
|
2f756f1e3a | ||
|
|
78e84182f0 | ||
|
|
65a7a8caf8 | ||
|
|
4c6a2f5df9 | ||
|
|
3bbc00cc8c | ||
|
|
0e781d18fa | ||
|
|
4575a8fffe | ||
|
|
10d0ff252b | ||
|
|
c7d54570cc | ||
|
|
7136b33f2e | ||
|
|
5659742d97 | ||
|
|
450eaf7c4a | ||
|
|
c1aa5d4e47 | ||
|
|
b3cb9b7fe2 | ||
|
|
9cb61fa34d | ||
|
|
7c219d235c | ||
|
|
6938c79f88 | ||
|
|
b8284a147d | ||
|
|
15ee90e99c | ||
|
|
795f80b4ec | ||
|
|
6b6427492d | ||
|
|
6055b8c3dc | ||
|
|
a98cb50d55 | ||
|
|
e98bbc1c52 | ||
|
|
7245aece4f | ||
|
|
60cbb02822 | ||
|
|
4e863ecdac | ||
|
|
5037033fcf | ||
|
|
e6b158bc97 | ||
|
|
4cc0dfa10b | ||
|
|
4ced8889d3 | ||
|
|
d26967a87d | ||
|
|
fc8955941b | ||
|
|
071a80360f | ||
|
|
d2154f5f2e | ||
|
|
334d382bfa | ||
|
|
90c4b00f74 | ||
|
|
71261525e8 | ||
|
|
3126959576 | ||
|
|
02e51d8282 | ||
|
|
ffb2027a19 | ||
|
|
70c9ab9074 | ||
|
|
6d1fdf1ba6 | ||
|
|
1f7180d9a8 | ||
|
|
b4e94ae4dd | ||
|
|
07c606bfc9 | ||
|
|
e705a8bd89 | ||
|
|
b3bdfb7f1f | ||
|
|
5af1aeb092 | ||
|
|
be64fa674a | ||
|
|
204f5591a9 | ||
|
|
ee3e3a3a40 | ||
|
|
f9200a2b75 | ||
|
|
f570b70827 | ||
|
|
0db141eeac | ||
|
|
acb2ee53bb | ||
|
|
c544b7f5ba | ||
|
|
c0024e97e5 | ||
|
|
bdf8aa9168 | ||
|
|
de5ce0f515 | ||
|
|
bb95484c8a | ||
|
|
cad18b8a3a | ||
|
|
0f6a98751a | ||
|
|
aac5a4c27f | ||
|
|
d3f6415387 | ||
|
|
04da44eb98 | ||
|
|
7649be97b1 | ||
|
|
c9ef777e0f | ||
|
|
c0cb2438d5 | ||
|
|
30c531b39e | ||
|
|
bf703a8a66 | ||
|
|
e7b631b087 | ||
|
|
a9f5dc036c | ||
|
|
0a83b51e00 | ||
|
|
eab63ecc6c | ||
|
|
b0794cf35e | ||
|
|
5b9e71a27d | ||
|
|
eae41de27d | ||
|
|
e9163aa3a7 | ||
|
|
8c617515ba | ||
|
|
04e4e71f2e | ||
|
|
a587482edf | ||
|
|
0aac9350d5 | ||
|
|
f56c12ee4e | ||
|
|
4bb9ae61f2 | ||
|
|
ff7f3484e4 | ||
|
|
5da3abe6b4 | ||
|
|
c0b398e0ce | ||
|
|
3de10adac2 | ||
|
|
1b573d6552 | ||
|
|
2a96f93919 | ||
|
|
c6b2639ca4 | ||
|
|
b9abf37a7e | ||
|
|
373cbb4144 | ||
|
|
a521982576 | ||
|
|
a77fde577c | ||
|
|
ea6926e57d | ||
|
|
ba25b7fee6 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+1
-1
@@ -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>
|
||||
Generated
+3
-3
@@ -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
@@ -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!/" />
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
+33
-39
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
txt {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 24
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
|
||||
txt {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
txt {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
|
||||
graphics {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 240
|
||||
|
||||
|
||||
@@ -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++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 *
|
||||
}}
|
||||
|
||||
@@ -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 {{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
%import textio
|
||||
|
||||
test_stack {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
asmsub test() {
|
||||
%asm {{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
; there is no diskio yet for the VM target.
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 +0,0 @@
|
||||
9.0
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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
Reference in new issue
Block a user