mirror of
https://github.com/irmen/prog8.git
synced 2025-06-18 08:23:37 +00:00
Compare commits
192 Commits
Author | SHA1 | Date | |
---|---|---|---|
914f19be86 | |||
d0b18dec8e | |||
549c598f51 | |||
7b59bc8d12 | |||
79d0fb0b52 | |||
edf56d34f8 | |||
6733253826 | |||
86210c4513 | |||
e08da659e5 | |||
e9ec310d8a | |||
19a2791c65 | |||
f1a7d5ecf7 | |||
8b05abb80d | |||
9a2df072cc | |||
224278e07a | |||
74b69e191e | |||
8cda8a727c | |||
4403e4ed62 | |||
6ee270d9d8 | |||
44fa309d20 | |||
58d88f3dd4 | |||
e980c23177 | |||
75224321bb | |||
801af05b20 | |||
7611dbbddc | |||
6d40ca15bc | |||
32c1c19224 | |||
bbf6357222 | |||
dc16629c24 | |||
3718b9d768 | |||
c25eb088ec | |||
3feb3e52f8 | |||
8e730ef93d | |||
e0913a39ab | |||
7a27fbc001 | |||
ee0dbdad35 | |||
9225f88f89 | |||
a04839dd6b | |||
002006517a | |||
f5b202d438 | |||
a7df094ff4 | |||
1e6fa77633 | |||
eb4cff202c | |||
7ee777f405 | |||
81bd5c784e | |||
b526e132a7 | |||
1860f66de5 | |||
ded9ada9bc | |||
d0e6a2eb8b | |||
4e103a1963 | |||
475e927178 | |||
ca7932c4f0 | |||
8ab47d3321 | |||
def7e87151 | |||
27568c2bef | |||
0694a187d7 | |||
832601b36b | |||
578969c34c | |||
d1d0115aed | |||
c89e6ebfab | |||
ca1089b881 | |||
a1d04f2aad | |||
bf0604133c | |||
a82b2da16e | |||
f2273c0acc | |||
17bedac96c | |||
4831fad27a | |||
5e896cf582 | |||
add3491c57 | |||
f470576822 | |||
10760a53a8 | |||
eee805183c | |||
b8fb391022 | |||
3c698f1584 | |||
2fad52d684 | |||
ec64a68a71 | |||
db55562f6a | |||
d8409a9d2b | |||
0d0ce6eec1 | |||
483f313eda | |||
7b6c742178 | |||
d4a35ba6ff | |||
68b112837a | |||
e2f20ebf94 | |||
f870e4965a | |||
7ebcb219d6 | |||
c21913a66b | |||
77e956a29f | |||
08275c406a | |||
2931e1b87b | |||
153b422496 | |||
0f6a6d6fea | |||
91fdb3e2d4 | |||
d8e87bd881 | |||
922033c1b2 | |||
df1793efbf | |||
836a2700f2 | |||
8f3aaf77a1 | |||
00c059e5b1 | |||
f4f355c74a | |||
b465fc5aaf | |||
2d78eaa48d | |||
d08451bccc | |||
d8e785aed0 | |||
267b6f49b5 | |||
e6688f4b9d | |||
9d7b9771c2 | |||
136a9a39de | |||
3dcf628fdb | |||
e614e9787a | |||
e426fc0922 | |||
5d4bfffc7e | |||
207cdaf7a4 | |||
7315b581ce | |||
38efaae7b2 | |||
469e042216 | |||
0f1a4b9d8f | |||
7303c00296 | |||
fc55b34d84 | |||
6f67fc0e02 | |||
562d722ad5 | |||
144c1ba3a6 | |||
06b032af91 | |||
3603140114 | |||
e094785cbd | |||
e7408224ac | |||
e67c05c274 | |||
b22804efaf | |||
890f55f91a | |||
cc5fc0b892 | |||
5efe2b027a | |||
5b6569d0f9 | |||
0eda7ac498 | |||
a5ef353484 | |||
67a36d8d31 | |||
7cc3cc3990 | |||
dc0edc4c2b | |||
71d2f091e5 | |||
c2f062a391 | |||
224f490455 | |||
5b35232ab4 | |||
6d6db70e42 | |||
6830e15b4e | |||
3f07cad35d | |||
e951340033 | |||
db8912a735 | |||
0e297731a3 | |||
f20c4f98ac | |||
05e60cc7c0 | |||
55b4469767 | |||
f15516e478 | |||
17ceadbadf | |||
8c25b2b316 | |||
8b1ae404a3 | |||
13534cd4a9 | |||
abfb345503 | |||
42ae935496 | |||
434515d957 | |||
094f7803b7 | |||
b0c7bad391 | |||
e9a4a905ef | |||
7b6cd0cfbe | |||
b718b12083 | |||
cfa7258ff4 | |||
b70e0a0870 | |||
da8eb464b8 | |||
8f9d1cfa30 | |||
585009ac5c | |||
30ee65fd14 | |||
76428b16f0 | |||
0d7b14e2d8 | |||
a9d19d02b3 | |||
adcbe55307 | |||
aa99a7df64 | |||
00afa1ce52 | |||
e94bf4c63c | |||
ec5adffdc2 | |||
733c17ad3a | |||
53b0b562e6 | |||
fabae6e970 | |||
a9f9c40d8a | |||
6fc89607d3 | |||
2340760f53 | |||
39d6d2857e | |||
7b722a0001 | |||
e7682119e0 | |||
af6be44676 | |||
5a8f97a0b6 | |||
0d4dd385b8 | |||
94f0f3e966 | |||
43e31765e5 | |||
7c1bdfe713 |
31
.github/workflows/all-ci.yml
vendored
Normal file
31
.github/workflows/all-ci.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
name: Build and Test the Prog8 compiler
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install 64tass
|
||||||
|
run: sudo apt-get update -y && sudo apt-get install -y 64tass
|
||||||
|
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
distribution: adopt
|
||||||
|
|
||||||
|
- name: Build and test with Gradle
|
||||||
|
run: ./gradlew build shadowJar --no-daemon
|
||||||
|
|
||||||
|
- name: Create compiler shadowJar artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: prog8-compiler-jar-zipped
|
||||||
|
path: compiler/build/libs/*-all.jar
|
10
.idea/libraries/antlr_antlr4.xml
generated
10
.idea/libraries/antlr_antlr4.xml
generated
@ -1,17 +1,17 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="antlr.antlr4" type="repository">
|
<library name="antlr.antlr4" type="repository">
|
||||||
<properties maven-id="org.antlr:antlr4:4.10.1">
|
<properties maven-id="org.antlr:antlr4:4.11.1">
|
||||||
<exclude>
|
<exclude>
|
||||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||||
</exclude>
|
</exclude>
|
||||||
</properties>
|
</properties>
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.10.1/antlr4-4.10.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.11.1/antlr4-4.11.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.10.1/antlr4-runtime-4.10.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.11.1/antlr4-runtime-4.11.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.3/ST4-4.3.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!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -19,7 +19,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<type id="Python" />
|
<type id="Python" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -78,6 +78,9 @@ It's handy to have an emulator (or a real machine perhaps!) to run the programs
|
|||||||
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||||
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
||||||
|
|
||||||
|
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
|
||||||
|
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
|
||||||
|
|
||||||
|
|
||||||
Example code
|
Example code
|
||||||
------------
|
------------
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package prog8
|
package prog8.code
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By convention, the right side of an `Either` is used to hold successful values.
|
* By convention, the right side of an `Either` is used to hold successful values.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
sealed class Either<out L, out R> {
|
sealed class Either<out L, out R> {
|
||||||
|
|
@ -8,16 +8,13 @@ import prog8.code.core.*
|
|||||||
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||||
*/
|
*/
|
||||||
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
||||||
fun print() = printIndented(0)
|
|
||||||
|
|
||||||
override fun printProperties() { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The table as a flat mapping of scoped names to the StNode.
|
* The table as a flat mapping of scoped names to the StNode.
|
||||||
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
||||||
*/
|
*/
|
||||||
val flat: Map<List<String>, StNode> by lazy {
|
|
||||||
val result = mutableMapOf<List<String>, StNode>()
|
val flat: Map<String, StNode> by lazy {
|
||||||
|
val result = mutableMapOf<String, StNode>()
|
||||||
fun flatten(node: StNode) {
|
fun flatten(node: StNode) {
|
||||||
result[node.scopedName] = node
|
result[node.scopedName] = node
|
||||||
node.children.values.forEach { flatten(it) }
|
node.children.values.forEach { flatten(it) }
|
||||||
@ -58,7 +55,7 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
|||||||
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
|
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>) = flat[scopedName]
|
override fun lookup(scopedName: String) = flat[scopedName]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -85,38 +82,18 @@ open class StNode(val name: String,
|
|||||||
|
|
||||||
lateinit var parent: StNode
|
lateinit var parent: StNode
|
||||||
|
|
||||||
val scopedName: List<String> by lazy {
|
val scopedName: String by lazy { scopedNameList.joinToString(".") }
|
||||||
if(type== StNodeType.GLOBAL)
|
|
||||||
emptyList()
|
|
||||||
else
|
|
||||||
parent.scopedName + name
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lookup(name: String) =
|
open fun lookup(scopedName: String) =
|
||||||
lookupUnqualified(name)
|
lookup(scopedName.split('.'))
|
||||||
open fun lookup(scopedName: List<String>) =
|
|
||||||
if(scopedName.size>1) lookupQualified(scopedName) else lookupUnqualified(scopedName[0])
|
|
||||||
fun lookupOrElse(name: String, default: () -> StNode) =
|
|
||||||
lookupUnqualified(name) ?: default()
|
|
||||||
fun lookupOrElse(scopedName: List<String>, default: () -> StNode) =
|
|
||||||
lookup(scopedName) ?: default()
|
|
||||||
|
|
||||||
private fun lookupQualified(scopedName: List<String>): StNode? {
|
fun lookupUnscopedOrElse(name: String, default: () -> StNode) =
|
||||||
// a scoped name refers to a name in another namespace, and always stars from the root.
|
lookupUnscoped(name) ?: default()
|
||||||
var node = this
|
|
||||||
while(node.type!= StNodeType.GLOBAL)
|
|
||||||
node = node.parent
|
|
||||||
|
|
||||||
for(name in scopedName) {
|
fun lookupOrElse(scopedName: String, default: () -> StNode): StNode =
|
||||||
if(name in node.children)
|
lookup(scopedName.split('.')) ?: default()
|
||||||
node = node.children.getValue(name)
|
|
||||||
else
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun lookupUnqualified(name: String): StNode? {
|
fun lookupUnscoped(name: String): StNode? {
|
||||||
// first consider the builtin functions
|
// first consider the builtin functions
|
||||||
var globalscope = this
|
var globalscope = this
|
||||||
while(globalscope.type!= StNodeType.GLOBAL)
|
while(globalscope.type!= StNodeType.GLOBAL)
|
||||||
@ -138,37 +115,37 @@ open class StNode(val name: String,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printIndented(indent: Int) {
|
|
||||||
print(" ".repeat(indent))
|
|
||||||
when(type) {
|
|
||||||
StNodeType.GLOBAL -> print("SYMBOL-TABLE:")
|
|
||||||
StNodeType.BLOCK -> print("(B) ")
|
|
||||||
StNodeType.SUBROUTINE -> print("(S) ")
|
|
||||||
StNodeType.LABEL -> print("(L) ")
|
|
||||||
StNodeType.STATICVAR -> print("(V) ")
|
|
||||||
StNodeType.MEMVAR -> print("(M) ")
|
|
||||||
StNodeType.MEMORYSLAB -> print("(MS) ")
|
|
||||||
StNodeType.CONSTANT -> print("(C) ")
|
|
||||||
StNodeType.BUILTINFUNC -> print("(F) ")
|
|
||||||
StNodeType.ROMSUB -> print("(R) ")
|
|
||||||
}
|
|
||||||
printProperties()
|
|
||||||
println()
|
|
||||||
children.forEach { (_, node) -> node.printIndented(indent+1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun printProperties() {
|
|
||||||
print("$name ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(child: StNode) {
|
fun add(child: StNode) {
|
||||||
children[child.name] = child
|
children[child.name] = child
|
||||||
child.parent = this
|
child.parent = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val scopedNameList: List<String> by lazy {
|
||||||
|
if(type== StNodeType.GLOBAL)
|
||||||
|
emptyList()
|
||||||
|
else
|
||||||
|
parent.scopedNameList + name
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lookup(scopedName: List<String>): StNode? {
|
||||||
|
// a scoped name refers to a name in another namespace, and always stars from the root.
|
||||||
|
var node = this
|
||||||
|
while(node.type!= StNodeType.GLOBAL)
|
||||||
|
node = node.parent
|
||||||
|
|
||||||
|
for(name in scopedName) {
|
||||||
|
if(name in node.children)
|
||||||
|
node = node.children.getValue(name)
|
||||||
|
else
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StStaticVariable(name: String,
|
class StStaticVariable(name: String,
|
||||||
val dt: DataType,
|
val dt: DataType,
|
||||||
|
val bss: Boolean,
|
||||||
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||||
val onetimeInitializationStringValue: StString?,
|
val onetimeInitializationStringValue: StString?,
|
||||||
val onetimeInitializationArrayValue: StArray?,
|
val onetimeInitializationArrayValue: StArray?,
|
||||||
@ -177,10 +154,19 @@ class StStaticVariable(name: String,
|
|||||||
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if(bss) {
|
||||||
|
require(onetimeInitializationNumericValue==null)
|
||||||
|
require(onetimeInitializationStringValue==null)
|
||||||
|
require(onetimeInitializationArrayValue.isNullOrEmpty())
|
||||||
|
} else {
|
||||||
|
require(onetimeInitializationNumericValue!=null ||
|
||||||
|
onetimeInitializationStringValue!=null ||
|
||||||
|
onetimeInitializationArrayValue!=null)
|
||||||
|
}
|
||||||
if(length!=null) {
|
if(length!=null) {
|
||||||
require(onetimeInitializationNumericValue == null)
|
require(onetimeInitializationNumericValue == null)
|
||||||
if(onetimeInitializationArrayValue!=null)
|
if(onetimeInitializationArrayValue!=null)
|
||||||
require(length == onetimeInitializationArrayValue.size)
|
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||||
}
|
}
|
||||||
if(onetimeInitializationNumericValue!=null)
|
if(onetimeInitializationNumericValue!=null)
|
||||||
require(dt in NumericDatatypes)
|
require(dt in NumericDatatypes)
|
||||||
@ -191,18 +177,11 @@ class StStaticVariable(name: String,
|
|||||||
require(length == onetimeInitializationStringValue.first.length+1)
|
require(length == onetimeInitializationStringValue.first.length+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print("$name dt=$dt zpw=$zpwish")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
|
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
|
||||||
StNode(name, StNodeType.CONSTANT, position) {
|
StNode(name, StNodeType.CONSTANT, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$name dt=$dt value=$value")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -212,9 +191,6 @@ class StMemVar(name: String,
|
|||||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||||
position: Position) :
|
position: Position) :
|
||||||
StNode(name, StNodeType.MEMVAR, position) {
|
StNode(name, StNodeType.MEMVAR, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$name dt=$dt address=${address.toHex()}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StMemorySlab(
|
class StMemorySlab(
|
||||||
@ -224,16 +200,10 @@ class StMemorySlab(
|
|||||||
position: Position
|
position: Position
|
||||||
):
|
):
|
||||||
StNode(name, StNodeType.MEMORYSLAB, position) {
|
StNode(name, StNodeType.MEMORYSLAB, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$name size=$size align=$align")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
||||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
StNode(name, StNodeType.SUBROUTINE, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print(name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -243,15 +213,12 @@ class StRomSub(name: String,
|
|||||||
val returns: List<RegisterOrStatusflag>,
|
val returns: List<RegisterOrStatusflag>,
|
||||||
position: Position) :
|
position: Position) :
|
||||||
StNode(name, StNodeType.ROMSUB, position) {
|
StNode(name, StNodeType.ROMSUB, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$name address=${address.toHex()}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StSubroutineParameter(val name: String, val type: DataType)
|
class StSubroutineParameter(val name: String, val type: DataType)
|
||||||
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||||
class StArrayElement(val number: Double?, val addressOf: List<String>?)
|
class StArrayElement(val number: Double?, val addressOfSymbol: String?)
|
||||||
|
|
||||||
typealias StString = Pair<String, Encoding>
|
typealias StString = Pair<String, Encoding>
|
||||||
typealias StArray = List<StArrayElement>
|
typealias StArray = List<StArrayElement>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package prog8.code.ast
|
package prog8.code.ast
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.IMemSizer
|
||||||
|
import prog8.code.core.IStringEncoding
|
||||||
|
import prog8.code.core.Position
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
// New simplified AST for the code generator.
|
// New simplified AST for the code generator.
|
||||||
@ -42,15 +44,17 @@ class PtNodeGroup : PtNode(Position.DUMMY) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class PtNamedNode(val name: String, position: Position): PtNode(position) {
|
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
|
||||||
val scopedName: List<String> by lazy {
|
// 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
|
var namedParent: PtNode = this.parent
|
||||||
if(namedParent is PtProgram)
|
if(namedParent is PtProgram)
|
||||||
listOf(name)
|
name
|
||||||
else {
|
else {
|
||||||
while (namedParent !is PtNamedNode)
|
while (namedParent !is PtNamedNode)
|
||||||
namedParent = namedParent.parent
|
namedParent = namedParent.parent
|
||||||
namedParent.scopedName + name
|
namedParent.scopedName + "." + name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,8 +100,13 @@ class PtBlock(name: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtInlineAssembly(val assembly: String, position: Position) : PtNode(position) {
|
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
||||||
override fun printProperties() {}
|
override fun printProperties() {}
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
|
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -125,12 +134,6 @@ class PtNop(position: Position): PtNode(position) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtScopeVarsDecls(position: Position): PtNode(position) {
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// find the parent node of a specific type or interface
|
// find the parent node of a specific type or interface
|
||||||
// (useful to figure out in what namespace/block something is defined, etc.)
|
// (useful to figure out in what namespace/block something is defined, etc.)
|
||||||
inline fun <reified T> findParentNode(node: PtNode): T? {
|
inline fun <reified T> findParentNode(node: PtNode): T? {
|
||||||
|
@ -33,7 +33,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
|||||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
|
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
|
||||||
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
||||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName
|
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
|
||||||
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
||||||
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
||||||
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
||||||
@ -108,7 +108,7 @@ class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, posit
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtFunctionCall(val functionName: List<String>,
|
class PtFunctionCall(val name: String,
|
||||||
val void: Boolean,
|
val void: Boolean,
|
||||||
type: DataType,
|
type: DataType,
|
||||||
position: Position) : PtExpression(type, position) {
|
position: Position) : PtExpression(type, position) {
|
||||||
@ -120,14 +120,14 @@ class PtFunctionCall(val functionName: List<String>,
|
|||||||
val args: List<PtExpression>
|
val args: List<PtExpression>
|
||||||
get() = children.map { it as PtExpression }
|
get() = children.map { it as PtExpression }
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
print("${functionName.joinToString(".")} void=$void")
|
print("$name void=$void")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtIdentifier(val ref: List<String>, val targetName: List<String>, type: DataType, position: Position) : PtExpression(type, position) {
|
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position) {
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
print("$ref --> $targetName $type")
|
print("$name $type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
153
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
153
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package prog8.code.ast
|
||||||
|
|
||||||
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces readable text from a [PtNode] (AST node, usually starting with PtProgram as root),
|
||||||
|
* passing it as a String to the specified receiver function.
|
||||||
|
*/
|
||||||
|
fun printAst(root: PtNode, output: (text: String) -> Unit) {
|
||||||
|
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||||
|
fun txt(node: PtNode): String {
|
||||||
|
return when(node) {
|
||||||
|
is PtAssignTarget -> ""
|
||||||
|
is PtAssignment -> "<assign>"
|
||||||
|
is PtBreakpoint -> "%breakpoint"
|
||||||
|
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
|
||||||
|
is PtAddressOf -> "&"
|
||||||
|
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
|
||||||
|
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)}"
|
||||||
|
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
||||||
|
is PtBuiltinFunctionCall -> {
|
||||||
|
val str = if(node.void) "void " else ""
|
||||||
|
str + node.name + "()"
|
||||||
|
}
|
||||||
|
is PtContainmentCheck -> "in"
|
||||||
|
is PtFunctionCall -> {
|
||||||
|
val str = if(node.void) "void " else ""
|
||||||
|
str + node.name + "()"
|
||||||
|
}
|
||||||
|
is PtIdentifier -> "${node.name} ${type(node.type)}"
|
||||||
|
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
|
||||||
|
is PtMemoryByte -> "@()"
|
||||||
|
is PtNumber -> "${node.number.toHex()} ${type(node.type)}"
|
||||||
|
is PtPrefix -> node.operator
|
||||||
|
is PtRange -> "<range>"
|
||||||
|
is PtString -> "\"${node.value.escape()}\""
|
||||||
|
is PtTypeCast -> "as ${node.type.name.lowercase()}"
|
||||||
|
is PtForLoop -> "for"
|
||||||
|
is PtIfElse -> "ifelse"
|
||||||
|
is PtIncludeBinary -> "%incbin '${node.file}', ${node.offset}, ${node.length}"
|
||||||
|
is PtInlineAssembly -> {
|
||||||
|
if(node.isIR)
|
||||||
|
"%ir {{ ...${node.assembly.length} characters... }}"
|
||||||
|
else
|
||||||
|
"%asm {{ ...${node.assembly.length} characters... }}"
|
||||||
|
}
|
||||||
|
is PtJump -> {
|
||||||
|
if(node.identifier!=null)
|
||||||
|
"goto ${node.identifier.name}"
|
||||||
|
else if(node.address!=null)
|
||||||
|
"goto ${node.address.toHex()}"
|
||||||
|
else if(node.generatedLabel!=null)
|
||||||
|
"goto ${node.generatedLabel}"
|
||||||
|
else
|
||||||
|
"???"
|
||||||
|
}
|
||||||
|
is PtAsmSub -> {
|
||||||
|
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
|
||||||
|
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
|
||||||
|
val returns = if (node.returnTypes.isEmpty()) "" else (if (node.returnTypes.size == 1) "-> ${node.returnTypes[0].name.lowercase()}" else "-> ${node.returnTypes.map { it.name.lowercase() }}")
|
||||||
|
val str = if (node.inline) "inline " else ""
|
||||||
|
if(node.address==null) {
|
||||||
|
str + "asmsub ${node.name}($params) $clobbers $returns"
|
||||||
|
} else {
|
||||||
|
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PtBlock -> {
|
||||||
|
val addr = if(node.address==null) "" else "@${node.address?.toHex()}"
|
||||||
|
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
|
||||||
|
"\nblock '${node.name}' $addr $align"
|
||||||
|
}
|
||||||
|
is PtConstant -> {
|
||||||
|
val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString()
|
||||||
|
"const ${node.type.name.lowercase()} ${node.name} = $value"
|
||||||
|
}
|
||||||
|
is PtLabel -> "${node.name}:"
|
||||||
|
is PtMemMapped -> {
|
||||||
|
if(node.type in ArrayDatatypes) {
|
||||||
|
val arraysize = if(node.arraySize==null) "" else node.arraySize.toString()
|
||||||
|
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||||
|
"&${eltType.name.lowercase()}[$arraysize] ${node.name} = ${node.address.toHex()}"
|
||||||
|
} else {
|
||||||
|
"&${node.type.name.lowercase()} ${node.name} = ${node.address.toHex()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PtSub -> {
|
||||||
|
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
|
||||||
|
var str = if(node.inline) "inline " else ""
|
||||||
|
str += "sub ${node.name}($params) "
|
||||||
|
if(node.returntype!=null)
|
||||||
|
str += "-> ${node.returntype.name.lowercase()}"
|
||||||
|
str
|
||||||
|
}
|
||||||
|
is PtVariable -> {
|
||||||
|
val str = if(node.arraySize!=null) {
|
||||||
|
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||||
|
"${eltType.name.lowercase()}[${node.arraySize}] ${node.name}"
|
||||||
|
}
|
||||||
|
else if(node.type in ArrayDatatypes) {
|
||||||
|
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||||
|
"${eltType.name.lowercase()}[] ${node.name}"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
"${node.type.name.lowercase()} ${node.name}"
|
||||||
|
if(node.value!=null)
|
||||||
|
str + " = " + txt(node.value!!)
|
||||||
|
else
|
||||||
|
str
|
||||||
|
}
|
||||||
|
is PtNodeGroup -> "<group>"
|
||||||
|
is PtNop -> "nop"
|
||||||
|
is PtPostIncrDecr -> "<post> ${node.operator}"
|
||||||
|
is PtProgram -> "PROGRAM ${node.name}"
|
||||||
|
is PtRepeatLoop -> "repeat"
|
||||||
|
is PtReturn -> "return"
|
||||||
|
is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}"
|
||||||
|
is PtWhen -> "when"
|
||||||
|
is PtWhenChoice -> {
|
||||||
|
if(node.isElse)
|
||||||
|
"else"
|
||||||
|
else
|
||||||
|
"->"
|
||||||
|
}
|
||||||
|
else -> throw InternalCompilerException("unrecognised ast node $node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(root is PtProgram) {
|
||||||
|
output(txt(root))
|
||||||
|
root.children.forEach {
|
||||||
|
walkAst(it) { node, depth ->
|
||||||
|
val txt = txt(node)
|
||||||
|
if(txt.isNotEmpty())
|
||||||
|
output(" ".repeat(depth) + txt(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
walkAst(root) { node, depth ->
|
||||||
|
val txt = txt(node)
|
||||||
|
if(txt.isNotEmpty())
|
||||||
|
output(" ".repeat(depth) + txt(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Unit) {
|
||||||
|
fun recurse(node: PtNode, depth: Int) {
|
||||||
|
act(node, depth)
|
||||||
|
node.children.forEach { recurse(it, depth+1) }
|
||||||
|
}
|
||||||
|
recurse(root, 0)
|
||||||
|
}
|
@ -66,7 +66,7 @@ class PtAssignment(position: Position) : PtNode(position) {
|
|||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName
|
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.name==source.name
|
||||||
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
|
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
|
||||||
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
|
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
|
||||||
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
|
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
|
||||||
@ -74,7 +74,7 @@ class PtAssignment(position: Position) : PtNode(position) {
|
|||||||
is PtPrefix -> {
|
is PtPrefix -> {
|
||||||
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
|
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
|
||||||
||
|
||
|
||||||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName)
|
(target is PtIdentifier && (source.value as? PtIdentifier)?.name==target.name)
|
||||||
}
|
}
|
||||||
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
|
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
|
||||||
is PtBinaryExpression ->
|
is PtBinaryExpression ->
|
||||||
@ -202,7 +202,7 @@ class PtConstant(name: String, val type: DataType, val value: Double, position:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) {
|
class PtMemMapped(name: String, val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
|
||||||
override fun printProperties() {
|
override fun printProperties() {
|
||||||
print("&$type $name = ${address.toHex()}")
|
print("&$type $name = ${address.toHex()}")
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ class CompilationOptions(val output: OutputType,
|
|||||||
var asmQuiet: Boolean = false,
|
var asmQuiet: Boolean = false,
|
||||||
var asmListfile: Boolean = false,
|
var asmListfile: Boolean = false,
|
||||||
var experimentalCodegen: Boolean = false,
|
var experimentalCodegen: Boolean = false,
|
||||||
var keepIR: Boolean = false,
|
|
||||||
var evalStackBaseAddress: UInt? = null,
|
var evalStackBaseAddress: UInt? = null,
|
||||||
var outputDir: Path = Path(""),
|
var outputDir: Path = Path(""),
|
||||||
var symbolDefs: Map<String, String> = emptyMap()
|
var symbolDefs: Map<String, String> = emptyMap()
|
||||||
|
@ -17,11 +17,11 @@ interface IMachineDefinition {
|
|||||||
var ESTACK_HI: UInt
|
var ESTACK_HI: UInt
|
||||||
val PROGRAM_LOAD_ADDRESS : UInt
|
val PROGRAM_LOAD_ADDRESS : UInt
|
||||||
|
|
||||||
val opcodeNames: Set<String>
|
|
||||||
var zeropage: Zeropage
|
|
||||||
val cpu: CpuType
|
val cpu: CpuType
|
||||||
|
var zeropage: Zeropage
|
||||||
|
var golden: GoldenRam
|
||||||
|
|
||||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||||
fun getFloatAsmBytes(num: Number): String
|
fun getFloatAsmBytes(num: Number): String
|
||||||
|
|
||||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||||
@ -31,5 +31,7 @@ interface IMachineDefinition {
|
|||||||
require(evalStackBaseAddress and 255u == 0u)
|
require(evalStackBaseAddress and 255u == 0u)
|
||||||
ESTACK_LO = evalStackBaseAddress
|
ESTACK_LO = evalStackBaseAddress
|
||||||
ESTACK_HI = evalStackBaseAddress + 256u
|
ESTACK_HI = evalStackBaseAddress + 256u
|
||||||
|
require(ESTACK_LO !in golden.region && ESTACK_HI !in golden.region) { "user-set ESTACK can't be in GOLDEN ram" }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,31 @@ import com.github.michaelbull.result.Ok
|
|||||||
import com.github.michaelbull.result.Result
|
import com.github.michaelbull.result.Result
|
||||||
|
|
||||||
|
|
||||||
class ZeropageAllocationError(message: String) : Exception(message)
|
class MemAllocationError(message: String) : Exception(message)
|
||||||
|
|
||||||
|
|
||||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
||||||
|
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
|
||||||
|
|
||||||
|
abstract fun allocate(name: List<String>,
|
||||||
|
datatype: DataType,
|
||||||
|
numElements: Int?,
|
||||||
|
position: Position?,
|
||||||
|
errors: IErrorReporter): Result<VarAllocation, MemAllocationError>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||||
|
|
||||||
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
|
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
|
||||||
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
|
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
|
||||||
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
|
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
|
||||||
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
|
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
|
||||||
|
|
||||||
data class ZpAllocation(val address: UInt, val dt: DataType, val size: Int)
|
|
||||||
|
|
||||||
// the variables allocated into Zeropage.
|
// the variables allocated into Zeropage.
|
||||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||||
val allocatedVariables = mutableMapOf<List<String>, ZpAllocation>()
|
val allocatedVariables = mutableMapOf<List<String>, VarAllocation>()
|
||||||
|
|
||||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||||
|
|
||||||
@ -41,17 +51,16 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allocate(name: List<String>,
|
override fun allocate(name: List<String>,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
errors: IErrorReporter
|
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||||
): Result<Pair<UInt, Int>, ZeropageAllocationError> {
|
|
||||||
|
|
||||||
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
||||||
|
|
||||||
if(options.zeropage== ZeropageType.DONTUSE)
|
if(options.zeropage== ZeropageType.DONTUSE)
|
||||||
return Err(ZeropageAllocationError("zero page usage has been disabled"))
|
return Err(MemAllocationError("zero page usage has been disabled"))
|
||||||
|
|
||||||
val size: Int =
|
val size: Int =
|
||||||
when (datatype) {
|
when (datatype) {
|
||||||
@ -72,9 +81,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
else
|
else
|
||||||
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
|
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
|
||||||
memsize
|
memsize
|
||||||
} else return Err(ZeropageAllocationError("floating point option not enabled"))
|
} else return Err(MemAllocationError("floating point option not enabled"))
|
||||||
}
|
}
|
||||||
else -> return Err(ZeropageAllocationError("cannot put datatype $datatype in zeropage"))
|
else -> throw MemAllocationError("weird dt")
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
@ -82,18 +91,18 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
if(size==1) {
|
if(size==1) {
|
||||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||||
if(oneSeparateByteFree(candidate))
|
if(oneSeparateByteFree(candidate))
|
||||||
return Ok(Pair(makeAllocation(candidate, 1, datatype, name), 1))
|
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||||
}
|
}
|
||||||
return Ok(Pair(makeAllocation(free[0], 1, datatype, name), 1))
|
return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
|
||||||
}
|
}
|
||||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||||
if (sequentialFree(candidate, size))
|
if (sequentialFree(candidate, size))
|
||||||
return Ok(Pair(makeAllocation(candidate, size, datatype, name), size))
|
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(ZeropageAllocationError("no more free space in ZP to allocate $size sequential bytes"))
|
return Err(MemAllocationError("no more free space in ZP to allocate $size sequential bytes"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reserve(range: UIntRange) = free.removeAll(range)
|
private fun reserve(range: UIntRange) = free.removeAll(range)
|
||||||
@ -103,9 +112,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
free.removeAll(address until address+size.toUInt())
|
free.removeAll(address until address+size.toUInt())
|
||||||
if(name.isNotEmpty()) {
|
if(name.isNotEmpty()) {
|
||||||
allocatedVariables[name] = when(datatype) {
|
allocatedVariables[name] = when(datatype) {
|
||||||
in NumericDatatypes -> ZpAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
|
in NumericDatatypes -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
|
||||||
DataType.STR -> ZpAllocation(address, datatype, size)
|
DataType.STR -> VarAllocation(address, datatype, size)
|
||||||
in ArrayDatatypes -> ZpAllocation(address, datatype, size)
|
in ArrayDatatypes -> VarAllocation(address, datatype, size)
|
||||||
else -> throw AssemblyError("invalid dt")
|
else -> throw AssemblyError("invalid dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,3 +129,37 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
abstract fun allocateCx16VirtualRegisters()
|
abstract fun allocateCx16VirtualRegisters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
||||||
|
private var nextLocation: UInt = region.first
|
||||||
|
|
||||||
|
override fun allocate(
|
||||||
|
name: List<String>,
|
||||||
|
datatype: DataType,
|
||||||
|
numElements: Int?,
|
||||||
|
position: Position?,
|
||||||
|
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||||
|
|
||||||
|
val size: Int =
|
||||||
|
when (datatype) {
|
||||||
|
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
|
||||||
|
DataType.STR, in ArrayDatatypes -> {
|
||||||
|
options.compTarget.memorySize(datatype, numElements!!)
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
if (options.floats) {
|
||||||
|
options.compTarget.memorySize(DataType.FLOAT)
|
||||||
|
} else return Err(MemAllocationError("floating point option not enabled"))
|
||||||
|
}
|
||||||
|
else -> throw MemAllocationError("weird dt")
|
||||||
|
}
|
||||||
|
|
||||||
|
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
|
||||||
|
val result = Ok(VarAllocation(nextLocation, datatype, size))
|
||||||
|
nextLocation += size.toUInt()
|
||||||
|
result
|
||||||
|
} else
|
||||||
|
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package prog8.code.target.atari
|
package prog8.code.target.atari
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.c64.normal6502instructions
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
@ -19,6 +18,7 @@ class AtariMachineDefinition: IMachineDefinition {
|
|||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||||
|
|
||||||
@ -57,9 +57,8 @@ class AtariMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
|
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
|
||||||
|
|
||||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
zeropage = AtariZeropage(compilerOptions)
|
zeropage = AtariZeropage(compilerOptions)
|
||||||
|
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val opcodeNames = normal6502instructions
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package prog8.code.target.c128
|
package prog8.code.target.c128
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.c64.normal6502instructions
|
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -18,8 +17,8 @@ class C128MachineDefinition: IMachineDefinition {
|
|||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
|
|
||||||
@ -47,9 +46,8 @@ class C128MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||||
|
|
||||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
zeropage = C128Zeropage(compilerOptions)
|
zeropage = C128Zeropage(compilerOptions)
|
||||||
|
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
|
||||||
}
|
}
|
||||||
|
|
||||||
override val opcodeNames = normal6502instructions
|
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||||
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||||
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
|
|
||||||
@ -55,22 +55,9 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||||
|
|
||||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
zeropage = C64Zeropage(compilerOptions)
|
zeropage = C64Zeropage(compilerOptions)
|
||||||
|
golden = GoldenRam(compilerOptions, 0xc000u until ESTACK_LO)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val opcodeNames = normal6502instructions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
|
||||||
internal val normal6502instructions = setOf(
|
|
||||||
"adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
|
||||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
|
||||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
|
|
||||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
|
||||||
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
|
|
||||||
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
|
|
||||||
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
|
|
||||||
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
|
|
||||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
|
@ -83,12 +83,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||||
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||||
for(reg in 0..15) {
|
for(reg in 0..15) {
|
||||||
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
free.remove((4+reg*2).toUInt())
|
free.remove((4+reg*2).toUInt())
|
||||||
free.remove((5+reg*2).toUInt())
|
free.remove((5+reg*2).toUInt())
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||||
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
||||||
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
@ -50,28 +50,16 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
println("\nStarting Commander X16 emulator $emulator...")
|
println("\nStarting Commander X16 emulator $emulator...")
|
||||||
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
|
||||||
val process: Process = processb.start()
|
val process: Process = processb.start()
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
||||||
|
|
||||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
zeropage = CX16Zeropage(compilerOptions)
|
zeropage = CX16Zeropage(compilerOptions)
|
||||||
|
golden = GoldenRam(compilerOptions, 0x0600u until 0x0800u)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 65c02 opcodes, these cannot be used as variable or label names
|
|
||||||
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
|
|
||||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
|
||||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
|
|
||||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
|
||||||
"inc", "inx", "iny", "jmp", "jsr",
|
|
||||||
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
|
|
||||||
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
|
|
||||||
"sec", "sed", "sei",
|
|
||||||
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
|
|
||||||
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
|
|
||||||
"rmb", "smb", "stp", "wai")
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||||
for(reg in 0..15) {
|
for(reg in 0..15) {
|
||||||
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
package prog8.code.target.virtual
|
package prog8.code.target.virtual
|
||||||
|
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.*
|
||||||
import prog8.code.core.CpuType
|
|
||||||
import prog8.code.core.IMachineDefinition
|
|
||||||
import prog8.code.core.Zeropage
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.isReadable
|
import kotlin.io.path.isReadable
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
@ -20,8 +17,8 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override var ESTACK_LO = 0u // not actually used
|
override var ESTACK_LO = 0u // not actually used
|
||||||
override var ESTACK_HI = 0u // not actually used
|
override var ESTACK_HI = 0u // not actually used
|
||||||
|
override lateinit var zeropage: Zeropage // not actually used
|
||||||
override lateinit var zeropage: Zeropage // not actually used
|
override lateinit var golden: GoldenRam // not actually used
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||||
|
|
||||||
@ -47,11 +44,9 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = false
|
override fun isIOAddress(address: UInt): Boolean = false
|
||||||
|
|
||||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {}
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
|
||||||
|
|
||||||
override val opcodeNames = emptySet<String>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IVirtualMachineRunner {
|
interface IVirtualMachineRunner {
|
||||||
fun runProgram(irSource: CharSequence)
|
fun runProgram(irSource: String)
|
||||||
}
|
}
|
||||||
|
@ -100,13 +100,13 @@ class AsmGen(internal val program: Program,
|
|||||||
fun asmSymbolName(identifier: IdentifierReference) = asmSymbolName(identifier.nameInSource)
|
fun asmSymbolName(identifier: IdentifierReference) = asmSymbolName(identifier.nameInSource)
|
||||||
fun asmVariableName(identifier: IdentifierReference) = asmVariableName(identifier.nameInSource)
|
fun asmVariableName(identifier: IdentifierReference) = asmVariableName(identifier.nameInSource)
|
||||||
|
|
||||||
internal fun getTempVarName(dt: DataType): List<String> {
|
internal fun getTempVarName(dt: DataType): String {
|
||||||
return when(dt) {
|
return when(dt) {
|
||||||
DataType.UBYTE -> listOf("cx16", "r9L")
|
DataType.UBYTE -> "cx16.r9L"
|
||||||
DataType.BYTE -> listOf("cx16", "r9sL")
|
DataType.BYTE -> "cx16.r9sL"
|
||||||
DataType.UWORD -> listOf("cx16", "r9")
|
DataType.UWORD -> "cx16.r9"
|
||||||
DataType.WORD -> listOf("cx16", "r9s")
|
DataType.WORD -> "cx16.r9s"
|
||||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8
|
DataType.FLOAT -> TODO("no temporary float var available")
|
||||||
else -> throw FatalAstException("invalid dt $dt")
|
else -> throw FatalAstException("invalid dt $dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,7 +375,7 @@ class AsmGen(internal val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
|
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||||
out(
|
out(
|
||||||
"""
|
"""
|
||||||
lda $indexName
|
lda $indexName
|
||||||
@ -406,7 +406,7 @@ class AsmGen(internal val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
|
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||||
out(
|
out(
|
||||||
"""
|
"""
|
||||||
lda $indexName
|
lda $indexName
|
||||||
@ -605,7 +605,7 @@ class AsmGen(internal val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
|
private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
|
||||||
require(count in 257..65535)
|
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
|
||||||
val repeatLabel = program.makeLabel("repeat")
|
val repeatLabel = program.makeLabel("repeat")
|
||||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||||
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
|
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
|
||||||
@ -667,7 +667,7 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
|
private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
|
||||||
require(count in 2..256)
|
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
|
||||||
val repeatLabel = program.makeLabel("repeat")
|
val repeatLabel = program.makeLabel("repeat")
|
||||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||||
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
|
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
|
||||||
@ -794,7 +794,7 @@ $repeatLabel lda $counterVar
|
|||||||
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
|
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
|
||||||
throw AssemblyError("only else part contains code, shoud have been switched already")
|
throw AssemblyError("only else part contains code, shoud have been switched already")
|
||||||
|
|
||||||
val jump = stmt.truepart.statements.first() as? Jump
|
val jump = stmt.truepart.statements.firstOrNull() as? Jump
|
||||||
if(jump!=null) {
|
if(jump!=null) {
|
||||||
// branch with only a jump (goto)
|
// branch with only a jump (goto)
|
||||||
val instruction = branchInstruction(stmt.condition, false)
|
val instruction = branchInstruction(stmt.condition, false)
|
||||||
@ -812,11 +812,13 @@ $repeatLabel lda $counterVar
|
|||||||
translate(stmt.elsepart)
|
translate(stmt.elsepart)
|
||||||
} else {
|
} else {
|
||||||
if(stmt.elsepart.isEmpty()) {
|
if(stmt.elsepart.isEmpty()) {
|
||||||
val instruction = branchInstruction(stmt.condition, true)
|
if(stmt.truepart.isNotEmpty()) {
|
||||||
val elseLabel = program.makeLabel("branch_else")
|
val instruction = branchInstruction(stmt.condition, true)
|
||||||
out(" $instruction $elseLabel")
|
val elseLabel = program.makeLabel("branch_else")
|
||||||
translate(stmt.truepart)
|
out(" $instruction $elseLabel")
|
||||||
out(elseLabel)
|
translate(stmt.truepart)
|
||||||
|
out(elseLabel)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val instruction = branchInstruction(stmt.condition, true)
|
val instruction = branchInstruction(stmt.condition, true)
|
||||||
val elseLabel = program.makeLabel("branch_else")
|
val elseLabel = program.makeLabel("branch_else")
|
||||||
@ -909,8 +911,7 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(asm: InlineAssembly) {
|
private fun translate(asm: InlineAssembly) {
|
||||||
val assembly = asm.assembly.trimEnd().trimStart('\r', '\n')
|
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
|
||||||
assemblyLines.add(assembly)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
|
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
|
||||||
@ -1069,11 +1070,20 @@ $repeatLabel lda $counterVar
|
|||||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
|
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||||
if(saveA)
|
if(saveA)
|
||||||
out(" pha")
|
out(" pha")
|
||||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(ptrAndIndex.second.isSimple) {
|
||||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
if(saveA)
|
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||||
out(" pla")
|
if(saveA)
|
||||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
out(" pla")
|
||||||
|
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
|
||||||
|
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, true)
|
||||||
|
if(saveA)
|
||||||
|
out(" pla")
|
||||||
|
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(pointervar!=null && isZpVar(pointervar)) {
|
if(pointervar!=null && isZpVar(pointervar)) {
|
||||||
@ -1081,9 +1091,16 @@ $repeatLabel lda $counterVar
|
|||||||
out(" lda (${asmSymbolName(pointervar)}),y")
|
out(" lda (${asmSymbolName(pointervar)}),y")
|
||||||
} else {
|
} else {
|
||||||
// copy the pointer var to zp first
|
// copy the pointer var to zp first
|
||||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(ptrAndIndex.second.isSimple) {
|
||||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||||
|
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
|
||||||
|
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -1577,8 +1594,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1612,8 +1635,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1649,8 +1678,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UWORD, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel")
|
return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1688,8 +1724,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.WORD, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1728,8 +1771,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1765,8 +1814,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.BYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1808,8 +1863,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UWORD, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2")
|
return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1852,8 +1914,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleLeftOperand(left, right, ::code))
|
if(wordJumpForSimpleLeftOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(right.isSimple) {
|
||||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.WORD, right)
|
||||||
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1893,8 +1962,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1930,8 +2005,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.BYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1975,8 +2056,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UWORD, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2023,8 +2111,15 @@ $repeatLabel lda $counterVar
|
|||||||
return code(asmVariableName(left))
|
return code(asmVariableName(left))
|
||||||
}
|
}
|
||||||
|
|
||||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(right.isSimple) {
|
||||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.WORD, right)
|
||||||
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2060,8 +2155,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2094,8 +2195,14 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.BYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
|
||||||
|
out(" pla")
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2130,8 +2237,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(right.isSimple) {
|
||||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UWORD, right)
|
||||||
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2169,8 +2283,15 @@ $repeatLabel lda $counterVar
|
|||||||
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
if(wordJumpForSimpleRightOperands(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.WORD, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2205,8 +2326,17 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else if(right.isSimple) {
|
||||||
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(right, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
out(" cmp P8ZP_SCRATCH_B1 | bne $jumpIfFalseLabel")
|
out(" cmp P8ZP_SCRATCH_B1 | bne $jumpIfFalseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2242,8 +2372,17 @@ $repeatLabel lda $counterVar
|
|||||||
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
if(byteJumpForSimpleRightOperand(left, right, ::code))
|
||||||
return
|
return
|
||||||
|
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.A)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.A)
|
||||||
|
} else if(right.isSimple) {
|
||||||
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
assignExpressionToRegister(right, RegisterOrPair.A)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UBYTE, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
return code("P8ZP_SCRATCH_B1")
|
return code("P8ZP_SCRATCH_B1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2309,8 +2448,18 @@ $repeatLabel lda $counterVar
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||||
|
} else if(right.isSimple) {
|
||||||
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UWORD, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
out("""
|
out("""
|
||||||
cmp P8ZP_SCRATCH_W2
|
cmp P8ZP_SCRATCH_W2
|
||||||
bne $jumpIfFalseLabel
|
bne $jumpIfFalseLabel
|
||||||
@ -2385,8 +2534,18 @@ $repeatLabel lda $counterVar
|
|||||||
+""")
|
+""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
if(left.isSimple) {
|
||||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||||
|
} else if (right.isSimple) {
|
||||||
|
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||||
|
} else {
|
||||||
|
pushCpuStack(DataType.UWORD, left)
|
||||||
|
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
|
restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
}
|
||||||
out("""
|
out("""
|
||||||
cmp P8ZP_SCRATCH_W2
|
cmp P8ZP_SCRATCH_W2
|
||||||
bne +
|
bne +
|
||||||
@ -2781,7 +2940,7 @@ $repeatLabel lda $counterVar
|
|||||||
val parameter = target.subroutineParameter
|
val parameter = target.subroutineParameter
|
||||||
if(parameter!=null) {
|
if(parameter!=null) {
|
||||||
val sub = parameter.definingSubroutine!!
|
val sub = parameter.definingSubroutine!!
|
||||||
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs" }
|
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs ${sub.position}" }
|
||||||
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
|
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
|
||||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||||
if(reg.statusflag!=null) {
|
if(reg.statusflag!=null) {
|
||||||
@ -2867,17 +3026,27 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun popCpuStack(dt: DataType, targetAsmVarName: String) {
|
||||||
|
when(dt) {
|
||||||
|
in ByteDatatypes -> out(" pla | sta $targetAsmVarName")
|
||||||
|
in WordDatatypes -> out(" pla | sta $targetAsmVarName+1 | pla | sta $targetAsmVarName")
|
||||||
|
else -> throw AssemblyError("can't pop $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun pushCpuStack(dt: DataType, value: Expression) {
|
internal fun pushCpuStack(dt: DataType, value: Expression) {
|
||||||
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
|
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
|
||||||
if(dt in ByteDatatypes) {
|
if(dt in ByteDatatypes) {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.A, signed)
|
assignExpressionToRegister(value, RegisterOrPair.A, signed)
|
||||||
out(" pha")
|
out(" pha")
|
||||||
} else {
|
} else if(dt in WordDatatypes) {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
|
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
|
||||||
if (isTargetCpu(CpuType.CPU65c02))
|
if (isTargetCpu(CpuType.CPU65c02))
|
||||||
out(" pha | phy")
|
out(" pha | phy")
|
||||||
else
|
else
|
||||||
out(" pha | tya | pha")
|
out(" pha | tya | pha")
|
||||||
|
} else {
|
||||||
|
throw AssemblyError("can't push $dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
|||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods= optimizeJsrRts(linesByFour)
|
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
@ -450,7 +450,7 @@ private fun getAddressArg(line: String, program: Program): UInt? {
|
|||||||
val identMatch = identifierRegex.find(loadArg)
|
val identMatch = identifierRegex.find(loadArg)
|
||||||
if(identMatch!=null) {
|
if(identMatch!=null) {
|
||||||
val identifier = identMatch.value
|
val identifier = identMatch.value
|
||||||
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
|
val decl = program.toplevelModule.lookup(identifier.split('.')) as? VarDecl
|
||||||
if(decl!=null) {
|
if(decl!=null) {
|
||||||
when(decl.type){
|
when(decl.type){
|
||||||
VarDeclType.VAR -> null
|
VarDeclType.VAR -> null
|
||||||
@ -486,8 +486,11 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
|
|||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// jsr Sub + rts -> jmp Sub
|
// jsr Sub + rts -> jmp Sub
|
||||||
|
// rts + jmp -> remove jmp
|
||||||
|
// rts + bxx -> remove bxx
|
||||||
|
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (lines in linesByFour) {
|
for (lines in linesByFour) {
|
||||||
val first = lines[0].value
|
val first = lines[0].value
|
||||||
@ -496,6 +499,28 @@ private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<
|
|||||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||||
mods += Modification(lines[1].index, true, null)
|
mods += Modification(lines[1].index, true, null)
|
||||||
}
|
}
|
||||||
|
else if (" rts" in first || "\trts" in first) {
|
||||||
|
if (" jmp" in second || "\tjmp" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bra" in second || "\tbra" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bcc" in second || "\tbcc" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bcs" in second || "\tbcs" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" beq" in second || "\tbeq" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bne" in second || "\tbne" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bmi" in second || "\tbmi" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bpl" in second || "\tbpl" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bvs" in second || "\tbvs" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
else if (" bvc" in second || "\tbvc" in second)
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ internal class AssemblyProgram(
|
|||||||
breakpoints.add("break \$" + match.groupValues[1])
|
breakpoints.add("break \$" + match.groupValues[1])
|
||||||
}
|
}
|
||||||
val num = breakpoints.size
|
val num = breakpoints.size
|
||||||
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
breakpoints.add(0, "; breakpoint list now follows")
|
||||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||||
breakpoints.add(2, "del")
|
breakpoints.add(2, "del")
|
||||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||||
|
@ -43,7 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
|
||||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"rol" -> funcRol(fcall)
|
"rol" -> funcRol(fcall)
|
||||||
"rol2" -> funcRol2(fcall)
|
"rol2" -> funcRol2(fcall)
|
||||||
@ -255,15 +254,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
||||||
} else {
|
} else {
|
||||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
if(arg1.isSimple) {
|
||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
|
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||||
|
} else {
|
||||||
|
asmgen.pushCpuStack(DataType.UBYTE, arg1)
|
||||||
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||||
|
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
if(arg1.isSimple) {
|
||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
|
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||||
|
} else {
|
||||||
|
asmgen.pushCpuStack(DataType.UBYTE, arg1)
|
||||||
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||||
|
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@ -289,13 +300,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
+""")
|
+""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
if(arg1.isSimple) {
|
||||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||||
asmgen.out("""
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||||
cpy P8ZP_SCRATCH_W1+1
|
asmgen.out("""
|
||||||
bne +
|
cpy P8ZP_SCRATCH_W1+1
|
||||||
cmp P8ZP_SCRATCH_W1
|
bne +
|
||||||
+""")
|
cmp P8ZP_SCRATCH_W1
|
||||||
|
+""")
|
||||||
|
} else {
|
||||||
|
asmgen.pushCpuStack(DataType.UWORD, arg1)
|
||||||
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||||
|
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||||
|
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||||
|
asmgen.out("""
|
||||||
|
cpy P8ZP_SCRATCH_W1+1
|
||||||
|
bne +
|
||||||
|
cmp P8ZP_SCRATCH_W1
|
||||||
|
+""")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@ -307,7 +330,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
if(discardResult || fcall !is BuiltinFunctionCall)
|
if(discardResult || fcall !is BuiltinFunctionCall)
|
||||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||||
val name = (fcall.args[0] as StringLiteral).value
|
val name = (fcall.args[0] as StringLiteral).value
|
||||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
|
||||||
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
|
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
|
||||||
slabname.linkParents(fcall)
|
slabname.linkParents(fcall)
|
||||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
||||||
@ -687,28 +710,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
|
||||||
when(func.name) {
|
|
||||||
"rnd" -> {
|
|
||||||
if(resultToStack)
|
|
||||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
|
||||||
else {
|
|
||||||
asmgen.out(" jsr math.randbyte")
|
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"rndw" -> {
|
|
||||||
if(resultToStack)
|
|
||||||
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
|
||||||
else {
|
|
||||||
asmgen.out(" jsr math.randword")
|
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, asmgen), RegisterOrPair.AY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("wrong func")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcPokeW(fcall: IFunctionCall) {
|
private fun funcPokeW(fcall: IFunctionCall) {
|
||||||
when(val addrExpr = fcall.args[0]) {
|
when(val addrExpr = fcall.args[0]) {
|
||||||
is NumericLiteral -> {
|
is NumericLiteral -> {
|
||||||
|
@ -53,7 +53,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
||||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
||||||
|
|
||||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
|
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
|
||||||
// Output only the code to set up the parameters and perform the actual call
|
// Output only the code to set up the parameters and perform the actual call
|
||||||
// NOTE: does NOT output the code to deal with the result values!
|
// NOTE: does NOT output the code to deal with the result values!
|
||||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||||
@ -124,7 +124,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
// this is called when one or more of the arguments are 'complex' and
|
// this is called when one or more of the arguments are 'complex' and
|
||||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||||
|
|
||||||
require(callee.isAsmSubroutine)
|
require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
|
||||||
if(callee.parameters.isEmpty())
|
if(callee.parameters.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -202,16 +202,10 @@ internal class ProgramAndVarsGen(
|
|||||||
asmsubs2asm(block.statements)
|
asmsubs2asm(block.statements)
|
||||||
|
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out("; subroutines in this block")
|
|
||||||
|
|
||||||
// First translate regular statements, and then put the subroutines at the end.
|
|
||||||
// (regular statements = everything except the initialization assignments;
|
|
||||||
// these will be part of the prog8_init_vars init routine generated below)
|
|
||||||
val initializers = blockVariableInitializers.getValue(block)
|
val initializers = blockVariableInitializers.getValue(block)
|
||||||
val statements = block.statements.filterNot { it in initializers }
|
val notInitializers = block.statements.filterNot { it in initializers }
|
||||||
val (subroutine, stmts) = statements.partition { it is Subroutine }
|
notInitializers.forEach { asmgen.translate(it) }
|
||||||
stmts.forEach { asmgen.translate(it) }
|
|
||||||
subroutine.forEach { asmgen.translate(it) }
|
|
||||||
|
|
||||||
if(!options.dontReinitGlobals) {
|
if(!options.dontReinitGlobals) {
|
||||||
// generate subroutine to initialize block-level (global) variables
|
// generate subroutine to initialize block-level (global) variables
|
||||||
@ -229,7 +223,7 @@ internal class ProgramAndVarsGen(
|
|||||||
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
|
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
|
||||||
|
|
||||||
private fun createBlockVariables(block: Block) {
|
private fun createBlockVariables(block: Block) {
|
||||||
val scope = symboltable.lookupOrElse(block.name) { throw AssemblyError("lookup") }
|
val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
|
||||||
require(scope.type==StNodeType.BLOCK)
|
require(scope.type==StNodeType.BLOCK)
|
||||||
val varsInBlock = getVars(scope)
|
val varsInBlock = getVars(scope)
|
||||||
|
|
||||||
@ -248,7 +242,7 @@ internal class ProgramAndVarsGen(
|
|||||||
|
|
||||||
// normal statically allocated variables
|
// normal statically allocated variables
|
||||||
val variables = varsInBlock
|
val variables = varsInBlock
|
||||||
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
|
||||||
.map { it.value as StStaticVariable }
|
.map { it.value as StStaticVariable }
|
||||||
nonZpVariables2asm(variables)
|
nonZpVariables2asm(variables)
|
||||||
}
|
}
|
||||||
@ -292,7 +286,7 @@ internal class ProgramAndVarsGen(
|
|||||||
// regular subroutine
|
// regular subroutine
|
||||||
asmgen.out("${sub.name}\t$asmStartScope")
|
asmgen.out("${sub.name}\t$asmStartScope")
|
||||||
|
|
||||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
val scope = symboltable.lookupOrElse(sub.scopedName.joinToString(".")) { throw AssemblyError("lookup") }
|
||||||
require(scope.type==StNodeType.SUBROUTINE)
|
require(scope.type==StNodeType.SUBROUTINE)
|
||||||
val varsInSubroutine = getVars(scope)
|
val varsInSubroutine = getVars(scope)
|
||||||
|
|
||||||
@ -363,7 +357,7 @@ internal class ProgramAndVarsGen(
|
|||||||
|
|
||||||
// normal statically allocated variables
|
// normal statically allocated variables
|
||||||
val variables = varsInSubroutine
|
val variables = varsInSubroutine
|
||||||
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
|
||||||
.map { it.value as StStaticVariable }
|
.map { it.value as StStaticVariable }
|
||||||
nonZpVariables2asm(variables)
|
nonZpVariables2asm(variables)
|
||||||
|
|
||||||
@ -434,14 +428,14 @@ internal class ProgramAndVarsGen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ZpStringWithInitial(
|
private class ZpStringWithInitial(
|
||||||
val name: List<String>,
|
val name: String,
|
||||||
val alloc: Zeropage.ZpAllocation,
|
val alloc: MemoryAllocator.VarAllocation,
|
||||||
val value: Pair<String, Encoding>
|
val value: Pair<String, Encoding>
|
||||||
)
|
)
|
||||||
|
|
||||||
private class ZpArrayWithInitial(
|
private class ZpArrayWithInitial(
|
||||||
val name: List<String>,
|
val name: String,
|
||||||
val alloc: Zeropage.ZpAllocation,
|
val alloc: MemoryAllocator.VarAllocation,
|
||||||
val value: StArray
|
val value: StArray
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -449,9 +443,10 @@ internal class ProgramAndVarsGen(
|
|||||||
val result = mutableListOf<ZpStringWithInitial>()
|
val result = mutableListOf<ZpStringWithInitial>()
|
||||||
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
||||||
for (variable in vars) {
|
for (variable in vars) {
|
||||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
val scopedName = variable.key.joinToString(".")
|
||||||
|
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||||
if(svar.onetimeInitializationStringValue!=null)
|
if(svar.onetimeInitializationStringValue!=null)
|
||||||
result.add(ZpStringWithInitial(variable.key, variable.value, svar.onetimeInitializationStringValue!!))
|
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -460,15 +455,17 @@ internal class ProgramAndVarsGen(
|
|||||||
val result = mutableListOf<ZpArrayWithInitial>()
|
val result = mutableListOf<ZpArrayWithInitial>()
|
||||||
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
||||||
for (variable in vars) {
|
for (variable in vars) {
|
||||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
val scopedName = variable.key.joinToString(".")
|
||||||
|
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||||
if(svar.onetimeInitializationArrayValue!=null)
|
if(svar.onetimeInitializationArrayValue!=null)
|
||||||
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.onetimeInitializationArrayValue!!))
|
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun zeropagevars2asm(varNames: Set<List<String>>) {
|
private fun zeropagevars2asm(varNames: Set<String>) {
|
||||||
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }
|
val namesLists = varNames.map { it.split('.') }.toSet()
|
||||||
|
val zpVariables = allocator.zeropageVars.filter { it.key in namesLists }.toList().sortedBy { it.second.address }
|
||||||
for ((scopedName, zpvar) in zpVariables) {
|
for ((scopedName, zpvar) in zpVariables) {
|
||||||
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
|
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
|
||||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||||
@ -479,7 +476,7 @@ internal class ProgramAndVarsGen(
|
|||||||
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
|
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out("; non-zeropage variables")
|
asmgen.out("; non-zeropage variables")
|
||||||
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
|
val (stringvars, othervars) = variables.sortedBy { it.name }.partition { it.dt==DataType.STR }
|
||||||
stringvars.forEach {
|
stringvars.forEach {
|
||||||
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
|
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
|
||||||
}
|
}
|
||||||
@ -585,10 +582,10 @@ internal class ProgramAndVarsGen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
|
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
|
||||||
memvars.forEach {
|
memvars.sortedBy { it.address }.forEach {
|
||||||
asmgen.out(" ${it.name} = ${it.address.toHex()}")
|
asmgen.out(" ${it.name} = ${it.address.toHex()}")
|
||||||
}
|
}
|
||||||
consts.forEach {
|
consts.sortedBy { it.name }.forEach {
|
||||||
if(it.dt==DataType.FLOAT)
|
if(it.dt==DataType.FLOAT)
|
||||||
asmgen.out(" ${it.name} = ${it.value}")
|
asmgen.out(" ${it.name} = ${it.value}")
|
||||||
else
|
else
|
||||||
@ -626,8 +623,8 @@ internal class ProgramAndVarsGen(
|
|||||||
if(it.number!=null) {
|
if(it.number!=null) {
|
||||||
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
||||||
}
|
}
|
||||||
else if(it.addressOf!=null) {
|
else if(it.addressOfSymbol!=null) {
|
||||||
asmgen.asmSymbolName(it.addressOf!!)
|
asmgen.asmSymbolName(it.addressOfSymbol!!)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw AssemblyError("weird array elt")
|
throw AssemblyError("weird array elt")
|
||||||
|
@ -16,13 +16,13 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
|
|
||||||
private val zeropage = options.compTarget.machine.zeropage
|
private val zeropage = options.compTarget.machine.zeropage
|
||||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||||
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
|
internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
|
||||||
|
|
||||||
init {
|
init {
|
||||||
allocateZeropageVariables()
|
allocateZeropageVariables()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
|
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropageVars
|
||||||
|
|
||||||
internal fun getFloatAsmConst(number: Double): String {
|
internal fun getFloatAsmConst(number: Double): String {
|
||||||
val asmName = globalFloatConsts[number]
|
val asmName = globalFloatConsts[number]
|
||||||
@ -56,7 +56,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
|
|
||||||
varsRequiringZp.forEach { variable ->
|
varsRequiringZp.forEach { variable ->
|
||||||
val result = zeropage.allocate(
|
val result = zeropage.allocate(
|
||||||
variable.scopedName,
|
variable.scopedName.split('.'),
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.position,
|
||||||
@ -75,7 +75,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
varsPreferringZp.forEach { variable ->
|
varsPreferringZp.forEach { variable ->
|
||||||
val result = zeropage.allocate(
|
val result = zeropage.allocate(
|
||||||
variable.scopedName,
|
variable.scopedName.split('.'),
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.position,
|
||||||
@ -88,13 +88,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
// try to allocate any other interger variables into the zeropage until it is full.
|
// try to allocate any other interger variables into the zeropage until it is full.
|
||||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
for (variable in varsDontCare.sortedBy { it.scopedName.size }) {
|
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
|
||||||
|
for (variable in sortedList) {
|
||||||
if(variable.dt in IntegerDatatypes) {
|
if(variable.dt in IntegerDatatypes) {
|
||||||
if(zeropage.free.isEmpty()) {
|
if(zeropage.free.isEmpty()) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
val result = zeropage.allocate(
|
val result = zeropage.allocate(
|
||||||
variable.scopedName,
|
variable.scopedName.split('.'),
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.position,
|
||||||
@ -124,6 +125,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
collect(st)
|
collect(st)
|
||||||
return vars
|
return vars.sortedBy { it.dt }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
|
||||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||||
"source dt size must be less or equal to target dt size at $position"
|
"source dt size must be less or equal to target dt size at $position"
|
||||||
}
|
}
|
||||||
|
@ -27,13 +27,6 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
|
|
||||||
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
|
|
||||||
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, origtarget.datatype, origtarget.scope,
|
|
||||||
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
|
|
||||||
} else origtarget
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateNormalAssignment(assign: AsmAssignment) {
|
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||||
if(assign.isAugmentable) {
|
if(assign.isAugmentable) {
|
||||||
augmentableAsmGen.translate(assign)
|
augmentableAsmGen.translate(assign)
|
||||||
@ -287,18 +280,25 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
// first assign the value to the target then apply the operator in place on the target.
|
if(assign.target.array==null) {
|
||||||
translateNormalAssignment(AsmAssignment(
|
// First assign the value to the target then apply the operator in place on the target.
|
||||||
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
|
// This saves a temporary variable
|
||||||
assign.target,
|
translateNormalAssignment(
|
||||||
false, program.memsizer, assign.position
|
AsmAssignment(
|
||||||
))
|
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
|
||||||
val target = virtualRegsToVariables(assign.target)
|
assign.target,
|
||||||
when(value.operator) {
|
false, program.memsizer, assign.position
|
||||||
"+" -> {}
|
)
|
||||||
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
|
)
|
||||||
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
|
when (value.operator) {
|
||||||
else -> throw AssemblyError("invalid prefix operator")
|
"+" -> {}
|
||||||
|
"-" -> augmentableAsmGen.inplaceNegate(assign, true)
|
||||||
|
"~" -> augmentableAsmGen.inplaceInvert(assign)
|
||||||
|
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
|
||||||
|
else -> throw AssemblyError("invalid prefix operator")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assignPrefixedExpressionToArrayElt(assign)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ContainmentCheck -> {
|
is ContainmentCheck -> {
|
||||||
@ -317,6 +317,28 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment) {
|
||||||
|
require(assign.source.expression is PrefixExpression)
|
||||||
|
if(assign.source.datatype==DataType.FLOAT) {
|
||||||
|
// floatarray[x] = -value ... just use FAC1 to calculate the expression into and then store that back into the array.
|
||||||
|
assignExpressionToRegister(assign.source.expression, RegisterOrPair.FAC1, true)
|
||||||
|
assignFAC1float(assign.target)
|
||||||
|
} else {
|
||||||
|
// array[x] = -value ... use a tempvar then store that back into the array.
|
||||||
|
val tempvar = asmgen.getTempVarName(assign.target.datatype)
|
||||||
|
val assignToTempvar = AsmAssignment(assign.source,
|
||||||
|
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, assign.target.datatype, assign.target.scope, variableAsmName=tempvar, origAstTarget = assign.target.origAstTarget),
|
||||||
|
false, program.memsizer, assign.position)
|
||||||
|
asmgen.translateNormalAssignment(assignToTempvar)
|
||||||
|
when(assign.target.datatype) {
|
||||||
|
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
|
||||||
|
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
|
||||||
|
DataType.FLOAT -> assignVariableFloat(assign.target, tempvar)
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
|
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
|
||||||
when(target.datatype) {
|
when(target.datatype) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
@ -357,48 +379,60 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
if(!expr.inferType(program).isInteger)
|
if(!expr.inferType(program).isInteger)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
|
fun simpleLogicalBytesExpr() {
|
||||||
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
|
// both left and right expression operands are simple.
|
||||||
expr.left.isSimple && expr.right.isSimple) {
|
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||||
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
|
||||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
|
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||||
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
|
||||||
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
|
else {
|
||||||
else {
|
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
||||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
|
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
when (expr.operator) {
|
||||||
when (expr.operator) {
|
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||||
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||||
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
else -> throw AssemblyError("invalid operator")
|
||||||
else -> throw AssemblyError("invalid operator")
|
|
||||||
}
|
|
||||||
assignRegisterByte(assign.target, CpuRegister.A)
|
|
||||||
}
|
}
|
||||||
return true
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
}
|
}
|
||||||
if(expr.left.inferType(program).isWords && expr.right.inferType(program).isWords &&
|
}
|
||||||
expr.left.isSimple && expr.right.isSimple) {
|
|
||||||
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
fun simpleLogicalWordsExpr() {
|
||||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
|
// both left and right expression operands are simple.
|
||||||
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
|
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||||
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
|
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
|
||||||
else {
|
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
|
||||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
|
||||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
else {
|
||||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
|
||||||
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||||
when (expr.operator) {
|
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||||
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
|
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
|
||||||
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
|
when (expr.operator) {
|
||||||
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
|
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
|
||||||
else -> throw AssemblyError("invalid operator")
|
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
|
||||||
}
|
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
|
||||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
else -> throw AssemblyError("invalid operator")
|
||||||
|
}
|
||||||
|
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
|
||||||
|
if (expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes) {
|
||||||
|
if (expr.right.isSimple) {
|
||||||
|
simpleLogicalBytesExpr()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expr.left.inferType(program).isWords && expr.right.inferType(program).isWords) {
|
||||||
|
if (expr.right.isSimple) {
|
||||||
|
simpleLogicalWordsExpr()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -594,6 +628,58 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(expr.operator=="<<" || expr.operator==">>") {
|
||||||
|
val shifts = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if(shifts!=null) {
|
||||||
|
val dt = expr.left.inferType(program)
|
||||||
|
if(dt.isBytes && shifts in 0..7) {
|
||||||
|
val signed = dt istype DataType.BYTE
|
||||||
|
assignExpressionToRegister(expr.left, RegisterOrPair.A, signed)
|
||||||
|
if(expr.operator=="<<") {
|
||||||
|
repeat(shifts) {
|
||||||
|
asmgen.out(" asl a")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(signed && shifts>0) {
|
||||||
|
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||||
|
} else {
|
||||||
|
repeat(shifts) {
|
||||||
|
asmgen.out(" lsr a")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assignRegisterByte(assign.target, CpuRegister.A)
|
||||||
|
return true
|
||||||
|
} else if(dt.isWords && shifts in 0..7) {
|
||||||
|
val signed = dt istype DataType.WORD
|
||||||
|
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
|
||||||
|
if(expr.operator=="<<") {
|
||||||
|
if(shifts>0) {
|
||||||
|
asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||||
|
repeat(shifts) {
|
||||||
|
asmgen.out(" asl a | rol P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
|
asmgen.out(" ldy P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(signed) {
|
||||||
|
// TODO("shift AY >> $shifts signed")
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if(shifts>0) {
|
||||||
|
asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||||
|
repeat(shifts) {
|
||||||
|
asmgen.out(" lsr P8ZP_SCRATCH_B1 | ror a")
|
||||||
|
}
|
||||||
|
asmgen.out(" ldy P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -729,7 +815,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
||||||
when(variable.datatype) {
|
when(variable.datatype) {
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
require(elementDt.isBytes)
|
require(elementDt.isBytes) { "must be byte string ${variable.position}" }
|
||||||
val stringVal = variable.value as StringLiteral
|
val stringVal = variable.value as StringLiteral
|
||||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||||
@ -745,7 +831,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
throw AssemblyError("containment check of floats not supported")
|
throw AssemblyError("containment check of floats not supported")
|
||||||
}
|
}
|
||||||
in ArrayDatatypes -> {
|
in ArrayDatatypes -> {
|
||||||
require(elementDt.isInteger)
|
require(elementDt.isInteger) { "must be integer array ${variable.position}" }
|
||||||
val arrayVal = variable.value as ArrayLiteral
|
val arrayVal = variable.value as ArrayLiteral
|
||||||
val dt = elementDt.getOr(DataType.UNDEFINED)
|
val dt = elementDt.getOr(DataType.UNDEFINED)
|
||||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||||
@ -788,20 +874,20 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
throw AssemblyError("containment check of floats not supported")
|
throw AssemblyError("containment check of floats not supported")
|
||||||
}
|
}
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
val arrayVal = variable.value as ArrayLiteral
|
val numElements = variable.arraysize!!.constIndex()!!
|
||||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||||
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.A)
|
asmgen.restoreRegisterLocal(CpuRegister.A)
|
||||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
asmgen.out(" ldy #$numElements")
|
||||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
val arrayVal = variable.value as ArrayLiteral
|
val numElements = variable.arraysize!!.constIndex()!!
|
||||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
asmgen.out(" ldy #$numElements")
|
||||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -852,18 +938,17 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
fun assignViaExprEval(addressExpression: Expression) {
|
fun assignViaExprEval(addressExpression: Expression) {
|
||||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||||
assignRegisterByte(target, CpuRegister.A)
|
asmgen.out(" ldy #0")
|
||||||
|
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (value.addressExpression) {
|
when (value.addressExpression) {
|
||||||
is NumericLiteral -> {
|
is NumericLiteral -> {
|
||||||
val address = (value.addressExpression as NumericLiteral).number.toUInt()
|
val address = (value.addressExpression as NumericLiteral).number.toUInt()
|
||||||
assignMemoryByteIntoWord(target, address, null)
|
assignMemoryByteIntoWord(target, address, null)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
|
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
is BinaryExpression -> {
|
is BinaryExpression -> {
|
||||||
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
|
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
|
||||||
@ -877,6 +962,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
assignViaExprEval(value.addressExpression)
|
assignViaExprEval(value.addressExpression)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
|
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
|
||||||
@ -885,7 +971,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
|
|
||||||
|
|
||||||
// special case optimizations
|
// special case optimizations
|
||||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||||
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
|
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
|
||||||
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
||||||
|
|
||||||
@ -920,7 +1006,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(valueDt==DataType.UBYTE) {
|
if(valueDt==DataType.UBYTE || valueDt==DataType.BOOL) {
|
||||||
when(target.register) {
|
when(target.register) {
|
||||||
RegisterOrPair.A,
|
RegisterOrPair.A,
|
||||||
RegisterOrPair.X,
|
RegisterOrPair.X,
|
||||||
@ -963,11 +1049,12 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
|
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
|
||||||
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
|
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
|
||||||
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
|
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
if(!(valueDt isAssignableTo targetDt)) {
|
if(!(valueDt isAssignableTo targetDt)) {
|
||||||
if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
|
return if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
|
||||||
// word to byte, just take the lsb
|
// word to byte, just take the lsb
|
||||||
return assignCastViaLsbFunc(value, target)
|
assignCastViaLsbFunc(value, target)
|
||||||
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
|
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
|
||||||
// word to word, just assign
|
// word to word, just assign
|
||||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||||
@ -977,44 +1064,87 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
|
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
|
||||||
// byte to word, just assign
|
// byte to word, just assign
|
||||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
|
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
|
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
|
||||||
}
|
}
|
||||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
|
||||||
}
|
}
|
||||||
return
|
}
|
||||||
|
|
||||||
|
if(targetDt in IntegerDatatypes && valueDt in IntegerDatatypes && valueDt!=targetDt && valueDt.isAssignableTo(targetDt)) {
|
||||||
|
require(targetDt in WordDatatypes && valueDt in ByteDatatypes) {
|
||||||
|
"should be byte to word assignment ${origTypeCastExpression.position}"
|
||||||
|
}
|
||||||
|
when(target.kind) {
|
||||||
|
// TargetStorageKind.VARIABLE -> {
|
||||||
|
// This has been handled already earlier on line 961.
|
||||||
|
// // byte to word, just assign to registers first, then assign to variable
|
||||||
|
// assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
|
||||||
|
// assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, targetDt)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
// byte to word, just assign to registers first, then assign into array
|
||||||
|
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
|
||||||
|
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
// byte to word, just assign to registers
|
||||||
|
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> {
|
||||||
|
// byte to word, just assign to registers first, then push onto stack
|
||||||
|
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
|
||||||
|
asmgen.out("""
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
tya
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex""")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||||
when(valueDt) {
|
when(valueDt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.FREADUY")
|
asmgen.out(" jsr floats.FREADUY")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.A, true)
|
assignExpressionToRegister(value, RegisterOrPair.A, true)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.FREADSA")
|
asmgen.out(" jsr floats.FREADSA")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.GIVUAYFAY")
|
asmgen.out(" jsr floats.GIVUAYFAY")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
|
||||||
asmgen.out(" jsr floats.GIVAYFAY")
|
asmgen.out(" jsr floats.GIVAYFAY")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid dt")
|
else -> throw AssemblyError("invalid dt")
|
||||||
}
|
}
|
||||||
if(target.register==RegisterOrPair.FAC2) {
|
if(target.register==RegisterOrPair.FAC2) {
|
||||||
asmgen.out(" jsr floats.MOVEF")
|
asmgen.out(" jsr floats.MOVEF")
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
|
||||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
|
||||||
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
|
||||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||||
|
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||||
|
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
||||||
|
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
||||||
@ -1170,14 +1300,10 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(
|
asmgen.out(
|
||||||
" st${
|
" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||||
regs.toString().lowercase()
|
|
||||||
} $targetAsmVarName | stz $targetAsmVarName+1")
|
|
||||||
else
|
else
|
||||||
asmgen.out(
|
asmgen.out(
|
||||||
" st${
|
" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||||
regs.toString().lowercase()
|
|
||||||
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
when(regs) {
|
when(regs) {
|
||||||
@ -1972,18 +2098,10 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||||
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair
|
// we make an exception in the type check for assigning something to a register pair AX, AY or XY
|
||||||
// these will be correctly typecasted from a byte to a word value
|
// these will be correctly typecasted from a byte to a word value here
|
||||||
if(target.register !in Cx16VirtualRegisters &&
|
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
|
||||||
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
|
require(target.datatype in ByteDatatypes) { "assign target must be byte type ${target.origAstTarget?.position ?: ""}"}
|
||||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
|
||||||
val parts = target.asmVarname.split('.')
|
|
||||||
if (parts.size != 2 || parts[0] != "cx16")
|
|
||||||
require(target.datatype in ByteDatatypes)
|
|
||||||
} else {
|
|
||||||
require(target.datatype in ByteDatatypes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when(target.kind) {
|
when(target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -2072,7 +2190,9 @@ internal class AssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
|
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
|
||||||
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
|
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes) {
|
||||||
|
"assign target must be word type ${target.origAstTarget?.position ?: ""}"
|
||||||
|
}
|
||||||
if(target.datatype==DataType.FLOAT)
|
if(target.datatype==DataType.FLOAT)
|
||||||
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
|
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
|
||||||
|
|
||||||
|
@ -16,18 +16,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
) {
|
) {
|
||||||
fun translate(assign: AsmAssignment) {
|
fun translate(assign: AsmAssignment) {
|
||||||
require(assign.isAugmentable)
|
require(assign.isAugmentable)
|
||||||
require(assign.source.kind== SourceStorageKind.EXPRESSION)
|
require(assign.source.kind == SourceStorageKind.EXPRESSION) {
|
||||||
|
"non-expression assign value should be handled elsewhere ${assign.position}"
|
||||||
|
}
|
||||||
|
|
||||||
when (val value = assign.source.expression!!) {
|
when (val value = assign.source.expression!!) {
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
// A = -A , A = +A, A = ~A, A = not A
|
// A = -A , A = +A, A = ~A, A = not A
|
||||||
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
|
|
||||||
val itype = value.inferType(program)
|
|
||||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
when (value.operator) {
|
when (value.operator) {
|
||||||
"+" -> {}
|
"+" -> {}
|
||||||
"-" -> inplaceNegate(target, type)
|
"-" -> inplaceNegate(assign, false)
|
||||||
"~" -> inplaceInvert(target, type)
|
"~" -> inplaceInvert(assign)
|
||||||
else -> throw AssemblyError("invalid prefix operator")
|
else -> throw AssemblyError("invalid prefix operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1796,8 +1795,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
internal fun inplaceInvert(assign: AsmAssignment) {
|
||||||
when (dt) {
|
val target = assign.target
|
||||||
|
when (assign.target.datatype) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -1840,7 +1840,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
||||||
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
@ -1864,15 +1865,24 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
||||||
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invert of invalid type")
|
else -> throw AssemblyError("invert of invalid type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
|
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
|
||||||
when (dt) {
|
val target = assign.target
|
||||||
|
val datatype = if(ignoreDatatype) {
|
||||||
|
when(target.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> DataType.BYTE
|
||||||
|
DataType.UWORD, DataType.WORD -> DataType.WORD
|
||||||
|
else -> target.datatype
|
||||||
|
}
|
||||||
|
} else target.datatype
|
||||||
|
when (datatype) {
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -1896,9 +1906,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
else -> throw AssemblyError("invalid reg dt for byte negate")
|
else -> throw AssemblyError("invalid reg dt for byte negate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
|
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
||||||
else -> throw AssemblyError("no asm gen for in-place negate byte")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
@ -1955,12 +1966,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
||||||
else -> throw AssemblyError("no asm gen for in-place negate word")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
|
TargetStorageKind.REGISTER -> {
|
||||||
|
when(target.register!!) {
|
||||||
|
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.NEGOP")
|
||||||
|
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.MOVFA | jsr floats.NEGOP | jsr floats.MOVEF")
|
||||||
|
else -> throw AssemblyError("invalid float register")
|
||||||
|
}
|
||||||
|
}
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
// simply flip the sign bit in the float
|
// simply flip the sign bit in the float
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -1970,10 +1990,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
||||||
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||||
|
else -> throw AssemblyError("weird target for in-place float negation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("negate of invalid type $dt")
|
else -> throw AssemblyError("negate of invalid type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,16 +3,12 @@ package prog8.codegen.intermediate
|
|||||||
import prog8.code.ast.*
|
import prog8.code.ast.*
|
||||||
import prog8.code.core.AssemblyError
|
import prog8.code.core.AssemblyError
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.Position
|
|
||||||
import prog8.code.core.SignedDatatypes
|
import prog8.code.core.SignedDatatypes
|
||||||
import prog8.intermediate.IRCodeChunk
|
import prog8.intermediate.*
|
||||||
import prog8.intermediate.IRInstruction
|
|
||||||
import prog8.intermediate.Opcode
|
|
||||||
import prog8.intermediate.VmDataType
|
|
||||||
|
|
||||||
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
|
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
|
||||||
|
|
||||||
internal fun translate(assignment: PtAssignment): IRCodeChunk {
|
internal fun translate(assignment: PtAssignment): IRCodeChunks {
|
||||||
if(assignment.target.children.single() is PtMachineRegister)
|
if(assignment.target.children.single() is PtMachineRegister)
|
||||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||||
|
|
||||||
@ -22,13 +18,13 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
translateRegularAssign(assignment)
|
translateRegularAssign(assignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunk {
|
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
|
||||||
val ident = assignment.target.identifier
|
val ident = assignment.target.identifier
|
||||||
val memory = assignment.target.memory
|
val memory = assignment.target.memory
|
||||||
val array = assignment.target.array
|
val array = assignment.target.array
|
||||||
|
|
||||||
return if(ident!=null) {
|
return if(ident!=null) {
|
||||||
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment)
|
assignSelfInMemory(ident.name, assignment.value, assignment)
|
||||||
} else if(memory != null) {
|
} else if(memory != null) {
|
||||||
if(memory.address is PtNumber)
|
if(memory.address is PtNumber)
|
||||||
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
||||||
@ -48,23 +44,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
address: Int,
|
address: Int,
|
||||||
value: PtExpression,
|
value: PtExpression,
|
||||||
origAssign: PtAssignment
|
origAssign: PtAssignment
|
||||||
): IRCodeChunk {
|
): IRCodeChunks {
|
||||||
val vmDt = codeGen.vmType(value.type)
|
val vmDt = codeGen.irType(value.type)
|
||||||
val code = IRCodeChunk(origAssign.position)
|
|
||||||
when(value) {
|
when(value) {
|
||||||
is PtIdentifier -> return code // do nothing, x=x null assignment.
|
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
|
||||||
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
|
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
|
||||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null, value.position)
|
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
|
||||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
|
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
|
||||||
is PtMemoryByte -> {
|
is PtMemoryByte -> {
|
||||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
||||||
code // do nothing, mem=mem null assignment.
|
emptyList() // do nothing, mem=mem null assignment.
|
||||||
else {
|
else {
|
||||||
// read and write a (i/o) memory location to itself.
|
// read and write a (i/o) memory location to itself.
|
||||||
val tempReg = codeGen.vmRegisters.nextFree()
|
val tempReg = codeGen.registers.nextFree()
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
||||||
code
|
listOf(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> return fallbackAssign(origAssign)
|
else -> return fallbackAssign(origAssign)
|
||||||
@ -75,25 +71,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
symbol: String,
|
symbol: String,
|
||||||
value: PtExpression,
|
value: PtExpression,
|
||||||
origAssign: PtAssignment
|
origAssign: PtAssignment
|
||||||
): IRCodeChunk {
|
): IRCodeChunks {
|
||||||
val vmDt = codeGen.vmType(value.type)
|
val vmDt = codeGen.irType(value.type)
|
||||||
val code = IRCodeChunk(origAssign.position)
|
return when(value) {
|
||||||
when(value) {
|
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
|
||||||
is PtIdentifier -> return code // do nothing, x=x null assignment.
|
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
|
||||||
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
|
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
|
||||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, null, symbol, value.position)
|
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
||||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
|
||||||
is PtMemoryByte -> {
|
is PtMemoryByte -> {
|
||||||
val tempReg = codeGen.vmRegisters.nextFree()
|
val code = IRCodeChunk(null, null)
|
||||||
|
val tempReg = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||||
return code
|
listOf(code)
|
||||||
}
|
}
|
||||||
else -> return fallbackAssign(origAssign)
|
|
||||||
|
else -> fallbackAssign(origAssign)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunk {
|
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
|
||||||
if (codeGen.options.slowCodegenWarnings)
|
if (codeGen.options.slowCodegenWarnings)
|
||||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
||||||
return translateRegularAssign(origAssign)
|
return translateRegularAssign(origAssign)
|
||||||
@ -102,12 +99,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
private fun inplaceBinexpr(
|
private fun inplaceBinexpr(
|
||||||
operator: String,
|
operator: String,
|
||||||
operand: PtExpression,
|
operand: PtExpression,
|
||||||
vmDt: VmDataType,
|
vmDt: IRDataType,
|
||||||
signed: Boolean,
|
signed: Boolean,
|
||||||
knownAddress: Int?,
|
knownAddress: Int?,
|
||||||
symbol: String?,
|
symbol: String?,
|
||||||
origAssign: PtAssignment
|
origAssign: PtAssignment
|
||||||
): IRCodeChunk {
|
): IRCodeChunks {
|
||||||
if(knownAddress!=null) {
|
if(knownAddress!=null) {
|
||||||
when (operator) {
|
when (operator) {
|
||||||
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
|
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
|
||||||
@ -139,8 +136,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
return fallbackAssign(origAssign)
|
return fallbackAssign(origAssign)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplacePrefix(operator: String, vmDt: VmDataType, knownAddress: Int?, addressSymbol: String?, position: Position): IRCodeChunk {
|
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
|
||||||
val code= IRCodeChunk(position)
|
val code= IRCodeChunk(null, null)
|
||||||
when(operator) {
|
when(operator) {
|
||||||
"+" -> { }
|
"+" -> { }
|
||||||
"-" -> {
|
"-" -> {
|
||||||
@ -150,8 +147,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
|
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
|
||||||
}
|
}
|
||||||
"~" -> {
|
"~" -> {
|
||||||
val regMask = codeGen.vmRegisters.nextFree()
|
val regMask = codeGen.registers.nextFree()
|
||||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
|
||||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
||||||
code += if(knownAddress!=null)
|
code += if(knownAddress!=null)
|
||||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
|
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
|
||||||
@ -160,48 +157,48 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird prefix operator")
|
else -> throw AssemblyError("weird prefix operator")
|
||||||
}
|
}
|
||||||
return code
|
return listOf(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunk {
|
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
||||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||||
val ident = assignment.target.identifier
|
val ident = assignment.target.identifier
|
||||||
val memory = assignment.target.memory
|
val memory = assignment.target.memory
|
||||||
val array = assignment.target.array
|
val array = assignment.target.array
|
||||||
val vmDt = codeGen.vmType(assignment.value.type)
|
val vmDt = codeGen.irType(assignment.value.type)
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val code = IRCodeChunk(assignment.position)
|
|
||||||
var resultRegister = -1
|
var resultRegister = -1
|
||||||
var resultFpRegister = -1
|
var resultFpRegister = -1
|
||||||
val zero = codeGen.isZero(assignment.value)
|
val zero = codeGen.isZero(assignment.value)
|
||||||
if(!zero) {
|
if(!zero) {
|
||||||
// calculate the assignment value
|
// calculate the assignment value
|
||||||
if (vmDt == VmDataType.FLOAT) {
|
if (vmDt == IRDataType.FLOAT) {
|
||||||
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
|
resultFpRegister = codeGen.registers.nextFreeFloat()
|
||||||
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
||||||
} else {
|
} else {
|
||||||
resultRegister = if (assignment.value is PtMachineRegister) {
|
resultRegister = if (assignment.value is PtMachineRegister) {
|
||||||
(assignment.value as PtMachineRegister).register
|
(assignment.value as PtMachineRegister).register
|
||||||
} else {
|
} else {
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
val reg = codeGen.registers.nextFree()
|
||||||
code += expressionEval.translateExpression(assignment.value, reg, -1)
|
result += expressionEval.translateExpression(assignment.value, reg, -1)
|
||||||
reg
|
reg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(ident!=null) {
|
if(ident!=null) {
|
||||||
val symbol = ident.targetName.joinToString(".")
|
val instruction = if(zero) {
|
||||||
code += if(zero) {
|
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = ident.name)
|
||||||
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
|
|
||||||
} else {
|
} else {
|
||||||
if (vmDt == VmDataType.FLOAT)
|
if (vmDt == IRDataType.FLOAT)
|
||||||
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = ident.name)
|
||||||
else
|
else
|
||||||
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = ident.name)
|
||||||
}
|
}
|
||||||
|
result += IRCodeChunk(null, null).also { it += instruction }
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
else if(array!=null) {
|
else if(array!=null) {
|
||||||
val variable = array.variable.targetName.joinToString(".")
|
val variable = array.variable.name
|
||||||
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
||||||
|
|
||||||
if(array.variable.type==DataType.UWORD) {
|
if(array.variable.type==DataType.UWORD) {
|
||||||
@ -210,85 +207,91 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||||
if(array.index.type!=DataType.UBYTE)
|
if(array.index.type!=DataType.UBYTE)
|
||||||
throw AssemblyError("non-array var indexing requires bytes index")
|
throw AssemblyError("non-array var indexing requires bytes index")
|
||||||
val idxReg = codeGen.vmRegisters.nextFree()
|
val idxReg = codeGen.registers.nextFree()
|
||||||
code += expressionEval.translateExpression(array.index, idxReg, -1)
|
result += expressionEval.translateExpression(array.index, idxReg, -1)
|
||||||
|
val code = IRCodeChunk(null, null)
|
||||||
if(zero) {
|
if(zero) {
|
||||||
// there's no STOREZIX instruction
|
// there's no STOREZIX instruction
|
||||||
resultRegister = codeGen.vmRegisters.nextFree()
|
resultRegister = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
||||||
}
|
}
|
||||||
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
|
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
|
||||||
return code
|
result += code
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
val fixedIndex = constIntValue(array.index)
|
val fixedIndex = constIntValue(array.index)
|
||||||
if(zero) {
|
if(zero) {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val offset = fixedIndex*itemsize
|
||||||
code += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset")
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
|
||||||
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.vmRegisters.nextFree()
|
val indexReg = codeGen.registers.nextFree()
|
||||||
code += loadIndexReg(array, itemsize, indexReg, array.position)
|
result += loadIndexReg(array, itemsize, indexReg)
|
||||||
code += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable)
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(vmDt== VmDataType.FLOAT) {
|
if(vmDt== IRDataType.FLOAT) {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val offset = fixedIndex*itemsize
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset")
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") }
|
||||||
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.vmRegisters.nextFree()
|
val indexReg = codeGen.registers.nextFree()
|
||||||
code += loadIndexReg(array, itemsize, indexReg, array.position)
|
result += loadIndexReg(array, itemsize, indexReg)
|
||||||
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val offset = fixedIndex*itemsize
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset")
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") }
|
||||||
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.vmRegisters.nextFree()
|
val indexReg = codeGen.registers.nextFree()
|
||||||
code += loadIndexReg(array, itemsize, indexReg, array.position)
|
result += loadIndexReg(array, itemsize, indexReg)
|
||||||
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
else if(memory!=null) {
|
else if(memory!=null) {
|
||||||
require(vmDt== VmDataType.BYTE)
|
require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
|
||||||
if(zero) {
|
if(zero) {
|
||||||
if(memory.address is PtNumber) {
|
if(memory.address is PtNumber) {
|
||||||
code += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt())
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
|
||||||
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(memory.address is PtNumber) {
|
if(memory.address is PtNumber) {
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) }
|
||||||
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw AssemblyError("weird assigntarget")
|
throw AssemblyError("weird assigntarget")
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int, position: Position): IRCodeChunk {
|
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(position)
|
return if(itemsize==1) {
|
||||||
if(itemsize==1) {
|
expressionEval.translateExpression(array.index, indexReg, -1)
|
||||||
code += expressionEval.translateExpression(array.index, indexReg, -1)
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||||
mult.children += array.index
|
mult.children += array.index
|
||||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||||
code += expressionEval.translateExpression(mult, indexReg, -1)
|
expressionEval.translateExpression(mult, indexReg, -1)
|
||||||
}
|
}
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,13 +4,12 @@ import prog8.code.StStaticVariable
|
|||||||
import prog8.code.ast.*
|
import prog8.code.ast.*
|
||||||
import prog8.code.core.AssemblyError
|
import prog8.code.core.AssemblyError
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.Position
|
|
||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
|
||||||
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
|
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
|
||||||
|
|
||||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
return when(call.name) {
|
return when(call.name) {
|
||||||
"any" -> funcAny(call, resultRegister)
|
"any" -> funcAny(call, resultRegister)
|
||||||
"all" -> funcAll(call, resultRegister)
|
"all" -> funcAll(call, resultRegister)
|
||||||
@ -25,9 +24,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
"rsave",
|
"rsave",
|
||||||
"rsavex",
|
"rsavex",
|
||||||
"rrestore",
|
"rrestore",
|
||||||
"rrestorex" -> IRCodeChunk(call.position) // vm doesn't have registers to save/restore
|
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
|
||||||
"rnd" -> funcRnd(resultRegister, call.position)
|
|
||||||
"rndw" -> funcRndw(resultRegister, call.position)
|
|
||||||
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
||||||
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
||||||
"msb" -> funcMsb(call, resultRegister)
|
"msb" -> funcMsb(call, resultRegister)
|
||||||
@ -37,7 +34,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
"peekw" -> funcPeekW(call, resultRegister)
|
"peekw" -> funcPeekW(call, resultRegister)
|
||||||
"poke" -> funcPoke(call)
|
"poke" -> funcPoke(call)
|
||||||
"pokew" -> funcPokeW(call)
|
"pokew" -> funcPokeW(call)
|
||||||
"pokemon" -> IRCodeChunk(call.position)
|
"pokemon" -> emptyList()
|
||||||
"mkword" -> funcMkword(call, resultRegister)
|
"mkword" -> funcMkword(call, resultRegister)
|
||||||
"sort" -> funcSort(call)
|
"sort" -> funcSort(call)
|
||||||
"reverse" -> funcReverse(call)
|
"reverse" -> funcReverse(call)
|
||||||
@ -49,20 +46,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val leftRegister = codeGen.registers.nextFree()
|
||||||
val leftRegister = codeGen.vmRegisters.nextFree()
|
val rightRegister = codeGen.registers.nextFree()
|
||||||
val rightRegister = codeGen.vmRegisters.nextFree()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
result += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
||||||
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
result += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
||||||
code += IRInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
result += IRCodeChunk(null, null).also {
|
||||||
return code
|
it += IRInstruction(Opcode.CMP, codeGen.irType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||||
val code = IRCodeChunk(call.position)
|
|
||||||
val syscall =
|
val syscall =
|
||||||
when (array.dt) {
|
when (array.dt) {
|
||||||
DataType.ARRAY_UB,
|
DataType.ARRAY_UB,
|
||||||
@ -72,17 +70,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
|
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
|
||||||
else -> throw IllegalArgumentException("weird type")
|
else -> throw IllegalArgumentException("weird type")
|
||||||
}
|
}
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
result += IRCodeChunk(null, null).also {
|
||||||
if (resultRegister != 0)
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
return code
|
if(resultRegister!=0)
|
||||||
|
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||||
val syscall =
|
val syscall =
|
||||||
when(array.dt) {
|
when(array.dt) {
|
||||||
DataType.ARRAY_UB,
|
DataType.ARRAY_UB,
|
||||||
@ -92,100 +93,118 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
|
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
|
||||||
else -> throw IllegalArgumentException("weird type")
|
else -> throw IllegalArgumentException("weird type")
|
||||||
}
|
}
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
result += IRCodeChunk(null, null).also {
|
||||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
if(resultRegister!=0)
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
|
if(resultRegister!=0)
|
||||||
return code
|
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
|
||||||
val sourceDt = call.args.single().type
|
val sourceDt = call.args.single().type
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(sourceDt!=DataType.UWORD) {
|
if(sourceDt!=DataType.UWORD) {
|
||||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||||
when (sourceDt) {
|
when (sourceDt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
code += IRInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = resultRegister)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
val notNegativeLabel = codeGen.createLabelName()
|
val notNegativeLabel = codeGen.createLabelName()
|
||||||
val compareReg = codeGen.vmRegisters.nextFree()
|
val compareReg = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
result += IRCodeChunk(null, null).also {
|
||||||
code += IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80)
|
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
||||||
code += IRInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, value=0x80)
|
||||||
code += IRInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
|
it += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||||
code += IRInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=resultRegister)
|
||||||
code += IRCodeLabel(notNegativeLabel)
|
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultRegister)
|
||||||
|
}
|
||||||
|
result += IRCodeChunk(notNegativeLabel, null)
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
val notNegativeLabel = codeGen.createLabelName()
|
val notNegativeLabel = codeGen.createLabelName()
|
||||||
val compareReg = codeGen.vmRegisters.nextFree()
|
val compareReg = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
result += IRCodeChunk(null, null).also {
|
||||||
code += IRInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000)
|
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
||||||
code += IRInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, value=0x8000)
|
||||||
code += IRInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
|
it += IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||||
code += IRCodeLabel(notNegativeLabel)
|
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=resultRegister)
|
||||||
|
}
|
||||||
|
result += IRCodeChunk(notNegativeLabel, null)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return code
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val reg = codeGen.registers.nextFree()
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
code += IRInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
|
result += IRCodeChunk(null, null).also {
|
||||||
return code
|
it += IRInstruction(Opcode.SGN, codeGen.irType(call.type), reg1 = resultRegister, reg2 = reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val reg = codeGen.registers.nextFree()
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
code += IRInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
|
result += IRCodeChunk(null, null).also {
|
||||||
return code
|
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultRegister, reg2=reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val code = IRCodeChunk(null, null)
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
val reg = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
|
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
|
||||||
code += assignRegisterTo(call.args.single(), reg)
|
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||||
return code
|
result += assignRegisterTo(call.args.single(), reg)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val code = IRCodeChunk(null, null)
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
val reg = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
|
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
|
||||||
code += assignRegisterTo(call.args.single(), reg)
|
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||||
return code
|
result += assignRegisterTo(call.args.single(), reg)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
val reg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
code += IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
|
result += IRCodeChunk(null, null).also {
|
||||||
return code
|
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val reg = codeGen.vmRegisters.nextFree()
|
val reg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||||
code += IRInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
|
result += IRCodeChunk(null, null).also {
|
||||||
return code
|
it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = reg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||||
val syscall =
|
val syscall =
|
||||||
when(array.dt) {
|
when(array.dt) {
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
|
||||||
@ -193,16 +212,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
|
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
|
||||||
else -> throw IllegalArgumentException("weird type to reverse")
|
else -> throw IllegalArgumentException("weird type to reverse")
|
||||||
}
|
}
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
result += IRCodeChunk(null, null).also {
|
||||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
return code
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val arrayName = call.args[0] as PtIdentifier
|
val arrayName = call.args[0] as PtIdentifier
|
||||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||||
val syscall =
|
val syscall =
|
||||||
when(array.dt) {
|
when(array.dt) {
|
||||||
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
|
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
|
||||||
@ -213,153 +234,171 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||||
else -> throw IllegalArgumentException("weird type to sort")
|
else -> throw IllegalArgumentException("weird type to sort")
|
||||||
}
|
}
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
||||||
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
result += IRCodeChunk(null, null).also {
|
||||||
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
||||||
return code
|
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val msbReg = codeGen.vmRegisters.nextFree()
|
val msbReg = codeGen.registers.nextFree()
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args[0], msbReg, -1)
|
result += exprGen.translateExpression(call.args[0], msbReg, -1)
|
||||||
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
result += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
||||||
code += IRInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
|
result += IRCodeChunk(null, null).also {
|
||||||
return code
|
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = resultRegister, reg2 = msbReg)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(codeGen.isZero(call.args[1])) {
|
if(codeGen.isZero(call.args[1])) {
|
||||||
if (call.args[0] is PtNumber) {
|
if (call.args[0] is PtNumber) {
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
code += IRInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, value = address)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg2 = addressReg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val valueReg = codeGen.vmRegisters.nextFree()
|
val valueReg = codeGen.registers.nextFree()
|
||||||
if (call.args[0] is PtNumber) {
|
if (call.args[0] is PtNumber) {
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = valueReg, value = address)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return code
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunk {
|
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(codeGen.isZero(call.args[1])) {
|
if(codeGen.isZero(call.args[1])) {
|
||||||
if (call.args[0] is PtNumber) {
|
if (call.args[0] is PtNumber) {
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
code += IRInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, value = address)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg2 = addressReg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val valueReg = codeGen.vmRegisters.nextFree()
|
val valueReg = codeGen.registers.nextFree()
|
||||||
if (call.args[0] is PtNumber) {
|
if (call.args[0] is PtNumber) {
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueReg, value = address)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||||
code += IRInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return code
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(call.args[0] is PtNumber) {
|
if(call.args[0] is PtNumber) {
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
code += IRInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, value = address)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||||
code += IRInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return code
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(call.args[0] is PtNumber) {
|
if(call.args[0] is PtNumber) {
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
code += IRInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, value = address)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.vmRegisters.nextFree()
|
val addressReg = codeGen.registers.nextFree()
|
||||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||||
code += IRInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return code
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcRnd(resultRegister: Int, position: Position): IRCodeChunk {
|
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(position)
|
|
||||||
code += IRInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcRndw(resultRegister: Int, position: Position): IRCodeChunk {
|
|
||||||
val code = IRCodeChunk(position)
|
|
||||||
code += IRInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
|
||||||
val name = (call.args[0] as PtString).value
|
val name = (call.args[0] as PtString).value
|
||||||
val code = IRCodeChunk(call.position)
|
val code = IRCodeChunk(null, null)
|
||||||
code += IRInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
|
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
|
||||||
return code
|
return listOf(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
return exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
|
||||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
result += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||||
code += IRInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultRegister, reg2 = resultRegister)
|
||||||
|
}
|
||||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||||
return code
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||||
val vmDt = codeGen.vmType(call.args[0].type)
|
val vmDt = codeGen.irType(call.args[0].type)
|
||||||
val code = IRCodeChunk(call.position)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||||
code += IRInstruction(opcode, vmDt, reg1=resultRegister)
|
result += IRCodeChunk(null, null).also {
|
||||||
code += assignRegisterTo(call.args[0], resultRegister)
|
it += IRInstruction(opcode, vmDt, reg1 = resultRegister)
|
||||||
return code
|
}
|
||||||
|
result += assignRegisterTo(call.args[0], resultRegister)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunk {
|
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||||
val code = IRCodeChunk(target.position)
|
|
||||||
val assignment = PtAssignment(target.position)
|
val assignment = PtAssignment(target.position)
|
||||||
val assignTarget = PtAssignTarget(target.position)
|
val assignTarget = PtAssignTarget(target.position)
|
||||||
assignTarget.children.add(target)
|
assignTarget.children.add(target)
|
||||||
assignment.children.add(assignTarget)
|
assignment.children.add(assignTarget)
|
||||||
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
||||||
code += codeGen.translateNode(assignment)
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
return code
|
result += codeGen.translateNode(assignment)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,26 +4,113 @@ import prog8.intermediate.*
|
|||||||
|
|
||||||
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||||
fun optimize() {
|
fun optimize() {
|
||||||
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
sub.chunks.forEach { chunk ->
|
removeEmptyChunks(sub)
|
||||||
|
joinChunks(sub)
|
||||||
|
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||||
// we don't optimize Inline Asm chunks here.
|
// we don't optimize Inline Asm chunks here.
|
||||||
if(chunk is IRCodeChunk) {
|
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
|
||||||
|
if(chunk1 is IRCodeChunk) {
|
||||||
do {
|
do {
|
||||||
val indexedInstructions = chunk.lines.withIndex()
|
val indexedInstructions = chunk1.instructions.withIndex()
|
||||||
.filter { it.value is IRInstruction }
|
.map { IndexedValue(it.index, it.value) }
|
||||||
.map { IndexedValue(it.index, it.value as IRInstruction) }
|
val changed = removeNops(chunk1, indexedInstructions)
|
||||||
val changed = removeNops(chunk, indexedInstructions)
|
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|
||||||
|| removeDoubleLoadsAndStores(chunk, indexedInstructions) // TODO not yet implemented
|
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
||||||
|| removeUselessArithmetic(chunk, indexedInstructions)
|
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|
||||||
|| removeWeirdBranches(chunk, indexedInstructions)
|
|| removeDoubleSecClc(chunk1, indexedInstructions)
|
||||||
|| removeDoubleSecClc(chunk, indexedInstructions)
|
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||||
|| cleanupPushPop(chunk, indexedInstructions)
|
|
||||||
// TODO other optimizations:
|
// TODO other optimizations:
|
||||||
// more complex optimizations such as unused registers
|
// more complex optimizations such as unused registers
|
||||||
} while (changed)
|
} while (changed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
removeEmptyChunks(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irprog.linkChunks() // re-link
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeEmptyChunks(sub: IRSubroutine) {
|
||||||
|
if(sub.chunks.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
/*
|
||||||
|
Empty Code chunk with label ->
|
||||||
|
If next chunk has no label -> move label to next chunk, remove original
|
||||||
|
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: consolidate labels into 1)
|
||||||
|
If is last chunk -> keep chunk in place because of the label.
|
||||||
|
Empty Code chunk without label ->
|
||||||
|
should not have been generated! ERROR.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
val relabelChunks = mutableListOf<Pair<Int, String>>()
|
||||||
|
val removeChunks = mutableListOf<Int>()
|
||||||
|
|
||||||
|
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||||
|
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
|
||||||
|
if(chunk.label==null) {
|
||||||
|
removeChunks += index
|
||||||
|
} else {
|
||||||
|
if (index < sub.chunks.size - 1) {
|
||||||
|
val nextchunk = sub.chunks[index + 1]
|
||||||
|
if (nextchunk.label == null) {
|
||||||
|
// can transplant label to next chunk and remove this empty one.
|
||||||
|
relabelChunks += Pair(index + 1, chunk.label!!)
|
||||||
|
removeChunks += index
|
||||||
|
} else {
|
||||||
|
if (chunk.label == nextchunk.label)
|
||||||
|
removeChunks += index
|
||||||
|
else {
|
||||||
|
// TODO: consolidate labels on same chunk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relabelChunks.forEach { (index, label) ->
|
||||||
|
val chunk = IRCodeChunk(label, null)
|
||||||
|
chunk.instructions += sub.chunks[index].instructions
|
||||||
|
sub.chunks[index] = chunk
|
||||||
|
}
|
||||||
|
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun joinChunks(sub: IRSubroutine) {
|
||||||
|
// Subroutine contains a list of chunks. Some can be joined into one.
|
||||||
|
|
||||||
|
if(sub.chunks.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
||||||
|
if(chunk.label!=null)
|
||||||
|
return false
|
||||||
|
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
||||||
|
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
|
||||||
|
val lastInstruction = previous.instructions.lastOrNull()
|
||||||
|
if(lastInstruction!=null)
|
||||||
|
return lastInstruction.opcode !in OpcodesThatJump
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val chunks = mutableListOf<IRCodeChunkBase>()
|
||||||
|
chunks += sub.chunks[0]
|
||||||
|
for(ix in 1 until sub.chunks.size) {
|
||||||
|
val lastChunk = chunks.last()
|
||||||
|
if(mayJoin(lastChunk, sub.chunks[ix])) {
|
||||||
|
lastChunk.instructions += sub.chunks[ix].instructions
|
||||||
|
lastChunk.next = sub.chunks[ix].next
|
||||||
|
}
|
||||||
|
else
|
||||||
|
chunks += sub.chunks[ix]
|
||||||
|
}
|
||||||
|
sub.chunks.clear()
|
||||||
|
sub.chunks += chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
@ -31,15 +118,15 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
var changed = false
|
var changed = false
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
if(ins.opcode== Opcode.PUSH) {
|
if(ins.opcode== Opcode.PUSH) {
|
||||||
if(idx < chunk.lines.size-1) {
|
if(idx < chunk.instructions.size-1) {
|
||||||
val insAfter = chunk.lines[idx+1] as? IRInstruction
|
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
||||||
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
|
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
|
||||||
if(ins.reg1==insAfter.reg1) {
|
if(ins.reg1==insAfter.reg1) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
} else {
|
} else {
|
||||||
chunk.lines[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
|
chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
|
||||||
chunk.lines.removeAt(idx+1)
|
chunk.instructions.removeAt(idx+1)
|
||||||
}
|
}
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
@ -55,18 +142,18 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
var changed = false
|
var changed = false
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
|
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
|
||||||
if(idx < chunk.lines.size-1) {
|
if(idx < chunk.instructions.size-1) {
|
||||||
val insAfter = chunk.lines[idx+1] as? IRInstruction
|
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
||||||
if(insAfter?.opcode == ins.opcode) {
|
if(insAfter?.opcode == ins.opcode) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
|
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
|
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,19 +162,24 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeWeirdBranches(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
private fun removeWeirdBranches(chunk: IRCodeChunk, nextChunk: IRCodeChunkBase?, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||||
// jump/branch to label immediately below
|
|
||||||
var changed = false
|
var changed = false
|
||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
val labelSymbol = ins.labelSymbol
|
val labelSymbol = ins.labelSymbol
|
||||||
|
|
||||||
|
// remove jump/branch to label immediately below (= next chunk if it has that label)
|
||||||
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
|
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
|
||||||
// if jumping to label immediately following this
|
if(idx==chunk.instructions.size-1 && ins.branchTarget===nextChunk) {
|
||||||
if(idx < chunk.lines.size-1) {
|
chunk.instructions.removeAt(idx)
|
||||||
val label = chunk.lines[idx+1] as? IRCodeLabel
|
changed = true
|
||||||
if(label?.name == labelSymbol) {
|
}
|
||||||
chunk.lines.removeAt(idx)
|
}
|
||||||
changed = true
|
// remove useless RETURN
|
||||||
}
|
if(ins.opcode == Opcode.RETURN && idx>0) {
|
||||||
|
val previous = chunk.instructions[idx-1] as? IRInstruction
|
||||||
|
if(previous?.opcode in OpcodesThatJump) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,47 +193,47 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
when (ins.opcode) {
|
when (ins.opcode) {
|
||||||
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
||||||
if (ins.value == 1) {
|
if (ins.value == 1) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.ADD, Opcode.SUB -> {
|
Opcode.ADD, Opcode.SUB -> {
|
||||||
if (ins.value == 1) {
|
if (ins.value == 1) {
|
||||||
chunk.lines[idx] = IRInstruction(
|
chunk.instructions[idx] = IRInstruction(
|
||||||
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
||||||
ins.type,
|
ins.type,
|
||||||
ins.reg1
|
ins.reg1
|
||||||
)
|
)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 0) {
|
} else if (ins.value == 0) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.AND -> {
|
Opcode.AND -> {
|
||||||
if (ins.value == 0) {
|
if (ins.value == 0) {
|
||||||
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 255 && ins.type == VmDataType.BYTE) {
|
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 65535 && ins.type == VmDataType.WORD) {
|
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.OR -> {
|
Opcode.OR -> {
|
||||||
if (ins.value == 0) {
|
if (ins.value == 0) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
} else if ((ins.value == 255 && ins.type == VmDataType.BYTE) || (ins.value == 65535 && ins.type == VmDataType.WORD)) {
|
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
|
||||||
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.XOR -> {
|
Opcode.XOR -> {
|
||||||
if (ins.value == 0) {
|
if (ins.value == 0) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +248,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
if (ins.opcode == Opcode.NOP) {
|
if (ins.opcode == Opcode.NOP) {
|
||||||
changed = true
|
changed = true
|
||||||
chunk.lines.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed
|
return changed
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.code.core.IErrorReporter
|
||||||
|
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||||
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
||||||
|
fun optimize(): Int {
|
||||||
|
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
|
||||||
|
|
||||||
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
|
sub.chunks.forEach { chunk ->
|
||||||
|
chunk.label?.let { allLabeledChunks[it] = chunk }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
|
||||||
|
|
||||||
|
// remove empty subs
|
||||||
|
irprog.blocks.forEach { block ->
|
||||||
|
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
|
||||||
|
if(sub.isEmpty()) {
|
||||||
|
if(!sub.position.file.startsWith(libraryFilePrefix))
|
||||||
|
errors.warn("unused subroutine ${sub.label}", sub.position)
|
||||||
|
block.children.remove(sub)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove empty blocks
|
||||||
|
irprog.blocks.reversed().forEach { block ->
|
||||||
|
if(block.isEmpty()) {
|
||||||
|
irprog.blocks.remove(block)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||||
|
val entrypointSub = irprog.blocks.single { it.name=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||||
|
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||||
|
|
||||||
|
fun grow() {
|
||||||
|
val new = mutableSetOf<IRCodeChunkBase>()
|
||||||
|
reachable.forEach {
|
||||||
|
it.next?.let { next -> new += next }
|
||||||
|
it.instructions.forEach { instr ->
|
||||||
|
if (instr.branchTarget == null)
|
||||||
|
instr.labelSymbol?.let { label -> allLabeledChunks[label]?.let { chunk -> new += chunk } }
|
||||||
|
else
|
||||||
|
new += instr.branchTarget!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reachable += new
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousCount = reachable.size
|
||||||
|
while(true) {
|
||||||
|
grow()
|
||||||
|
if(reachable.size<=previousCount)
|
||||||
|
break
|
||||||
|
previousCount = reachable.size
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeUnlinkedChunks(reachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
|
||||||
|
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
||||||
|
|
||||||
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
|
sub.chunks.forEach { chunk ->
|
||||||
|
chunk.next?.let { next -> linkedChunks += next }
|
||||||
|
chunk.instructions.forEach {
|
||||||
|
if(it.branchTarget==null) {
|
||||||
|
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
|
||||||
|
} else {
|
||||||
|
linkedChunks += it.branchTarget!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chunk.label == "main.start")
|
||||||
|
linkedChunks += chunk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeUnlinkedChunks(linkedChunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnlinkedChunks(
|
||||||
|
linkedChunks: MutableSet<IRCodeChunkBase>
|
||||||
|
): Int {
|
||||||
|
var numRemoved = 0
|
||||||
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
|
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||||
|
if (chunk !in linkedChunks) {
|
||||||
|
if (chunk === sub.chunks[0]) {
|
||||||
|
when(chunk) {
|
||||||
|
is IRCodeChunk -> {
|
||||||
|
if (chunk.isNotEmpty()) {
|
||||||
|
// don't remove the first chunk of the sub itself because it has to have the name of the sub as label
|
||||||
|
chunk.instructions.clear()
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IRInlineAsmChunk, is IRInlineBinaryChunk -> {
|
||||||
|
sub.chunks[index] = IRCodeChunk(chunk.label, chunk.next)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sub.chunks.removeAt(index)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
package prog8.codegen.intermediate
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
import prog8.code.core.AssemblyError
|
import prog8.code.core.AssemblyError
|
||||||
|
import prog8.intermediate.SyscallRegisterBase
|
||||||
|
|
||||||
internal class RegisterPool {
|
internal class RegisterPool {
|
||||||
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
|
// reserve 0,1,2 for return values of subroutine calls and syscalls
|
||||||
private var firstFreeFloat: Int=0
|
private var firstFree: Int=3
|
||||||
|
private var firstFreeFloat: Int=3
|
||||||
|
|
||||||
fun peekNext() = firstFree
|
fun peekNext() = firstFree
|
||||||
fun peekNextFloat() = firstFreeFloat
|
fun peekNextFloat() = firstFreeFloat
|
||||||
@ -12,7 +14,7 @@ internal class RegisterPool {
|
|||||||
fun nextFree(): Int {
|
fun nextFree(): Int {
|
||||||
val result = firstFree
|
val result = firstFree
|
||||||
firstFree++
|
firstFree++
|
||||||
if(firstFree>65535)
|
if(firstFree >= SyscallRegisterBase)
|
||||||
throw AssemblyError("out of virtual registers (int)")
|
throw AssemblyError("out of virtual registers (int)")
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -20,7 +22,7 @@ internal class RegisterPool {
|
|||||||
fun nextFreeFloat(): Int {
|
fun nextFreeFloat(): Int {
|
||||||
val result = firstFreeFloat
|
val result = firstFreeFloat
|
||||||
firstFreeFloat++
|
firstFreeFloat++
|
||||||
if(firstFreeFloat>65535)
|
if(firstFreeFloat >= SyscallRegisterBase)
|
||||||
throw AssemblyError("out of virtual registers (fp)")
|
throw AssemblyError("out of virtual registers (fp)")
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package prog8.vm.codegen
|
package prog8.codegen.vm
|
||||||
|
|
||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.ast.PtProgram
|
import prog8.code.ast.PtProgram
|
||||||
@ -7,10 +7,8 @@ import prog8.code.core.IAssemblyGenerator
|
|||||||
import prog8.code.core.IAssemblyProgram
|
import prog8.code.core.IAssemblyProgram
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.codegen.intermediate.IRCodeGen
|
import prog8.codegen.intermediate.IRCodeGen
|
||||||
import prog8.intermediate.IRFileReader
|
|
||||||
import prog8.intermediate.IRFileWriter
|
import prog8.intermediate.IRFileWriter
|
||||||
import prog8.intermediate.IRProgram
|
import prog8.intermediate.IRProgram
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
class VmCodeGen(private val program: PtProgram,
|
class VmCodeGen(private val program: PtProgram,
|
||||||
private val symbolTable: SymbolTable,
|
private val symbolTable: SymbolTable,
|
||||||
@ -22,16 +20,8 @@ class VmCodeGen(private val program: PtProgram,
|
|||||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||||
val irProgram = irCodeGen.generate()
|
val irProgram = irCodeGen.generate()
|
||||||
|
|
||||||
// no need to check options.keepIR, as the VM file format *is* the IR file.
|
|
||||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun compileIR(irFile: Path): IAssemblyProgram {
|
|
||||||
val irProgram = IRFileReader().read(irFile)
|
|
||||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6,13 +6,11 @@ import prog8.codegen.intermediate.IRPeepholeOptimizer
|
|||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
class TestIRPeepholeOpt: FunSpec({
|
class TestIRPeepholeOpt: FunSpec({
|
||||||
fun makeIRProgram(lines: List<IRCodeLine>): IRProgram {
|
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||||
|
require(chunks.first().label=="main.start")
|
||||||
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
||||||
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
||||||
val chunk = IRCodeChunk(Position.DUMMY)
|
chunks.forEach { sub += it }
|
||||||
for(line in lines)
|
|
||||||
chunk += line
|
|
||||||
sub += chunk
|
|
||||||
block += sub
|
block += sub
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val options = CompilationOptions(
|
val options = CompilationOptions(
|
||||||
@ -27,44 +25,60 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
)
|
)
|
||||||
val prog = IRProgram("test", IRSymbolTable(null), options, target)
|
val prog = IRProgram("test", IRSymbolTable(null), options, target)
|
||||||
prog.addBlock(block)
|
prog.addBlock(block)
|
||||||
|
prog.linkChunks()
|
||||||
|
prog.validate()
|
||||||
return prog
|
return prog
|
||||||
}
|
}
|
||||||
|
|
||||||
fun IRProgram.lines(): List<IRCodeLine> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.flatMap { it.lines }
|
fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
|
||||||
|
val chunk = IRCodeChunk("main.start", null)
|
||||||
|
instructions.forEach { chunk += it }
|
||||||
|
return makeIRProgram(listOf(chunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
|
||||||
|
|
||||||
test("remove nops") {
|
test("remove nops") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.JUMP, labelSymbol = "dummy"),
|
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
|
||||||
IRInstruction(Opcode.NOP),
|
IRInstruction(Opcode.NOP),
|
||||||
IRInstruction(Opcode.NOP)
|
IRInstruction(Opcode.NOP)
|
||||||
))
|
))
|
||||||
irProg.lines().size shouldBe 3
|
irProg.chunks().single().instructions.size shouldBe 3
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
irProg.lines().size shouldBe 1
|
irProg.chunks().single().instructions.size shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
test("remove jmp to label below") {
|
test("remove jmp to label below") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val c1 = IRCodeChunk("main.start", null)
|
||||||
IRInstruction(Opcode.JUMP, labelSymbol = "label"), // removed
|
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
|
||||||
IRCodeLabel("label"),
|
val c2 = IRCodeChunk("label", null)
|
||||||
IRInstruction(Opcode.JUMP, labelSymbol = "label2"), // removed
|
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
|
||||||
IRInstruction(Opcode.NOP), // removed
|
c2 += IRInstruction(Opcode.NOP) // removed
|
||||||
IRCodeLabel("label2"),
|
val c3 = IRCodeChunk("label2", null)
|
||||||
IRInstruction(Opcode.JUMP, labelSymbol = "label3"),
|
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
|
||||||
IRInstruction(Opcode.INC, VmDataType.BYTE, reg1=1),
|
c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
|
||||||
IRCodeLabel("label3")
|
val c4 = IRCodeChunk("label3", null)
|
||||||
))
|
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
|
||||||
irProg.lines().size shouldBe 8
|
|
||||||
|
irProg.chunks().size shouldBe 4
|
||||||
|
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
val lines = irProg.lines()
|
irProg.chunks().size shouldBe 4
|
||||||
lines.size shouldBe 5
|
irProg.chunks()[0].label shouldBe "main.start"
|
||||||
(lines[0] as IRCodeLabel).name shouldBe "label"
|
irProg.chunks()[1].label shouldBe "label"
|
||||||
(lines[1] as IRCodeLabel).name shouldBe "label2"
|
irProg.chunks()[2].label shouldBe "label2"
|
||||||
(lines[2] as IRInstruction).opcode shouldBe Opcode.JUMP
|
irProg.chunks()[3].label shouldBe "label3"
|
||||||
(lines[3] as IRInstruction).opcode shouldBe Opcode.INC
|
irProg.chunks()[0].isEmpty() shouldBe true
|
||||||
(lines[4] as IRCodeLabel).name shouldBe "label3"
|
irProg.chunks()[1].isEmpty() shouldBe true
|
||||||
|
irProg.chunks()[2].isEmpty() shouldBe false
|
||||||
|
irProg.chunks()[3].isEmpty() shouldBe true
|
||||||
|
val instr = irProg.chunks().flatMap { it.instructions }
|
||||||
|
instr.size shouldBe 2
|
||||||
|
instr[0].opcode shouldBe Opcode.JUMP
|
||||||
|
instr[1].opcode shouldBe Opcode.INC
|
||||||
}
|
}
|
||||||
|
|
||||||
test("remove double sec/clc") {
|
test("remove double sec/clc") {
|
||||||
@ -76,102 +90,100 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
IRInstruction(Opcode.CLC),
|
IRInstruction(Opcode.CLC),
|
||||||
IRInstruction(Opcode.CLC)
|
IRInstruction(Opcode.CLC)
|
||||||
))
|
))
|
||||||
irProg.lines().size shouldBe 6
|
irProg.chunks().single().instructions.size shouldBe 6
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
val lines = irProg.lines()
|
val instr = irProg.chunks().single().instructions
|
||||||
lines.size shouldBe 1
|
instr.size shouldBe 1
|
||||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.CLC
|
instr[0].opcode shouldBe Opcode.CLC
|
||||||
}
|
}
|
||||||
|
|
||||||
test("push followed by pop") {
|
test("push followed by pop") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
|
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42),
|
||||||
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
|
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42),
|
||||||
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
|
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
|
||||||
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
|
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
|
||||||
))
|
))
|
||||||
irProg.lines().size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
val lines = irProg.lines()
|
val instr = irProg.chunks().single().instructions
|
||||||
lines.size shouldBe 1
|
instr.size shouldBe 1
|
||||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOADR
|
instr[0].opcode shouldBe Opcode.LOADR
|
||||||
(lines[0] as IRInstruction).reg1 shouldBe 222
|
instr[0].reg1 shouldBe 222
|
||||||
(lines[0] as IRInstruction).reg2 shouldBe 99
|
instr[0].reg2 shouldBe 99
|
||||||
}
|
}
|
||||||
|
|
||||||
test("remove useless div/mul, add/sub") {
|
test("remove useless div/mul, add/sub") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
|
||||||
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
|
||||||
))
|
))
|
||||||
irProg.lines().size shouldBe 10
|
irProg.chunks().single().instructions.size shouldBe 10
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
val lines = irProg.lines()
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
lines.size shouldBe 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("replace add/sub 1 by inc/dec") {
|
test("replace add/sub 1 by inc/dec") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
|
||||||
))
|
))
|
||||||
irProg.lines().size shouldBe 2
|
irProg.chunks().single().instructions.size shouldBe 2
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
val lines = irProg.lines()
|
val instr = irProg.chunks().single().instructions
|
||||||
lines.size shouldBe 2
|
instr.size shouldBe 2
|
||||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.INC
|
instr[0].opcode shouldBe Opcode.INC
|
||||||
(lines[1] as IRInstruction).opcode shouldBe Opcode.DEC
|
instr[1].opcode shouldBe Opcode.DEC
|
||||||
}
|
}
|
||||||
|
|
||||||
test("remove useless and/or/xor") {
|
test("remove useless and/or/xor") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
|
||||||
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
|
||||||
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
|
||||||
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
|
||||||
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
|
||||||
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
|
||||||
))
|
))
|
||||||
irProg.lines().size shouldBe 8
|
irProg.chunks().single().instructions.size shouldBe 8
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
val lines = irProg.lines()
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
lines.size shouldBe 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("replace and/or/xor by constant number") {
|
test("replace and/or/xor by constant number") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 0),
|
||||||
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
|
||||||
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
|
||||||
IRInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
|
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
|
||||||
))
|
))
|
||||||
irProg.lines().size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize()
|
||||||
val lines = irProg.lines()
|
val instr = irProg.chunks().single().instructions
|
||||||
lines.size shouldBe 4
|
instr.size shouldBe 4
|
||||||
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOAD
|
instr[0].opcode shouldBe Opcode.LOAD
|
||||||
(lines[1] as IRInstruction).opcode shouldBe Opcode.LOAD
|
instr[1].opcode shouldBe Opcode.LOAD
|
||||||
(lines[2] as IRInstruction).opcode shouldBe Opcode.LOAD
|
instr[2].opcode shouldBe Opcode.LOAD
|
||||||
(lines[3] as IRInstruction).opcode shouldBe Opcode.LOAD
|
instr[3].opcode shouldBe Opcode.LOAD
|
||||||
(lines[0] as IRInstruction).value shouldBe 0
|
instr[0].value shouldBe 0
|
||||||
(lines[1] as IRInstruction).value shouldBe 0
|
instr[1].value shouldBe 0
|
||||||
(lines[2] as IRInstruction).value shouldBe 255
|
instr[2].value shouldBe 255
|
||||||
(lines[3] as IRInstruction).value shouldBe 65535
|
instr[3].value shouldBe 65535
|
||||||
}
|
}
|
||||||
})
|
})
|
8
codeGenVirtual/codeGenVirtual.iml
Normal file
8
codeGenVirtual/codeGenVirtual.iml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -20,7 +20,9 @@ import kotlin.math.pow
|
|||||||
|
|
||||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||||
|
|
||||||
class ExpressionSimplifier(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
class ExpressionSimplifier(private val program: Program,
|
||||||
|
private val errors: IErrorReporter,
|
||||||
|
private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||||
|
|
||||||
@ -191,11 +193,15 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
|||||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||||
}
|
}
|
||||||
|
|
||||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||||
// unsigned >= 0 --> true
|
// unsigned >= 0 --> true
|
||||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
|
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
|
||||||
}
|
}
|
||||||
|
else if(expr.operator == ">" && rightVal?.number == 0.0) {
|
||||||
|
// unsigned > 0 --> unsigned != 0
|
||||||
|
return listOf(IAstModification.SetExpression({expr.operator="!="}, expr, parent))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
|
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
|
||||||
@ -204,11 +210,15 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
|||||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||||
}
|
}
|
||||||
|
|
||||||
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
||||||
// unsigned < 0 --> false
|
// unsigned < 0 --> false
|
||||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
||||||
}
|
}
|
||||||
|
else if(expr.operator == "<=" && rightVal?.number == 0.0) {
|
||||||
|
// unsigned <= 0 --> unsigned==0
|
||||||
|
return listOf(IAstModification.SetExpression({expr.operator="=="}, expr, parent))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// boolvar & 1 --> boolvar
|
// boolvar & 1 --> boolvar
|
||||||
@ -221,6 +231,18 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(leftDt==DataType.BOOL) {
|
||||||
|
// optimize boolean constant comparisons
|
||||||
|
// if(expr.operator=="==" && rightVal?.number==0.0)
|
||||||
|
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||||
|
// if(expr.operator=="!=" && rightVal?.number==1.0)
|
||||||
|
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||||
|
if(expr.operator=="==" && rightVal?.number==1.0)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||||
|
if(expr.operator=="!=" && rightVal?.number==0.0)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||||
|
}
|
||||||
|
|
||||||
// simplify when a term is constant and directly determines the outcome
|
// simplify when a term is constant and directly determines the outcome
|
||||||
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
|
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
|
||||||
val newExpr: Expression? = when (expr.operator) {
|
val newExpr: Expression? = when (expr.operator) {
|
||||||
@ -586,11 +608,13 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
|||||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
if (amount >= 8) {
|
if (amount >= 8) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD -> {
|
||||||
if (amount >= 16) {
|
if (amount >= 16) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral(targetDt, 0.0, expr.position)
|
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
else if(amount==8) {
|
else if(amount==8) {
|
||||||
@ -605,6 +629,25 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
|||||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if (amount >= 16) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
|
return NumericLiteral(targetDt, 0.0, expr.position)
|
||||||
|
}
|
||||||
|
else if(amount==8) {
|
||||||
|
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
|
||||||
|
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
|
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(DataType.UBYTE, 0.0, expr.position)), expr.position)
|
||||||
|
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
|
||||||
|
}
|
||||||
|
else if (amount > 8) {
|
||||||
|
// same as above but with residual shifts.
|
||||||
|
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
|
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
|
||||||
|
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
|
||||||
|
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -625,6 +668,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
|||||||
when (idt.getOr(DataType.UNDEFINED)) {
|
when (idt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if (amount >= 8) {
|
if (amount >= 8) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral.optimalInteger(0, expr.position)
|
return NumericLiteral.optimalInteger(0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -636,6 +680,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
|
|||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if (amount >= 16) {
|
if (amount >= 16) {
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
return NumericLiteral.optimalInteger(0, expr.position)
|
return NumericLiteral.optimalInteger(0, expr.position)
|
||||||
}
|
}
|
||||||
else if(amount==8) {
|
else if(amount==8) {
|
||||||
|
@ -60,8 +60,8 @@ fun Program.inlineSubroutines(): Int {
|
|||||||
return inliner.applyModifications()
|
return inliner.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Program.simplifyExpressions(target: ICompilationTarget) : Int {
|
fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
|
||||||
val opti = ExpressionSimplifier(this, target)
|
val opti = ExpressionSimplifier(this, errors, target)
|
||||||
opti.visit(this)
|
opti.visit(this)
|
||||||
return opti.applyModifications()
|
return opti.applyModifications()
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,8 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
is Return -> {
|
is Return -> {
|
||||||
if(stmt.value is NumericLiteral)
|
if(stmt.value is NumericLiteral)
|
||||||
true
|
true
|
||||||
|
else if(stmt.value==null)
|
||||||
|
true
|
||||||
else if (stmt.value is IdentifierReference) {
|
else if (stmt.value is IdentifierReference) {
|
||||||
makeFullyScoped(stmt.value as IdentifierReference)
|
makeFullyScoped(stmt.value as IdentifierReference)
|
||||||
true
|
true
|
||||||
@ -172,7 +174,9 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
|
|
||||||
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
|
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
|
||||||
if(sub.inline && sub.parameters.isEmpty()) {
|
if(sub.inline && sub.parameters.isEmpty()) {
|
||||||
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1])))
|
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
|
||||||
|
"invalid inline sub at ${sub.position}"
|
||||||
|
}
|
||||||
return if(sub.isAsmSubroutine) {
|
return if(sub.isAsmSubroutine) {
|
||||||
// simply insert the asm for the argument-less routine
|
// simply insert the asm for the argument-less routine
|
||||||
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
|
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
|
||||||
@ -206,7 +210,9 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||||
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
|
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
|
||||||
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
|
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
|
||||||
require(sub.statements.size==1 || (sub.statements.size==2 && isEmptyReturn(sub.statements[1])))
|
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
|
||||||
|
"invalid inline sub at ${sub.position}"
|
||||||
|
}
|
||||||
return if(sub.isAsmSubroutine) {
|
return if(sub.isAsmSubroutine) {
|
||||||
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
|
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
|
||||||
noModifications
|
noModifications
|
||||||
|
@ -97,11 +97,13 @@ class StatementOptimizer(private val program: Program,
|
|||||||
if(constvalue!=null) {
|
if(constvalue!=null) {
|
||||||
return if(constvalue.asBooleanValue){
|
return if(constvalue.asBooleanValue){
|
||||||
// always true -> keep only if-part
|
// always true -> keep only if-part
|
||||||
errors.warn("condition is always true", ifElse.condition.position)
|
if(!ifElse.definingModule.isLibrary)
|
||||||
|
errors.warn("condition is always true", ifElse.condition.position)
|
||||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
|
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
|
||||||
} else {
|
} else {
|
||||||
// always false -> keep only else-part
|
// always false -> keep only else-part
|
||||||
errors.warn("condition is always false", ifElse.condition.position)
|
if(!ifElse.definingModule.isLibrary)
|
||||||
|
errors.warn("condition is always false", ifElse.condition.position)
|
||||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
|
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,9 +293,9 @@ class StatementOptimizer(private val program: Program,
|
|||||||
val bexpr=assignment.value as? BinaryExpression
|
val bexpr=assignment.value as? BinaryExpression
|
||||||
if(bexpr!=null) {
|
if(bexpr!=null) {
|
||||||
val rightCv = bexpr.right.constValue(program)?.number
|
val rightCv = bexpr.right.constValue(program)?.number
|
||||||
if(bexpr.operator=="-" && rightCv==null) {
|
if(bexpr.operator=="-" && rightCv==null && targetIDt.isInteger) {
|
||||||
if(bexpr.right isSameAs assignment.target) {
|
if(bexpr.right.isSimple && bexpr.right isSameAs assignment.target) {
|
||||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
|
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation, for integers)
|
||||||
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
||||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
|
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
@ -355,51 +357,17 @@ class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// word = msb(word) , word=lsb(word)
|
// word = lsb(word)
|
||||||
if(assignment.target.inferType(program).isWords) {
|
if(assignment.target.inferType(program).isWords) {
|
||||||
var fcall = assignment.value as? FunctionCallExpression
|
var fcall = assignment.value as? FunctionCallExpression
|
||||||
if (fcall == null)
|
if (fcall == null)
|
||||||
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
|
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
|
||||||
if (fcall != null && (fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))) {
|
if (fcall != null && (fcall.target.nameInSource == listOf("lsb"))) {
|
||||||
if (fcall.args.single() isSameAs assignment.target) {
|
if (fcall.args.single() isSameAs assignment.target) {
|
||||||
return if (fcall.target.nameInSource == listOf("lsb")) {
|
// optimize word=lsb(word) ==> word &= $00ff
|
||||||
// optimize word=lsb(word) ==> word &= $00ff
|
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
|
||||||
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
|
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
|
||||||
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
|
return listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
||||||
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
|
||||||
} else {
|
|
||||||
// optimize word=msb(word) ==> word >>= 8
|
|
||||||
val shift8 = BinaryExpression(fcall.args[0], ">>", NumericLiteral(DataType.UBYTE, 8.0, fcall.position), fcall.position)
|
|
||||||
val newAssign = Assignment(assignment.target, shift8, AssignmentOrigin.OPTIMIZER, fcall.position)
|
|
||||||
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
|
||||||
|
|
||||||
if(compTarget.name==VMTarget.NAME)
|
|
||||||
return noModifications
|
|
||||||
|
|
||||||
val returnvalue = returnStmt.value
|
|
||||||
if (returnvalue!=null) {
|
|
||||||
val dt = returnvalue.inferType(program).getOr(DataType.UNDEFINED)
|
|
||||||
if(dt!=DataType.UNDEFINED) {
|
|
||||||
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
|
|
||||||
// first assign to intermediary variable, then return that
|
|
||||||
val (returnVarName, _) = program.getTempVar(dt)
|
|
||||||
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
|
|
||||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
|
||||||
val assign = Assignment(tgt, returnvalue, AssignmentOrigin.OPTIMIZER, returnStmt.position)
|
|
||||||
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
|
||||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,12 +120,26 @@ class UnusedCodeRemover(private val program: Program,
|
|||||||
if(singleUse is AssignTarget) {
|
if(singleUse is AssignTarget) {
|
||||||
val assignment = singleUse.parent as Assignment
|
val assignment = singleUse.parent as Assignment
|
||||||
if(assignment.origin==AssignmentOrigin.VARINIT) {
|
if(assignment.origin==AssignmentOrigin.VARINIT) {
|
||||||
if(!decl.definingModule.isLibrary)
|
if(assignment.value.isSimple) {
|
||||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
// remove the vardecl
|
||||||
return listOf(
|
if(!decl.definingModule.isLibrary)
|
||||||
IAstModification.Remove(decl, parent as IStatementContainer),
|
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
return listOf(
|
||||||
)
|
IAstModification.Remove(decl, parent as IStatementContainer),
|
||||||
|
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||||
|
)
|
||||||
|
} else if(assignment.value is IFunctionCall) {
|
||||||
|
// replace the unused variable's initializer function call by a void
|
||||||
|
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||||
|
val fcall = assignment.value as IFunctionCall
|
||||||
|
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(decl, voidCall, parent),
|
||||||
|
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
errors.warn("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,14 +31,16 @@ dependencies {
|
|||||||
implementation project(':codeOptimizers')
|
implementation project(':codeOptimizers')
|
||||||
implementation project(':compilerAst')
|
implementation project(':compilerAst')
|
||||||
implementation project(':codeGenCpu6502')
|
implementation project(':codeGenCpu6502')
|
||||||
|
implementation project(':codeGenIntermediate')
|
||||||
implementation project(':codeGenExperimental')
|
implementation project(':codeGenExperimental')
|
||||||
implementation project(':virtualmachine')
|
implementation project(':virtualmachine')
|
||||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
implementation 'org.antlr:antlr4-runtime:4.11.1'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||||
|
|
||||||
|
testImplementation project(':intermediate')
|
||||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
<orderEntry type="module" module-name="codeOptimizers" />
|
<orderEntry type="module" module-name="codeOptimizers" />
|
||||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||||
|
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||||
<orderEntry type="module" module-name="virtualmachine" />
|
<orderEntry type="module" module-name="virtualmachine" />
|
||||||
|
<orderEntry type="module" module-name="intermediate" scope="TEST" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
@ -56,7 +56,8 @@ romsub $af4b = ROUND() clobbers(A,X,Y) ; round fac1
|
|||||||
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||||
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||||
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
|
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||||
|
romsub $af57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||||
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||||
romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||||
@ -151,6 +152,17 @@ asmsub FREADUY (ubyte value @Y) {
|
|||||||
|
|
||||||
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
|
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
|
||||||
|
|
||||||
|
sub rndf() -> float {
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #1
|
||||||
|
jsr RND_0
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
%asminclude "library:c128/floats.asm"
|
%asminclude "library:c128/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm"
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
|
@ -12,9 +12,34 @@ c64 {
|
|||||||
|
|
||||||
&ubyte COLOR = $00f1 ; cursor color
|
&ubyte COLOR = $00f1 ; cursor color
|
||||||
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
|
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
|
||||||
|
|
||||||
|
&uword IERROR = $0300
|
||||||
|
&uword IMAIN = $0302
|
||||||
|
&uword ICRNCH = $0304
|
||||||
|
&uword IQPLOP = $0306
|
||||||
|
&uword IGONE = $0308
|
||||||
|
&uword IEVAL = $030a
|
||||||
|
&uword ICRNCH2 = $030c
|
||||||
|
&uword IQPLOP2 = $030e
|
||||||
|
&uword IGONE2 = $0310
|
||||||
|
; $0312 and $0313 are unused.
|
||||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||||
|
&uword IOPEN = $031a
|
||||||
|
&uword ICLOSE = $031c
|
||||||
|
&uword ICHKIN = $031e
|
||||||
|
&uword ICKOUT = $0320
|
||||||
|
&uword ICLRCH = $0322
|
||||||
|
&uword IBASIN = $0324
|
||||||
|
&uword IBSOUT = $0326
|
||||||
|
&uword ISTOP = $0328
|
||||||
|
&uword IGETIN = $032a
|
||||||
|
&uword ICLALL = $032c
|
||||||
|
&uword IEXMON = $032e
|
||||||
|
&uword ILOAD = $0330
|
||||||
|
&uword ISAVE = $0332
|
||||||
|
|
||||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
&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
|
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||||
@ -536,15 +561,32 @@ sys {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub wait(uword jiffies) {
|
asmsub wait(uword jiffies @AY) {
|
||||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||||
repeat jiffies {
|
%asm {{
|
||||||
ubyte jiff = lsb(c64.RDTIM16())
|
stx P8ZP_SCRATCH_B1
|
||||||
while jiff==lsb(c64.RDTIM16()) {
|
sta P8ZP_SCRATCH_W1
|
||||||
; wait until 1 jiffy has passed
|
sty P8ZP_SCRATCH_W1+1
|
||||||
}
|
|
||||||
}
|
_loop lda P8ZP_SCRATCH_W1
|
||||||
|
ora P8ZP_SCRATCH_W1+1
|
||||||
|
bne +
|
||||||
|
ldx P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
|
||||||
|
+ lda c64.TIME_LO
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
- lda c64.TIME_LO
|
||||||
|
cmp P8ZP_SCRATCH_B1
|
||||||
|
beq -
|
||||||
|
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
dec P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec P8ZP_SCRATCH_W1
|
||||||
|
jmp _loop
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub waitvsync() clobbers(A) {
|
asmsub waitvsync() clobbers(A) {
|
||||||
|
@ -171,6 +171,18 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
|||||||
|
|
||||||
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
|
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
|
||||||
|
|
||||||
|
sub rndf() -> float {
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #1
|
||||||
|
jsr FREADSA
|
||||||
|
jsr RND ; rng into fac1
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
%asminclude "library:c64/floats.asm"
|
%asminclude "library:c64/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm"
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
|
@ -11,9 +11,36 @@ c64 {
|
|||||||
|
|
||||||
&ubyte COLOR = $0286 ; cursor color
|
&ubyte COLOR = $0286 ; cursor color
|
||||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||||
|
|
||||||
|
&uword IERROR = $0300
|
||||||
|
&uword IMAIN = $0302
|
||||||
|
&uword ICRNCH = $0304
|
||||||
|
&uword IQPLOP = $0306
|
||||||
|
&uword IGONE = $0308
|
||||||
|
&uword IEVAL = $030a
|
||||||
|
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||||
|
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||||
|
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||||
|
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||||
|
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||||
|
; $0313 is unused.
|
||||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||||
|
&uword IOPEN = $031a
|
||||||
|
&uword ICLOSE = $031c
|
||||||
|
&uword ICHKIN = $031e
|
||||||
|
&uword ICKOUT = $0320
|
||||||
|
&uword ICLRCH = $0322
|
||||||
|
&uword IBASIN = $0324
|
||||||
|
&uword IBSOUT = $0326
|
||||||
|
&uword ISTOP = $0328
|
||||||
|
&uword IGETIN = $032a
|
||||||
|
&uword ICLALL = $032c
|
||||||
|
&uword USERCMD = $032e
|
||||||
|
&uword ILOAD = $0330
|
||||||
|
&uword ISAVE = $0332
|
||||||
|
|
||||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
&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
|
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||||
@ -500,15 +527,32 @@ sys {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub wait(uword jiffies) {
|
asmsub wait(uword jiffies @AY) {
|
||||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||||
repeat jiffies {
|
%asm {{
|
||||||
ubyte jiff = lsb(c64.RDTIM16())
|
stx P8ZP_SCRATCH_B1
|
||||||
while jiff==lsb(c64.RDTIM16()) {
|
sta P8ZP_SCRATCH_W1
|
||||||
; wait until 1 jiffy has passed
|
sty P8ZP_SCRATCH_W1+1
|
||||||
}
|
|
||||||
}
|
_loop lda P8ZP_SCRATCH_W1
|
||||||
|
ora P8ZP_SCRATCH_W1+1
|
||||||
|
bne +
|
||||||
|
ldx P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
|
||||||
|
+ lda c64.TIME_LO
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
- lda c64.TIME_LO
|
||||||
|
cmp P8ZP_SCRATCH_B1
|
||||||
|
beq -
|
||||||
|
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
dec P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec P8ZP_SCRATCH_W1
|
||||||
|
jmp _loop
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub waitvsync() clobbers(A) {
|
asmsub waitvsync() clobbers(A) {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
; Cx16 specific disk drive I/O routines.
|
; Cx16 specific disk drive I/O routines.
|
||||||
|
|
||||||
%import diskio
|
%import diskio
|
||||||
|
%import string
|
||||||
|
|
||||||
cx16diskio {
|
cx16diskio {
|
||||||
|
|
||||||
|
; Same as diskio.load() but with additional bank parameter to select the Ram bank to load into.
|
||||||
; Use kernal LOAD routine to load the given program file in memory.
|
; Use kernal LOAD routine to load the given program file in memory.
|
||||||
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
||||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||||
@ -13,9 +15,10 @@ cx16diskio {
|
|||||||
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
||||||
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||||
cx16.rambank(bank)
|
cx16.rambank(bank)
|
||||||
return diskio.load(drivenumber, filenameptr, address_override)
|
return diskio.internal_load_routine(drivenumber, filenameptr, address_override, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; Same as diskio.load_raw() but with additional bank parameter to select the Ram bank to load into.
|
||||||
; Use kernal LOAD routine to load the given file in memory.
|
; Use kernal LOAD routine to load the given file in memory.
|
||||||
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
|
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
|
||||||
; The load address is mandatory. Returns the number of bytes loaded.
|
; The load address is mandatory. Returns the number of bytes loaded.
|
||||||
@ -23,9 +26,9 @@ cx16diskio {
|
|||||||
; or alternatively make sure to reset the correct ram bank yourself after the load!
|
; or alternatively make sure to reset the correct ram bank yourself after the load!
|
||||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||||
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
||||||
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
|
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||||
cx16.rambank(bank)
|
cx16.rambank(bank)
|
||||||
return diskio.load_headerless_cx16(drivenumber, filenameptr, address, true)
|
return diskio.internal_load_routine(drivenumber, filenameptr, address_override, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
|
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
|
||||||
@ -34,17 +37,22 @@ cx16diskio {
|
|||||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
asmsub vload(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||||
; -- like the basic command VLOAD "filename",device,bank,address
|
; -- like the basic command VLOAD "filename",drivenumber,bank,address
|
||||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||||
|
; the file has to have the usual 2 byte header (which will be skipped)
|
||||||
%asm {{
|
%asm {{
|
||||||
; -- load a file into video ram
|
clc
|
||||||
|
internal_vload:
|
||||||
phx
|
phx
|
||||||
pha
|
pha
|
||||||
tya
|
tya
|
||||||
tax
|
tax
|
||||||
lda #1
|
bcc +
|
||||||
ldy #0
|
ldy #%00000010 ; headerless load mode
|
||||||
|
bne ++
|
||||||
|
+ ldy #0 ; normal load mode
|
||||||
|
+ lda #1
|
||||||
jsr c64.SETLFS
|
jsr c64.SETLFS
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
ldy cx16.r0+1
|
ldy cx16.r0+1
|
||||||
@ -71,9 +79,19 @@ cx16diskio {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub vload_raw(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||||
|
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
|
||||||
|
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||||
|
; the file is read fully including the first two bytes.
|
||||||
|
%asm {{
|
||||||
|
sec
|
||||||
|
jmp vload.internal_vload
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
; replacement function that makes use of fast block read capability of the X16
|
; Replacement function that makes use of fast block read capability of the X16,
|
||||||
; use this in place of regular diskio.f_read()
|
; and can wrap over multiple ram banks while reading.
|
||||||
|
; Use this in place of regular diskio.f_read() on X16.
|
||||||
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||||
; -- read from the currently open file, up to the given number of bytes.
|
; -- read from the currently open file, up to the given number of bytes.
|
||||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||||
@ -81,15 +99,6 @@ cx16diskio {
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||||
if diskio.have_first_byte {
|
|
||||||
diskio.have_first_byte=false
|
|
||||||
@(bufferpointer) = diskio.first_byte
|
|
||||||
bufferpointer++
|
|
||||||
diskio.list_blocks++
|
|
||||||
num_bytes--
|
|
||||||
}
|
|
||||||
|
|
||||||
void c64.CHKIN(11) ; use #11 as input channel again
|
|
||||||
|
|
||||||
; commander X16 supports fast block-read via macptr() kernal call
|
; commander X16 supports fast block-read via macptr() kernal call
|
||||||
uword size
|
uword size
|
||||||
@ -97,7 +106,7 @@ cx16diskio {
|
|||||||
size = 255
|
size = 255
|
||||||
if num_bytes<size
|
if num_bytes<size
|
||||||
size = num_bytes
|
size = num_bytes
|
||||||
size = cx16.macptr(lsb(size), bufferpointer)
|
size = cx16.macptr(lsb(size), bufferpointer, false)
|
||||||
if_cs
|
if_cs
|
||||||
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
||||||
diskio.list_blocks += size
|
diskio.list_blocks += size
|
||||||
@ -119,54 +128,107 @@ byte_read_loop: ; fallback if macptr() isn't supported on the device
|
|||||||
lda bufferpointer+1
|
lda bufferpointer+1
|
||||||
sta m_in_buffer+2
|
sta m_in_buffer+2
|
||||||
}}
|
}}
|
||||||
repeat num_bytes {
|
while num_bytes {
|
||||||
|
if c64.READST() {
|
||||||
|
diskio.f_close()
|
||||||
|
if c64.READST() & $40 ; eof?
|
||||||
|
return diskio.list_blocks ; number of bytes read
|
||||||
|
return 0 ; error.
|
||||||
|
}
|
||||||
%asm {{
|
%asm {{
|
||||||
jsr c64.CHRIN
|
jsr c64.CHRIN
|
||||||
sta cx16.r5
|
|
||||||
m_in_buffer sta $ffff
|
m_in_buffer sta $ffff
|
||||||
inc m_in_buffer+1
|
inc m_in_buffer+1
|
||||||
bne +
|
bne +
|
||||||
inc m_in_buffer+2
|
inc m_in_buffer+2
|
||||||
+ inc diskio.list_blocks
|
|
||||||
bne +
|
|
||||||
inc diskio.list_blocks+1
|
|
||||||
+
|
+
|
||||||
}}
|
}}
|
||||||
|
diskio.list_blocks++
|
||||||
if cx16.r5==$0d { ; chance on I/o error status?
|
num_bytes--
|
||||||
diskio.first_byte = c64.READST()
|
|
||||||
if diskio.first_byte & $40 {
|
|
||||||
diskio.f_close() ; end of file, close it
|
|
||||||
diskio.list_blocks-- ; don't count that last CHRIN read
|
|
||||||
}
|
|
||||||
if diskio.first_byte
|
|
||||||
return diskio.list_blocks ; number of bytes read
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return diskio.list_blocks ; number of bytes read
|
return diskio.list_blocks ; number of bytes read
|
||||||
}
|
}
|
||||||
|
|
||||||
; replacement function that makes use of fast block read capability of the X16
|
; replacement function that makes use of fast block read capability of the X16
|
||||||
; use this in place of regular diskio.f_read_all()
|
; use this in place of regular diskio.f_read_all() on X16
|
||||||
sub f_read_all(uword bufferpointer) -> uword {
|
sub f_read_all(uword bufferpointer) -> uword {
|
||||||
; -- read the full contents of the file, returns number of bytes read.
|
; -- read the full contents of the file, returns number of bytes read.
|
||||||
if not diskio.iteration_in_progress
|
if not diskio.iteration_in_progress
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
uword total_read = 0
|
uword total_read = 0
|
||||||
if diskio.have_first_byte {
|
|
||||||
diskio.have_first_byte=false
|
|
||||||
@(bufferpointer) = diskio.first_byte
|
|
||||||
bufferpointer++
|
|
||||||
total_read = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
while not c64.READST() {
|
while not c64.READST() {
|
||||||
uword size = cx16diskio.f_read(bufferpointer, 256)
|
cx16.r0 = cx16diskio.f_read(bufferpointer, 256)
|
||||||
total_read += size
|
total_read += cx16.r0
|
||||||
bufferpointer += size
|
bufferpointer += cx16.r0
|
||||||
}
|
}
|
||||||
return total_read
|
return total_read
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub chdir(ubyte drivenumber, str path) {
|
||||||
|
; -- change current directory.
|
||||||
|
diskio.list_filename[0] = 'c'
|
||||||
|
diskio.list_filename[1] = 'd'
|
||||||
|
diskio.list_filename[2] = ':'
|
||||||
|
void string.copy(path, &diskio.list_filename+3)
|
||||||
|
diskio.send_command(drivenumber, diskio.list_filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mkdir(ubyte drivenumber, str name) {
|
||||||
|
; -- make a new subdirectory.
|
||||||
|
diskio.list_filename[0] = 'm'
|
||||||
|
diskio.list_filename[1] = 'd'
|
||||||
|
diskio.list_filename[2] = ':'
|
||||||
|
void string.copy(name, &diskio.list_filename+3)
|
||||||
|
diskio.send_command(drivenumber, diskio.list_filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rmdir(ubyte drivenumber, str name) {
|
||||||
|
; -- remove a subdirectory.
|
||||||
|
void string.find(name, '*')
|
||||||
|
if_cs
|
||||||
|
return ; refuse to act on a wildcard *
|
||||||
|
diskio.list_filename[0] = 'r'
|
||||||
|
diskio.list_filename[1] = 'd'
|
||||||
|
diskio.list_filename[2] = ':'
|
||||||
|
void string.copy(name, &diskio.list_filename+3)
|
||||||
|
diskio.send_command(drivenumber, diskio.list_filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub relabel(ubyte drivenumber, str name) {
|
||||||
|
; -- change the disk label.
|
||||||
|
diskio.list_filename[0] = 'r'
|
||||||
|
diskio.list_filename[1] = '-'
|
||||||
|
diskio.list_filename[2] = 'h'
|
||||||
|
diskio.list_filename[3] = ':'
|
||||||
|
void string.copy(name, &diskio.list_filename+4)
|
||||||
|
diskio.send_command(drivenumber, diskio.list_filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||||
|
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||||
|
ubyte[6] command = ['p',0,0,0,0,0]
|
||||||
|
command[1] = 12 ; f_open uses channel 12
|
||||||
|
command[2] = lsb(pos_loword)
|
||||||
|
command[3] = msb(pos_loword)
|
||||||
|
command[4] = lsb(pos_hiword)
|
||||||
|
command[5] = msb(pos_hiword)
|
||||||
|
send_command:
|
||||||
|
c64.SETNAM(sizeof(command), &command)
|
||||||
|
c64.SETLFS(15, diskio.last_drivenumber, 15)
|
||||||
|
void c64.OPEN()
|
||||||
|
c64.CLOSE(15)
|
||||||
|
}
|
||||||
|
|
||||||
|
; TODO see if we can get this to work as well:
|
||||||
|
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||||
|
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
|
||||||
|
; cx16diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
|
||||||
|
; cx16diskio.f_seek.command[2] = lsb(pos_loword)
|
||||||
|
; cx16diskio.f_seek.command[3] = msb(pos_loword)
|
||||||
|
; cx16diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||||
|
; cx16diskio.f_seek.command[5] = msb(pos_hiword)
|
||||||
|
; goto cx16diskio.f_seek.send_command
|
||||||
|
; }
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
|||||||
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||||
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!]
|
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!]
|
||||||
|
|
||||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
|
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
|
||||||
@ -57,7 +57,8 @@ romsub $fe4b = ROUND() clobbers(A,X,Y) ; round fac1
|
|||||||
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||||
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||||
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!!
|
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
|
||||||
|
romsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
|
||||||
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||||
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
|
||||||
@ -67,18 +68,19 @@ romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
|||||||
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||||
|
|
||||||
; X16 additions
|
; X16 additions
|
||||||
romsub $fe81 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||||
romsub $fe84 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||||
romsub $fe87 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||||
romsub $fe8a = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
||||||
romsub $fe8d = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||||
romsub $fe90 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||||
romsub $fe93 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||||
romsub $fe96 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||||
romsub $fe99 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
|
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
|
||||||
romsub $fe9c = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
||||||
romsub $fe9f = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
|
romsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
|
||||||
romsub $fea2 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
romsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||||
@ -147,19 +149,21 @@ asmsub FREADUY (ubyte value @Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub RND() clobbers(A,X,Y) {
|
|
||||||
%asm {{
|
|
||||||
lda #0
|
|
||||||
php
|
|
||||||
jsr cx16.entropy_get
|
|
||||||
plp
|
|
||||||
jmp RND_0
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
||||||
|
|
||||||
|
sub rndf() -> float {
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
lda #1
|
||||||
|
jsr RND_0
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
%asminclude "library:c64/floats.asm"
|
%asminclude "library:c64/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm"
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -82,9 +82,10 @@ gfx2 {
|
|||||||
bpp = 2
|
bpp = 2
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
; back to default text mode and colors
|
; back to default text mode
|
||||||
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
|
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||||
c64.CINT() ; back to text mode
|
c64.CINT()
|
||||||
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||||
width = 0
|
width = 0
|
||||||
height = 0
|
height = 0
|
||||||
bpp = 0
|
bpp = 0
|
||||||
@ -366,7 +367,7 @@ _done
|
|||||||
position2(x,y,true)
|
position2(x,y,true)
|
||||||
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||||
color &= 3
|
color &= 3
|
||||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
color <<= gfx2.plot.shift4c[lsb(x) & 3] ; TODO with lookup table
|
||||||
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
|
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||||
repeat lheight {
|
repeat lheight {
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -561,7 +562,11 @@ _done
|
|||||||
and #1
|
and #1
|
||||||
}}
|
}}
|
||||||
if_nz {
|
if_nz {
|
||||||
cx16.r0L = lsb(x) & 7 ; xbits
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
x /= 8
|
x /= 8
|
||||||
x += y*(320/8)
|
x += y*(320/8)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -571,7 +576,7 @@ _done
|
|||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
lda x
|
lda x
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
ldy cx16.r0L ; xbits
|
ply ; xbits
|
||||||
lda bits,y
|
lda bits,y
|
||||||
ldy color
|
ldy color
|
||||||
beq +
|
beq +
|
||||||
@ -608,7 +613,11 @@ _done
|
|||||||
and #1
|
and #1
|
||||||
}}
|
}}
|
||||||
if_nz {
|
if_nz {
|
||||||
cx16.r0L = lsb(x) & 7 ; xbits
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
x /= 8
|
x /= 8
|
||||||
x += y*(640/8)
|
x += y*(640/8)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -618,7 +627,7 @@ _done
|
|||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
lda x
|
lda x
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
ldy cx16.r0L ; xbits
|
ply ; xbits
|
||||||
lda bits,y
|
lda bits,y
|
||||||
ldy color
|
ldy color
|
||||||
beq +
|
beq +
|
||||||
@ -635,7 +644,7 @@ _done
|
|||||||
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
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
|
cx16.r2L = lsb(x) & 3 ; xbits
|
||||||
color &= 3
|
color &= 3
|
||||||
color <<= shift4c[cx16.r2L]
|
color <<= shift4c[cx16.r2L] ; TODO with lookup table
|
||||||
%asm {{
|
%asm {{
|
||||||
stz cx16.VERA_CTRL
|
stz cx16.VERA_CTRL
|
||||||
lda cx16.r1L
|
lda cx16.r1L
|
||||||
@ -654,6 +663,93 @@ _done
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub pget(uword @zp x, uword y) -> ubyte {
|
||||||
|
when active_mode {
|
||||||
|
1 -> {
|
||||||
|
; lores monochrome
|
||||||
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
|
x /= 8
|
||||||
|
x += y*(320/8)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
lda x+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda x
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
ply ; xbits
|
||||||
|
lda plot.bits,y
|
||||||
|
and cx16.VERA_DATA0
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
; TODO mode 2 and 3
|
||||||
|
4 -> {
|
||||||
|
; lores 256c
|
||||||
|
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda cx16.r1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
5 -> {
|
||||||
|
; hires monochrome
|
||||||
|
%asm {{
|
||||||
|
lda x
|
||||||
|
and #7
|
||||||
|
pha ; xbits
|
||||||
|
}}
|
||||||
|
x /= 8
|
||||||
|
x += y*(640/8)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
lda x+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda x
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
ply ; xbits
|
||||||
|
lda plot.bits,y
|
||||||
|
and cx16.VERA_DATA0
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
6 -> {
|
||||||
|
; hires 4c
|
||||||
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
%asm {{
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda cx16.r1L
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0H
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda cx16.r0L
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.r0L
|
||||||
|
}}
|
||||||
|
cx16.r1L = lsb(x) & 3
|
||||||
|
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L] ; TODO with lookup table
|
||||||
|
return cx16.r0L & 3
|
||||||
|
}
|
||||||
|
else -> return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub position(uword @zp x, uword y) {
|
sub position(uword @zp x, uword y) {
|
||||||
ubyte bank
|
ubyte bank
|
||||||
when active_mode {
|
when active_mode {
|
||||||
@ -823,7 +919,7 @@ _done
|
|||||||
y++
|
y++
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
phx
|
||||||
ldx #1
|
ldx color
|
||||||
lda cx16.VERA_DATA1
|
lda cx16.VERA_DATA1
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
ldy #8
|
ldy #8
|
||||||
|
@ -26,7 +26,7 @@ palette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub set_rgb(uword palette_words_ptr, uword num_colors) {
|
sub set_rgb(uword palette_words_ptr, uword num_colors) {
|
||||||
; 1 word per color entry (in little endian format as layed out in video memory, so $gb0r)
|
; 1 word per color entry (in little endian format as layed out in video memory, so $gb;$0r)
|
||||||
vera_palette_ptr = $fa00
|
vera_palette_ptr = $fa00
|
||||||
repeat num_colors*2 {
|
repeat num_colors*2 {
|
||||||
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
|
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
|
||||||
|
@ -85,6 +85,18 @@ asmsub RDTIM16() -> uword @AY {
|
|||||||
cx16 {
|
cx16 {
|
||||||
|
|
||||||
; irq, system and hardware vectors:
|
; irq, system and hardware vectors:
|
||||||
|
&uword IERROR = $0300
|
||||||
|
&uword IMAIN = $0302
|
||||||
|
&uword ICRNCH = $0304
|
||||||
|
&uword IQPLOP = $0306
|
||||||
|
&uword IGONE = $0308
|
||||||
|
&uword IEVAL = $030a
|
||||||
|
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||||
|
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||||
|
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||||
|
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||||
|
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||||
|
; $0313 is unused.
|
||||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||||
@ -290,8 +302,9 @@ cx16 {
|
|||||||
&ubyte d2ier = via2+14
|
&ubyte d2ier = via2+14
|
||||||
&ubyte d2ora = via2+15
|
&ubyte d2ora = via2+15
|
||||||
|
|
||||||
&ubyte ym2151adr = $9f40
|
; YM-2151 sound chip
|
||||||
&ubyte ym2151dat = $9f41
|
&ubyte YM_ADDRESS = $9f40
|
||||||
|
&ubyte YM_DATA = $9f41
|
||||||
|
|
||||||
const uword extdev = $9f60
|
const uword extdev = $9f60
|
||||||
|
|
||||||
@ -363,7 +376,7 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
|||||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||||
|
|
||||||
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY) clobbers(A) -> ubyte @Pc, uword @XY
|
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||||
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
||||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||||
@ -393,9 +406,10 @@ asmsub kbdbuf_clear() {
|
|||||||
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
||||||
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
|
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
|
||||||
%asm {{
|
%asm {{
|
||||||
|
pha ; save shape
|
||||||
sec
|
sec
|
||||||
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
|
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
|
||||||
lda #1
|
pla ; get shape back
|
||||||
jmp cx16.mouse_config
|
jmp cx16.mouse_config
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -446,13 +460,23 @@ inline asmsub getrambank() -> ubyte @A {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub numbanks() -> ubyte @A {
|
asmsub numbanks() -> uword @AY {
|
||||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
||||||
|
; Note that the number of such banks can be bigger than 255 so a word is returned, but mostly
|
||||||
|
; the A register could suffice as the lsb.
|
||||||
|
; The maximum number of banks is 256 = 2 Megabytes of banked Ram aka Hiram. (Y=1 and A=0 in this case).
|
||||||
|
; MEMTOP itself reports 0 in this case which we change into 256 for convenience.
|
||||||
|
; It reporting 0 doesn't mean 'zero banks', instead it means 256 banks (=2Mb banked RAM),
|
||||||
|
; as there is no X16 without at least 1 page of banked RAM.
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
phx
|
||||||
sec
|
sec
|
||||||
jsr c64.MEMTOP
|
jsr c64.MEMTOP
|
||||||
plx
|
ldy #0
|
||||||
|
cmp #0
|
||||||
|
bne +
|
||||||
|
iny
|
||||||
|
+ plx
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -611,12 +635,19 @@ asmsub init_system() {
|
|||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
cld
|
cld
|
||||||
|
lda VERA_DC_VIDEO
|
||||||
|
and #%00000111 ; retain chroma + output mode
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
lda #$80
|
lda #$80
|
||||||
sta VERA_CTRL
|
sta VERA_CTRL ; reset vera
|
||||||
stz $01 ; select rom bank 0 (enable kernal)
|
stz $01 ; select rom bank 0 (enable kernal)
|
||||||
jsr c64.IOINIT
|
jsr c64.IOINIT
|
||||||
jsr c64.RESTOR
|
jsr c64.RESTOR
|
||||||
jsr c64.CINT
|
jsr c64.CINT
|
||||||
|
lda VERA_DC_VIDEO
|
||||||
|
and #%11111000
|
||||||
|
ora P8ZP_SCRATCH_REG
|
||||||
|
sta VERA_DC_VIDEO ; keep old output mode
|
||||||
lda #$90 ; black
|
lda #$90 ; black
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
lda #1 ; swap fg/bg
|
lda #1 ; swap fg/bg
|
||||||
@ -887,17 +918,29 @@ sys {
|
|||||||
|
|
||||||
asmsub wait(uword jiffies @AY) {
|
asmsub wait(uword jiffies @AY) {
|
||||||
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||||
; note: regular system vsync irq handler must be running, and no nother irqs
|
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||||
%asm {{
|
%asm {{
|
||||||
- wai ; wait for irq (assume it was vsync)
|
phx
|
||||||
cmp #0
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
|
||||||
|
_loop lda P8ZP_SCRATCH_W1
|
||||||
|
ora P8ZP_SCRATCH_W1+1
|
||||||
bne +
|
bne +
|
||||||
dey
|
plx
|
||||||
+ dec a
|
|
||||||
bne -
|
|
||||||
cpy #0
|
|
||||||
bne -
|
|
||||||
rts
|
rts
|
||||||
|
|
||||||
|
+ jsr c64.RDTIM
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
- jsr c64.RDTIM
|
||||||
|
cmp P8ZP_SCRATCH_B1
|
||||||
|
beq -
|
||||||
|
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
dec P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec P8ZP_SCRATCH_W1
|
||||||
|
bra _loop
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,10 @@ cx16logo {
|
|||||||
|
|
||||||
sub logo() {
|
sub logo() {
|
||||||
uword strptr
|
uword strptr
|
||||||
for strptr in logo_lines
|
for strptr in logo_lines {
|
||||||
txt.print(strptr)
|
txt.print(strptr)
|
||||||
txt.nl()
|
txt.nl()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str[] logo_lines = [
|
str[] logo_lines = [
|
||||||
|
@ -10,12 +10,12 @@ diskio {
|
|||||||
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||||
|
|
||||||
c64.SETNAM(1, "$")
|
c64.SETNAM(1, "$")
|
||||||
c64.SETLFS(13, drivenumber, 0)
|
c64.SETLFS(12, drivenumber, 0)
|
||||||
ubyte status = 1
|
ubyte status = 1
|
||||||
void c64.OPEN() ; open 13,8,0,"$"
|
void c64.OPEN() ; open 12,8,0,"$"
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
void c64.CHKIN(13) ; use #13 as input channel
|
void c64.CHKIN(12) ; use #12 as input channel
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ diskio {
|
|||||||
void c64.CHRIN() ; skip the 4 prologue bytes
|
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
; while not key pressed / EOF encountered, read data.
|
; while not stop key pressed / EOF encountered, read data.
|
||||||
status = c64.READST()
|
status = c64.READST()
|
||||||
if status!=0 {
|
if status!=0 {
|
||||||
status = 1
|
status = 1
|
||||||
@ -53,7 +53,7 @@ diskio {
|
|||||||
|
|
||||||
io_error:
|
io_error:
|
||||||
c64.CLRCHN() ; restore default i/o devices
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
c64.CLOSE(13)
|
c64.CLOSE(12)
|
||||||
|
|
||||||
if status and status & $40 == 0 { ; bit 6=end of file
|
if status and status & $40 == 0 { ; bit 6=end of file
|
||||||
txt.print("\ni/o error, status: ")
|
txt.print("\ni/o error, status: ")
|
||||||
@ -69,12 +69,12 @@ io_error:
|
|||||||
; -- Returns pointer to disk name string or 0 if failure.
|
; -- Returns pointer to disk name string or 0 if failure.
|
||||||
|
|
||||||
c64.SETNAM(1, "$")
|
c64.SETNAM(1, "$")
|
||||||
c64.SETLFS(13, drivenumber, 0)
|
c64.SETLFS(12, drivenumber, 0)
|
||||||
ubyte okay = false
|
ubyte okay = false
|
||||||
void c64.OPEN() ; open 13,8,0,"$"
|
void c64.OPEN() ; open 12,8,0,"$"
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
void c64.CHKIN(13) ; use #13 as input channel
|
void c64.CHKIN(12) ; use #12 as input channel
|
||||||
if_cs
|
if_cs
|
||||||
goto io_error
|
goto io_error
|
||||||
|
|
||||||
@ -84,7 +84,6 @@ io_error:
|
|||||||
if c64.READST()!=0
|
if c64.READST()!=0
|
||||||
goto io_error
|
goto io_error
|
||||||
|
|
||||||
; while not key pressed / EOF encountered, read data.
|
|
||||||
cx16.r0 = &list_filename
|
cx16.r0 = &list_filename
|
||||||
repeat {
|
repeat {
|
||||||
@(cx16.r0) = c64.CHRIN()
|
@(cx16.r0) = c64.CHRIN()
|
||||||
@ -96,7 +95,7 @@ io_error:
|
|||||||
|
|
||||||
io_error:
|
io_error:
|
||||||
c64.CLRCHN() ; restore default i/o devices
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
c64.CLOSE(13)
|
c64.CLOSE(12)
|
||||||
if okay
|
if okay
|
||||||
return &list_filename
|
return &list_filename
|
||||||
return 0
|
return 0
|
||||||
@ -107,33 +106,37 @@ io_error:
|
|||||||
uword list_pattern
|
uword list_pattern
|
||||||
uword list_blocks
|
uword list_blocks
|
||||||
bool iteration_in_progress = false
|
bool iteration_in_progress = false
|
||||||
ubyte @zp first_byte
|
ubyte last_drivenumber = 8 ; which drive was last used for a f_open operation?
|
||||||
bool have_first_byte
|
str list_filetype = "???" ; prg, seq, dir
|
||||||
str list_filename = "?" * 50
|
str list_filename = "?" * 50
|
||||||
|
|
||||||
; ----- get a list of files (uses iteration functions internally) -----
|
; ----- get a list of files (uses iteration functions internally) -----
|
||||||
|
|
||||||
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
|
sub list_filenames(ubyte drivenumber, uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||||
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. Returns number of files.
|
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||||
const uword filenames_buf_size = 800
|
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
|
||||||
uword filenames_buffer = memory("filenames", filenames_buf_size, 0)
|
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||||
|
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||||
|
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||||
uword buffer_start = filenames_buffer
|
uword buffer_start = filenames_buffer
|
||||||
ubyte files_found = 0
|
ubyte files_found = 0
|
||||||
if lf_start_list(drivenumber, pattern_ptr) {
|
if lf_start_list(drivenumber, pattern_ptr) {
|
||||||
while lf_next_entry() {
|
while lf_next_entry() {
|
||||||
@(name_ptrs) = lsb(filenames_buffer)
|
if list_filetype!="dir" {
|
||||||
name_ptrs++
|
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
||||||
@(name_ptrs) = msb(filenames_buffer)
|
files_found++
|
||||||
name_ptrs++
|
if filenames_buffer - buffer_start > filenames_buf_size-20 {
|
||||||
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
|
@(filenames_buffer)=0
|
||||||
files_found++
|
lf_end_list()
|
||||||
if filenames_buffer - buffer_start > filenames_buf_size-18
|
sys.set_carry()
|
||||||
break
|
return files_found
|
||||||
if files_found == max_names
|
}
|
||||||
break
|
}
|
||||||
}
|
}
|
||||||
lf_end_list()
|
lf_end_list()
|
||||||
}
|
}
|
||||||
|
@(filenames_buffer)=0
|
||||||
|
sys.clear_carry()
|
||||||
return files_found
|
return files_found
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +173,7 @@ io_error:
|
|||||||
|
|
||||||
sub lf_next_entry() -> bool {
|
sub lf_next_entry() -> bool {
|
||||||
; -- retrieve the next entry from an iterative file listing session.
|
; -- retrieve the next entry from an iterative file listing session.
|
||||||
; results will be found in list_blocks and list_filename.
|
; results will be found in list_blocks, list_filename, and list_filetype.
|
||||||
; if it returns false though, there are no more entries (or an error occurred).
|
; if it returns false though, there are no more entries (or an error occurred).
|
||||||
|
|
||||||
if not iteration_in_progress
|
if not iteration_in_progress
|
||||||
@ -207,6 +210,12 @@ io_error:
|
|||||||
|
|
||||||
@(nameptr) = 0
|
@(nameptr) = 0
|
||||||
|
|
||||||
|
do {
|
||||||
|
cx16.r15L = c64.CHRIN()
|
||||||
|
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
|
||||||
|
list_filetype[0] = cx16.r15L
|
||||||
|
list_filetype[1] = c64.CHRIN()
|
||||||
|
list_filetype[2] = c64.CHRIN()
|
||||||
while c64.CHRIN() {
|
while c64.CHRIN() {
|
||||||
; read the rest of the entry until the end
|
; read the rest of the entry until the end
|
||||||
}
|
}
|
||||||
@ -238,7 +247,7 @@ close_end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
; ----- iterative file loader functions (uses io channel 11) -----
|
; ----- iterative file loader functions (uses io channel 12) -----
|
||||||
|
|
||||||
sub f_open(ubyte drivenumber, uword filenameptr) -> bool {
|
sub f_open(ubyte drivenumber, uword filenameptr) -> bool {
|
||||||
; -- open a file for iterative reading with f_read
|
; -- open a file for iterative reading with f_read
|
||||||
@ -246,17 +255,19 @@ close_end:
|
|||||||
f_close()
|
f_close()
|
||||||
|
|
||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
c64.SETLFS(11, drivenumber, 0)
|
c64.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
|
||||||
void c64.OPEN() ; open 11,8,0,"filename"
|
last_drivenumber = drivenumber
|
||||||
|
void c64.OPEN() ; open 12,8,12,"filename"
|
||||||
if_cc {
|
if_cc {
|
||||||
if c64.READST()==0 {
|
if c64.READST()==0 {
|
||||||
iteration_in_progress = true
|
iteration_in_progress = true
|
||||||
have_first_byte = false
|
void c64.CHKIN(12) ; use #12 as input channel
|
||||||
void c64.CHKIN(11) ; use #11 as input channel
|
|
||||||
if_cc {
|
if_cc {
|
||||||
first_byte = c64.CHRIN() ; read first byte to test for file not found
|
void c64.CHRIN() ; read first byte to test for file not found
|
||||||
if not c64.READST() {
|
if not c64.READST() {
|
||||||
have_first_byte = true
|
c64.CLOSE(12) ; close file because we already consumed first byte
|
||||||
|
void c64.OPEN() ; re-open the file
|
||||||
|
void c64.CHKIN(12)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -271,19 +282,11 @@ close_end:
|
|||||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||||
; NOTE: on systems with banked ram (such as Commander X16) this routine DOES NOT
|
; NOTE: on systems with banked ram (such as Commander X16) this routine DOES NOT
|
||||||
; automatically load into subsequent banks if it reaches a bank boundary!
|
; automatically load into subsequent banks if it reaches a bank boundary!
|
||||||
|
; Consider using cx16diskio.f_read() on X16.
|
||||||
if not iteration_in_progress or not num_bytes
|
if not iteration_in_progress or not num_bytes
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||||
if have_first_byte {
|
|
||||||
have_first_byte=false
|
|
||||||
@(bufferpointer) = first_byte
|
|
||||||
bufferpointer++
|
|
||||||
list_blocks++
|
|
||||||
num_bytes--
|
|
||||||
}
|
|
||||||
|
|
||||||
void c64.CHKIN(11) ; use #11 as input channel again
|
|
||||||
|
|
||||||
%asm {{
|
%asm {{
|
||||||
lda bufferpointer
|
lda bufferpointer
|
||||||
@ -291,50 +294,38 @@ close_end:
|
|||||||
lda bufferpointer+1
|
lda bufferpointer+1
|
||||||
sta m_in_buffer+2
|
sta m_in_buffer+2
|
||||||
}}
|
}}
|
||||||
repeat num_bytes {
|
while num_bytes {
|
||||||
|
if c64.READST() {
|
||||||
|
f_close()
|
||||||
|
if c64.READST() & $40 ; eof?
|
||||||
|
return list_blocks ; number of bytes read
|
||||||
|
return 0 ; error.
|
||||||
|
}
|
||||||
%asm {{
|
%asm {{
|
||||||
jsr c64.CHRIN
|
jsr c64.CHRIN
|
||||||
sta cx16.r5
|
|
||||||
m_in_buffer sta $ffff
|
m_in_buffer sta $ffff
|
||||||
inc m_in_buffer+1
|
inc m_in_buffer+1
|
||||||
bne +
|
bne +
|
||||||
inc m_in_buffer+2
|
inc m_in_buffer+2
|
||||||
+ inc list_blocks
|
|
||||||
bne +
|
|
||||||
inc list_blocks+1
|
|
||||||
+
|
+
|
||||||
}}
|
}}
|
||||||
|
list_blocks++
|
||||||
if cx16.r5==$0d { ; chance on I/o error status?
|
num_bytes--
|
||||||
first_byte = c64.READST()
|
|
||||||
if first_byte & $40 {
|
|
||||||
f_close() ; end of file, close it
|
|
||||||
list_blocks-- ; don't count that last CHRIN read
|
|
||||||
}
|
|
||||||
if first_byte
|
|
||||||
return list_blocks ; number of bytes read
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return list_blocks ; number of bytes read
|
return list_blocks ; number of bytes read
|
||||||
}
|
}
|
||||||
|
|
||||||
sub f_read_all(uword bufferpointer) -> uword {
|
sub f_read_all(uword bufferpointer) -> uword {
|
||||||
; -- read the full contents of the file, returns number of bytes read.
|
; -- read the full contents of the file, returns number of bytes read.
|
||||||
|
; Note: Consider using cx16diskio.f_read_all() on X16!
|
||||||
if not iteration_in_progress
|
if not iteration_in_progress
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
uword total_read = 0
|
uword total_read = 0
|
||||||
if have_first_byte {
|
|
||||||
have_first_byte=false
|
|
||||||
@(bufferpointer) = first_byte
|
|
||||||
bufferpointer++
|
|
||||||
total_read = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
while not c64.READST() {
|
while not c64.READST() {
|
||||||
uword size = f_read(bufferpointer, 256)
|
cx16.r0 = f_read(bufferpointer, 256)
|
||||||
total_read += size
|
total_read += cx16.r0
|
||||||
bufferpointer += size
|
bufferpointer += cx16.r0
|
||||||
}
|
}
|
||||||
return total_read
|
return total_read
|
||||||
}
|
}
|
||||||
@ -348,16 +339,9 @@ m_in_buffer sta $ffff
|
|||||||
%asm {{
|
%asm {{
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
sty P8ZP_SCRATCH_W1+1
|
sty P8ZP_SCRATCH_W1+1
|
||||||
ldx #11
|
ldx #12
|
||||||
jsr c64.CHKIN ; use channel 11 again for input
|
jsr c64.CHKIN ; use channel 12 again for input
|
||||||
ldy #0
|
ldy #0
|
||||||
lda have_first_byte
|
|
||||||
beq _loop
|
|
||||||
lda #0
|
|
||||||
sta have_first_byte
|
|
||||||
lda first_byte
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
iny
|
|
||||||
_loop jsr c64.CHRIN
|
_loop jsr c64.CHRIN
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
beq _end
|
beq _end
|
||||||
@ -378,23 +362,23 @@ _end rts
|
|||||||
; -- end an iterative file loading session (close channels).
|
; -- end an iterative file loading session (close channels).
|
||||||
if iteration_in_progress {
|
if iteration_in_progress {
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
c64.CLOSE(11)
|
c64.CLOSE(12)
|
||||||
iteration_in_progress = false
|
iteration_in_progress = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
; ----- iterative file saver functions (uses io channel 14) -----
|
; ----- iterative file writing functions (uses io channel 13) -----
|
||||||
|
|
||||||
sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool {
|
sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool {
|
||||||
; -- open a file for iterative writing with f_write
|
; -- open a file for iterative writing with f_write
|
||||||
f_close_w()
|
f_close_w()
|
||||||
|
|
||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
c64.SETLFS(14, drivenumber, 1)
|
c64.SETLFS(13, drivenumber, 1)
|
||||||
void c64.OPEN() ; open 14,8,1,"filename"
|
void c64.OPEN() ; open 13,8,1,"filename"
|
||||||
if_cc {
|
if_cc {
|
||||||
void c64.CHKOUT(14) ; use #14 as input channel
|
c64.CHKOUT(13) ; use #13 as output channel
|
||||||
return not c64.READST()
|
return not c64.READST()
|
||||||
}
|
}
|
||||||
f_close_w()
|
f_close_w()
|
||||||
@ -402,9 +386,9 @@ _end rts
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||||
; -- write the given umber of bytes to the currently open file
|
; -- write the given number of bytes to the currently open file
|
||||||
if num_bytes!=0 {
|
if num_bytes!=0 {
|
||||||
void c64.CHKOUT(14) ; use #14 as input channel again
|
c64.CHKOUT(13) ; use #13 as output channel again
|
||||||
repeat num_bytes {
|
repeat num_bytes {
|
||||||
c64.CHROUT(@(bufferpointer))
|
c64.CHROUT(@(bufferpointer))
|
||||||
bufferpointer++
|
bufferpointer++
|
||||||
@ -417,7 +401,7 @@ _end rts
|
|||||||
sub f_close_w() {
|
sub f_close_w() {
|
||||||
; -- end an iterative file writing session (close channels).
|
; -- end an iterative file writing session (close channels).
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
c64.CLOSE(14)
|
c64.CLOSE(13)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -436,10 +420,10 @@ _end rts
|
|||||||
goto io_error
|
goto io_error
|
||||||
|
|
||||||
while not c64.READST() {
|
while not c64.READST() {
|
||||||
first_byte = c64.CHRIN()
|
cx16.r5L = c64.CHRIN()
|
||||||
if first_byte=='\r' or first_byte=='\n'
|
if cx16.r5L=='\r' or cx16.r5L=='\n'
|
||||||
break
|
break
|
||||||
@(messageptr) = first_byte
|
@(messageptr) = cx16.r5L
|
||||||
messageptr++
|
messageptr++
|
||||||
}
|
}
|
||||||
@(messageptr) = 0
|
@(messageptr) = 0
|
||||||
@ -458,7 +442,7 @@ io_error:
|
|||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
c64.SETLFS(1, drivenumber, 0)
|
c64.SETLFS(1, drivenumber, 0)
|
||||||
uword @shared end_address = address + size
|
uword @shared end_address = address + size
|
||||||
first_byte = 0 ; result var reuse
|
cx16.r0L = 0
|
||||||
|
|
||||||
%asm {{
|
%asm {{
|
||||||
lda address
|
lda address
|
||||||
@ -476,12 +460,12 @@ io_error:
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
if_cc
|
if_cc
|
||||||
first_byte = c64.READST()==0
|
cx16.r0L = c64.READST()==0
|
||||||
|
|
||||||
c64.CLRCHN()
|
c64.CLRCHN()
|
||||||
c64.CLOSE(1)
|
c64.CLOSE(1)
|
||||||
|
|
||||||
return first_byte
|
return cx16.r0L
|
||||||
}
|
}
|
||||||
|
|
||||||
; Use kernal LOAD routine to load the given program file in memory.
|
; Use kernal LOAD routine to load the given program file in memory.
|
||||||
@ -493,9 +477,9 @@ io_error:
|
|||||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||||
; (which is possible on the Commander X16), the returned size is not correct,
|
; (which is possible on the Commander X16), the returned size is not correct,
|
||||||
; because it doesn't take the number of ram banks into account.
|
; because it doesn't take the number of ram banks into account.
|
||||||
; Consider using cx16diskio.load() instead.
|
; Also consider using cx16diskio.load() instead on the Commander X16.
|
||||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||||
return load_headerless_cx16(drivenumber, filenameptr, address_override, false)
|
return internal_load_routine(drivenumber, filenameptr, address_override, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
; Use kernal LOAD routine to load the given file in memory.
|
; Use kernal LOAD routine to load the given file in memory.
|
||||||
@ -506,10 +490,10 @@ io_error:
|
|||||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||||
; (which is possible on the Commander X16), the returned size is not correct,
|
; (which is possible on the Commander X16), the returned size is not correct,
|
||||||
; because it doesn't take the number of ram banks into account.
|
; because it doesn't take the number of ram banks into account.
|
||||||
; Consider using cx16diskio.load_raw() instead on the Commander X16.
|
; Also consider using cx16diskio.load_raw() instead on the Commander X16.
|
||||||
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
|
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
|
||||||
if sys.target==16 ; are we on commander X16?
|
if sys.target==16 ; are we on commander X16?
|
||||||
return load_headerless_cx16(drivenumber, filenameptr, address, true)
|
return internal_load_routine(drivenumber, filenameptr, address, true)
|
||||||
; fallback to reading the 2 header bytes separately
|
; fallback to reading the 2 header bytes separately
|
||||||
if not f_open(drivenumber, filenameptr)
|
if not f_open(drivenumber, filenameptr)
|
||||||
return 0
|
return 0
|
||||||
@ -524,7 +508,8 @@ io_error:
|
|||||||
|
|
||||||
; Internal routine, only to be used on Commander X16 platform if headerless=true,
|
; Internal routine, only to be used on Commander X16 platform if headerless=true,
|
||||||
; because this routine uses kernal support for that to load headerless files.
|
; because this routine uses kernal support for that to load headerless files.
|
||||||
sub load_headerless_cx16(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
|
; On C64 it will always be called with headerless=false.
|
||||||
|
sub internal_load_routine(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
|
||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
ubyte secondary = 1
|
ubyte secondary = 1
|
||||||
cx16.r1 = 0
|
cx16.r1 = 0
|
||||||
|
@ -221,13 +221,18 @@ sub ceil(float value) -> float {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub rndf() -> float {
|
sub rndseedf(float seed) {
|
||||||
|
if seed>0
|
||||||
|
seed = -seed ; make sure fp seed is always negative
|
||||||
|
|
||||||
%asm {{
|
%asm {{
|
||||||
stx P8ZP_SCRATCH_REG
|
stx floats_store_reg
|
||||||
lda #1
|
lda #<seed
|
||||||
jsr FREADSA
|
ldy #>seed
|
||||||
jsr RND ; rng into fac1
|
jsr MOVFM ; load float into fac1
|
||||||
ldx P8ZP_SCRATCH_REG
|
lda #-1
|
||||||
|
jsr floats.RND
|
||||||
|
ldx floats_store_reg
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -229,60 +229,32 @@ _divisor .word 0
|
|||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
randseed .proc
|
|
||||||
; -- reset the random seeds for the byte and word random generators
|
|
||||||
; arguments: uword seed in A/Y clobbers A
|
|
||||||
; (default starting values are: A=$2c Y=$9e)
|
|
||||||
sta randword._seed
|
|
||||||
sty randword._seed+1
|
|
||||||
clc
|
|
||||||
adc #14
|
|
||||||
sta randbyte._seed
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
randbyte .proc
|
|
||||||
; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
|
||||||
jmp randword
|
|
||||||
.pend
|
|
||||||
|
|
||||||
randword .proc
|
randword .proc
|
||||||
; -- 16 bit pseudo random number generator into AY
|
; -- 16 bit pseudo random number generator into AY
|
||||||
|
; default seed = $00c2 $1137
|
||||||
; rand64k ;Factors of 65535: 3 5 17 257
|
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
|
||||||
lda sr1+1
|
inc x1
|
||||||
asl a
|
clc
|
||||||
asl a
|
x1=*+1
|
||||||
eor sr1+1
|
lda #$00 ;x1
|
||||||
asl a
|
c1=*+1
|
||||||
eor sr1+1
|
eor #$c2 ;c1
|
||||||
asl a
|
a1=*+1
|
||||||
asl a
|
eor #$11 ;a1
|
||||||
eor sr1+1
|
sta a1
|
||||||
asl a
|
b1=*+1
|
||||||
rol sr1 ;shift this left, "random" bit comes from low
|
adc #$37 ;b1
|
||||||
rol sr1+1
|
sta b1
|
||||||
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
|
lsr a
|
||||||
lda sr2+1
|
eor a1
|
||||||
asl a
|
adc c1
|
||||||
eor sr2+1
|
sta c1
|
||||||
asl a
|
ldy b1
|
||||||
asl a
|
|
||||||
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
|
|
||||||
rol sr2+1
|
|
||||||
lda sr1+1 ;can be left out
|
|
||||||
eor sr2+1 ;if you dont use
|
|
||||||
tay ;y as suggested
|
|
||||||
lda sr1 ;mix up lowbytes of SR1
|
|
||||||
eor sr2 ;and SR2 to combine both
|
|
||||||
rts
|
rts
|
||||||
|
|
||||||
sr1 .word $a55a
|
|
||||||
sr2 .word $7653
|
|
||||||
|
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
randbyte = randword ; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
||||||
|
|
||||||
|
|
||||||
; ----------- optimized multiplications (stack) : ---------
|
; ----------- optimized multiplications (stack) : ---------
|
||||||
stack_mul_byte_3 .proc
|
stack_mul_byte_3 .proc
|
||||||
|
@ -71,4 +71,28 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub rnd() -> ubyte @A {
|
||||||
|
%asm {{
|
||||||
|
jmp math.randbyte
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub rndw() -> uword @AY {
|
||||||
|
%asm {{
|
||||||
|
jmp math.randword
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
|
||||||
|
; -- set new pseudo RNG's seed values. Defaults are: $00c2, $1137
|
||||||
|
%asm {{
|
||||||
|
sta math.randword.x1
|
||||||
|
sty math.randword.c1
|
||||||
|
lda cx16.r0L
|
||||||
|
sta math.randword.a1
|
||||||
|
lda cx16.r0H
|
||||||
|
sta math.randword.b1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,24 +244,6 @@ func_sqrt16_into_A .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
func_rnd_stack .proc
|
|
||||||
; -- put a random ubyte on the estack
|
|
||||||
jsr math.randbyte
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rndw_stack .proc
|
|
||||||
; -- put a random uword on the estack
|
|
||||||
jsr math.randword
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
tya
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
func_sort_ub .proc
|
func_sort_ub .proc
|
||||||
; 8bit unsigned sort
|
; 8bit unsigned sort
|
||||||
|
@ -1082,6 +1082,7 @@ containment_wordarray .proc
|
|||||||
iny
|
iny
|
||||||
cmp (P8ZP_SCRATCH_W2),y
|
cmp (P8ZP_SCRATCH_W2),y
|
||||||
beq _found
|
beq _found
|
||||||
|
dey
|
||||||
+ dey
|
+ dey
|
||||||
dey
|
dey
|
||||||
cpy #254
|
cpy #254
|
||||||
|
@ -195,8 +195,8 @@ sub str2uword(str string) -> uword {
|
|||||||
; -- returns the unsigned word value of the string number argument in AY
|
; -- returns the unsigned word value of the string number argument in AY
|
||||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||||
; (any non-digit character will terminate the number string that is parsed)
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,conv.str2uword.string
|
loadm.w r65500,conv.str2uword.string
|
||||||
syscall 11
|
syscall 11
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
@ -206,8 +206,8 @@ sub str2word(str string) -> word {
|
|||||||
; -- returns the signed word value of the string number argument in AY
|
; -- returns the signed word value of the string number argument in AY
|
||||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||||
; (any non-digit character will terminate the number string that is parsed)
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,conv.str2word.string
|
loadm.w r65500,conv.str2word.string
|
||||||
syscall 12
|
syscall 12
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
|
@ -9,15 +9,15 @@ floats {
|
|||||||
|
|
||||||
sub print_f(float value) {
|
sub print_f(float value) {
|
||||||
; ---- prints the floating point value (without a newline).
|
; ---- prints the floating point value (without a newline).
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.print_f.value
|
loadm.f fr65500,floats.print_f.value
|
||||||
syscall 25
|
syscall 25
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub pow(float value, float power) -> float {
|
sub pow(float value, float power) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.pow.value
|
loadm.f fr0,floats.pow.value
|
||||||
loadm.f fr1,floats.pow.power
|
loadm.f fr1,floats.pow.power
|
||||||
fpow.f fr0,fr1
|
fpow.f fr0,fr1
|
||||||
@ -26,7 +26,7 @@ sub pow(float value, float power) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub fabs(float value) -> float {
|
sub fabs(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.fabs.value
|
loadm.f fr0,floats.fabs.value
|
||||||
fabs.f fr0,fr0
|
fabs.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -34,7 +34,7 @@ sub fabs(float value) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub sin(float angle) -> float {
|
sub sin(float angle) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.sin.angle
|
loadm.f fr0,floats.sin.angle
|
||||||
fsin.f fr0,fr0
|
fsin.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -42,7 +42,7 @@ sub sin(float angle) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub cos(float angle) -> float {
|
sub cos(float angle) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.cos.angle
|
loadm.f fr0,floats.cos.angle
|
||||||
fcos.f fr0,fr0
|
fcos.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -50,7 +50,7 @@ sub cos(float angle) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub tan(float value) -> float {
|
sub tan(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.tan.value
|
loadm.f fr0,floats.tan.value
|
||||||
ftan.f fr0,fr0
|
ftan.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -58,7 +58,7 @@ sub tan(float value) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub atan(float value) -> float {
|
sub atan(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.atan.value
|
loadm.f fr0,floats.atan.value
|
||||||
fatan.f fr0,fr0
|
fatan.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -66,7 +66,7 @@ sub atan(float value) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub ln(float value) -> float {
|
sub ln(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.ln.value
|
loadm.f fr0,floats.ln.value
|
||||||
fln.f fr0,fr0
|
fln.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -74,7 +74,7 @@ sub ln(float value) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub log2(float value) -> float {
|
sub log2(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.log2.value
|
loadm.f fr0,floats.log2.value
|
||||||
flog.f fr0,fr0
|
flog.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -82,7 +82,7 @@ sub log2(float value) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub sqrt(float value) -> float {
|
sub sqrt(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.sqrt.value
|
loadm.f fr0,floats.sqrt.value
|
||||||
sqrt.f fr0,fr0
|
sqrt.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -100,7 +100,7 @@ sub deg(float angle) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub round(float value) -> float {
|
sub round(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.round.value
|
loadm.f fr0,floats.round.value
|
||||||
fround.f fr0,fr0
|
fround.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -108,7 +108,7 @@ sub round(float value) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub floor(float value) -> float {
|
sub floor(float value) -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.floor.value
|
loadm.f fr0,floats.floor.value
|
||||||
ffloor.f fr0,fr0
|
ffloor.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -117,7 +117,7 @@ sub floor(float value) -> float {
|
|||||||
|
|
||||||
sub ceil(float value) -> float {
|
sub ceil(float value) -> float {
|
||||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.f fr0,floats.ceil.value
|
loadm.f fr0,floats.ceil.value
|
||||||
fceil.f fr0,fr0
|
fceil.f fr0,fr0
|
||||||
return
|
return
|
||||||
@ -125,9 +125,17 @@ sub ceil(float value) -> float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub rndf() -> float {
|
sub rndf() -> float {
|
||||||
%asm {{
|
%ir {{
|
||||||
rnd.f fr0
|
syscall 35
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub rndseedf(float seed) {
|
||||||
|
%ir {{
|
||||||
|
loadm.f fr65500,floats.rndseedf.seed
|
||||||
|
syscall 32
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -159,4 +159,27 @@ math {
|
|||||||
return costab[radians] as byte
|
return costab[radians] as byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub rnd() -> ubyte {
|
||||||
|
%ir {{
|
||||||
|
syscall 33
|
||||||
|
return
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rndw() -> uword {
|
||||||
|
%ir {{
|
||||||
|
syscall 34
|
||||||
|
return
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rndseed(uword seed1, uword seed2) {
|
||||||
|
; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653.
|
||||||
|
%ir {{
|
||||||
|
loadm.w r65500,math.rndseed.seed1
|
||||||
|
loadm.w r65501,math.rndseed.seed2
|
||||||
|
syscall 31
|
||||||
|
return
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,6 @@
|
|||||||
; Internal library routines - always included by the compiler
|
; Internal library routines - always included by the compiler
|
||||||
|
|
||||||
%import textio
|
|
||||||
|
|
||||||
prog8_lib {
|
prog8_lib {
|
||||||
%option force_output
|
%option force_output
|
||||||
|
|
||||||
sub string_contains(ubyte needle, str haystack) -> ubyte {
|
|
||||||
repeat {
|
|
||||||
if @(haystack)==0
|
|
||||||
return false
|
|
||||||
if @(haystack)==needle
|
|
||||||
return true
|
|
||||||
haystack++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub bytearray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
|
|
||||||
haystack_ptr--
|
|
||||||
while num_elements {
|
|
||||||
if haystack_ptr[num_elements]==needle
|
|
||||||
return true
|
|
||||||
num_elements--
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
sub wordarray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
|
|
||||||
haystack_ptr += (num_elements-1) * 2
|
|
||||||
while num_elements {
|
|
||||||
if peekw(haystack_ptr)==needle
|
|
||||||
return true
|
|
||||||
haystack_ptr -= 2
|
|
||||||
num_elements--
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
sub string_compare(str st1, str st2) -> byte {
|
|
||||||
; Compares two strings for sorting.
|
|
||||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
|
||||||
; Note that you can also directly compare strings and string values with eachother using
|
|
||||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
|
||||||
%asm {{
|
|
||||||
loadm.w r0,prog8_lib.string_compare.st1
|
|
||||||
loadm.w r1,prog8_lib.string_compare.st2
|
|
||||||
syscall 29
|
|
||||||
return
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,12 @@ string {
|
|||||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||||
; Note that you can also directly compare strings and string values with eachother using
|
; Note that you can also directly compare strings and string values with eachother using
|
||||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||||
return prog8_lib.string_compare(st1, st2)
|
%ir {{
|
||||||
|
loadm.w r65500,string.compare.st1
|
||||||
|
loadm.w r65501,string.compare.st2
|
||||||
|
syscall 29
|
||||||
|
return
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub lower(str st) -> ubyte {
|
sub lower(str st) -> ubyte {
|
||||||
|
@ -7,22 +7,22 @@ sys {
|
|||||||
|
|
||||||
sub reset_system() {
|
sub reset_system() {
|
||||||
; Soft-reset the system back to initial power-on Basic prompt.
|
; Soft-reset the system back to initial power-on Basic prompt.
|
||||||
%asm {{
|
%ir {{
|
||||||
syscall 0
|
syscall 0
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub wait(uword jiffies) {
|
sub wait(uword jiffies) {
|
||||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,sys.wait.jiffies
|
loadm.w r65500,sys.wait.jiffies
|
||||||
syscall 13
|
syscall 13
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub waitvsync() {
|
sub waitvsync() {
|
||||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||||
%asm {{
|
%ir {{
|
||||||
syscall 14
|
syscall 14
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -61,52 +61,52 @@ sys {
|
|||||||
|
|
||||||
sub exit(ubyte returnvalue) {
|
sub exit(ubyte returnvalue) {
|
||||||
; -- immediately exit the program with a return code in the A register
|
; -- immediately exit the program with a return code in the A register
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.b r0,sys.exit.returnvalue
|
loadm.b r65500,sys.exit.returnvalue
|
||||||
syscall 1
|
syscall 1
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub set_carry() {
|
sub set_carry() {
|
||||||
%asm {{
|
%ir {{
|
||||||
sec
|
sec
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub clear_carry() {
|
sub clear_carry() {
|
||||||
%asm {{
|
%ir {{
|
||||||
clc
|
clc
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub gfx_enable(ubyte mode) {
|
sub gfx_enable(ubyte mode) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.b r0,sys.gfx_enable.mode
|
loadm.b r65500,sys.gfx_enable.mode
|
||||||
syscall 8
|
syscall 8
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub gfx_clear(ubyte color) {
|
sub gfx_clear(ubyte color) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.b r0,sys.gfx_clear.color
|
loadm.b r65500,sys.gfx_clear.color
|
||||||
syscall 9
|
syscall 9
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub gfx_plot(uword xx, uword yy, ubyte color) {
|
sub gfx_plot(uword xx, uword yy, ubyte color) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,sys.gfx_plot.xx
|
loadm.w r65500,sys.gfx_plot.xx
|
||||||
loadm.w r1,sys.gfx_plot.yy
|
loadm.w r65501,sys.gfx_plot.yy
|
||||||
loadm.b r2,sys.gfx_plot.color
|
loadm.b r65502,sys.gfx_plot.color
|
||||||
syscall 10
|
syscall 10
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
|
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,sys.gfx_getpixel.xx
|
loadm.w r65500,sys.gfx_getpixel.xx
|
||||||
loadm.w r1,sys.gfx_getpixel.yy
|
loadm.w r65501,sys.gfx_getpixel.yy
|
||||||
syscall 30
|
syscall 30
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
|
@ -6,8 +6,8 @@ txt {
|
|||||||
|
|
||||||
sub clear_screen() {
|
sub clear_screen() {
|
||||||
str @shared sequence = "\x1b[2J\x1B[H"
|
str @shared sequence = "\x1b[2J\x1B[H"
|
||||||
%asm {{
|
%ir {{
|
||||||
load.w r0,txt.clear_screen.sequence
|
load.w r65500,txt.clear_screen.sequence
|
||||||
syscall 3
|
syscall 3
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -29,15 +29,15 @@ sub uppercase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub chrout(ubyte char) {
|
sub chrout(ubyte char) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.b r0,txt.chrout.char
|
loadm.b r65500,txt.chrout.char
|
||||||
syscall 2
|
syscall 2
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print (str text) {
|
sub print (str text) {
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,txt.print.text
|
loadm.w r65500,txt.print.text
|
||||||
syscall 3
|
syscall 3
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -113,8 +113,8 @@ sub print_w (word value) {
|
|||||||
sub input_chars (uword buffer) -> ubyte {
|
sub input_chars (uword buffer) -> ubyte {
|
||||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
|
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
|
||||||
; It assumes the keyboard is selected as I/O channel!
|
; It assumes the keyboard is selected as I/O channel!
|
||||||
%asm {{
|
%ir {{
|
||||||
loadm.w r0,txt.input_chars.buffer
|
loadm.w r65500,txt.input_chars.buffer
|
||||||
syscall 6
|
syscall 6
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
|
@ -1 +1 @@
|
|||||||
8.6.2
|
8.9
|
||||||
|
@ -41,10 +41,9 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||||
val keepIR by cli.option(ArgType.Boolean, fullName = "keepIR", description = "keep the IR code file (for targets that use it)")
|
|
||||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||||
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)")
|
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental)")
|
||||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||||
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
||||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||||
@ -127,7 +126,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
quietAssembler == true,
|
quietAssembler == true,
|
||||||
asmListfile == true,
|
asmListfile == true,
|
||||||
experimentalCodegen == true,
|
experimentalCodegen == true,
|
||||||
keepIR == true,
|
|
||||||
compilationTarget,
|
compilationTarget,
|
||||||
evalStackAddr,
|
evalStackAddr,
|
||||||
processedSymbols,
|
processedSymbols,
|
||||||
@ -192,7 +190,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
quietAssembler == true,
|
quietAssembler == true,
|
||||||
asmListfile == true,
|
asmListfile == true,
|
||||||
experimentalCodegen == true,
|
experimentalCodegen == true,
|
||||||
keepIR == true,
|
|
||||||
compilationTarget,
|
compilationTarget,
|
||||||
evalStackAddr,
|
evalStackAddr,
|
||||||
processedSymbols,
|
processedSymbols,
|
||||||
|
@ -14,10 +14,10 @@ import prog8.ast.walk.IAstVisitor
|
|||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.*
|
import prog8.code.target.*
|
||||||
|
import prog8.codegen.vm.VmCodeGen
|
||||||
import prog8.compiler.astprocessing.*
|
import prog8.compiler.astprocessing.*
|
||||||
import prog8.optimizer.*
|
import prog8.optimizer.*
|
||||||
import prog8.parser.ParseError
|
import prog8.parser.ParseError
|
||||||
import prog8.vm.codegen.VmCodeGen
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.nameWithoutExtension
|
import kotlin.io.path.nameWithoutExtension
|
||||||
@ -38,7 +38,6 @@ class CompilerArguments(val filepath: Path,
|
|||||||
val quietAssembler: Boolean,
|
val quietAssembler: Boolean,
|
||||||
val asmListfile: Boolean,
|
val asmListfile: Boolean,
|
||||||
val experimentalCodegen: Boolean,
|
val experimentalCodegen: Boolean,
|
||||||
val keepIR: Boolean,
|
|
||||||
val compilationTarget: String,
|
val compilationTarget: String,
|
||||||
val evalStackBaseAddress: UInt?,
|
val evalStackBaseAddress: UInt?,
|
||||||
val symbolDefs: Map<String, String>,
|
val symbolDefs: Map<String, String>,
|
||||||
@ -81,7 +80,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
asmQuiet = args.quietAssembler
|
asmQuiet = args.quietAssembler
|
||||||
asmListfile = args.asmListfile
|
asmListfile = args.asmListfile
|
||||||
experimentalCodegen = args.experimentalCodegen
|
experimentalCodegen = args.experimentalCodegen
|
||||||
keepIR = args.keepIR
|
|
||||||
evalStackBaseAddress = args.evalStackBaseAddress
|
evalStackBaseAddress = args.evalStackBaseAddress
|
||||||
outputDir = args.outputDir.normalize()
|
outputDir = args.outputDir.normalize()
|
||||||
symbolDefs = args.symbolDefs
|
symbolDefs = args.symbolDefs
|
||||||
@ -95,7 +93,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
|
|
||||||
processAst(program, args.errors, compilationOptions)
|
processAst(program, args.errors, compilationOptions)
|
||||||
if (compilationOptions.optimize) {
|
if (compilationOptions.optimize) {
|
||||||
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
|
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
|
||||||
// printProgram(program)
|
// printProgram(program)
|
||||||
|
|
||||||
optimizeAst(
|
optimizeAst(
|
||||||
@ -108,7 +106,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
}
|
}
|
||||||
postprocessAst(program, args.errors, compilationOptions)
|
postprocessAst(program, args.errors, compilationOptions)
|
||||||
|
|
||||||
// println("*********** AST BEFORE ASSEMBLYGEN *************")
|
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
|
||||||
// printProgram(program)
|
// printProgram(program)
|
||||||
|
|
||||||
determineProgramLoadAddress(program, compilationOptions, args.errors)
|
determineProgramLoadAddress(program, compilationOptions, args.errors)
|
||||||
@ -361,7 +359,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
|||||||
remover.applyModifications()
|
remover.applyModifications()
|
||||||
while (true) {
|
while (true) {
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
val optsDone1 = program.simplifyExpressions(compTarget)
|
val optsDone1 = program.simplifyExpressions(errors, compTarget)
|
||||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
val optsDone2 = program.splitBinaryExpressions(compilerOptions)
|
||||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||||
val optsDone4 = program.inlineSubroutines()
|
val optsDone4 = program.inlineSubroutines()
|
||||||
@ -382,7 +380,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
|||||||
callGraph.checkRecursiveCalls(errors)
|
callGraph.checkRecursiveCalls(errors)
|
||||||
program.verifyFunctionArgTypes(errors)
|
program.verifyFunctionArgTypes(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
program.moveMainAndStartToFirst()
|
program.moveMainBlockAsFirst()
|
||||||
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||||
errors.report()
|
errors.report()
|
||||||
}
|
}
|
||||||
@ -391,10 +389,10 @@ private fun createAssemblyAndAssemble(program: Program,
|
|||||||
errors: IErrorReporter,
|
errors: IErrorReporter,
|
||||||
compilerOptions: CompilationOptions
|
compilerOptions: CompilationOptions
|
||||||
): Boolean {
|
): Boolean {
|
||||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
|
||||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
val symbolTable = SymbolTableMaker().makeFrom(program)
|
val symbolTable = SymbolTableMaker(program, compilerOptions).make()
|
||||||
|
|
||||||
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
|
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
|
||||||
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
|
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
|
||||||
@ -402,7 +400,7 @@ private fun createAssemblyAndAssemble(program: Program,
|
|||||||
// to help clean up the code that still depends on them.
|
// to help clean up the code that still depends on them.
|
||||||
// removeAllVardeclsFromAst(program)
|
// removeAllVardeclsFromAst(program)
|
||||||
|
|
||||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
|
||||||
// printProgram(program)
|
// printProgram(program)
|
||||||
|
|
||||||
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
|
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
|
||||||
@ -448,14 +446,14 @@ internal fun asmGeneratorFor(program: Program,
|
|||||||
options: CompilationOptions): IAssemblyGenerator
|
options: CompilationOptions): IAssemblyGenerator
|
||||||
{
|
{
|
||||||
if(options.experimentalCodegen) {
|
if(options.experimentalCodegen) {
|
||||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
|
||||||
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
|
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
|
||||||
} else {
|
} else {
|
||||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
||||||
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
|
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
|
||||||
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
|
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
|
||||||
if (options.compTarget.name == VMTarget.NAME) {
|
if (options.compTarget.name == VMTarget.NAME) {
|
||||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
|
||||||
return VmCodeGen(intermediateAst, symbolTable, options, errors)
|
return VmCodeGen(intermediateAst, symbolTable, options, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
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, subroutine.statements, subroutine.position)
|
||||||
|
if(changedParams.isNotEmpty())
|
||||||
|
subsWithParamRefsToFix += newSub
|
||||||
|
listOf(IAstModification.ReplaceNode(subroutine, newSub, parent))
|
||||||
|
} else {
|
||||||
|
if(changedParams.isNotEmpty())
|
||||||
|
subsWithParamRefsToFix += subroutine
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -61,6 +61,14 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(identifier: IdentifierReference) {
|
||||||
|
val targetParam = identifier.targetVarDecl(program)?.subroutineParameter
|
||||||
|
if(targetParam!=null) {
|
||||||
|
if((targetParam.parent as Subroutine).isAsmSubroutine)
|
||||||
|
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(returnStmt: Return) {
|
override fun visit(returnStmt: Return) {
|
||||||
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
|
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
|
||||||
if(expectedReturnValues.size>1) {
|
if(expectedReturnValues.size>1) {
|
||||||
@ -252,16 +260,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(inlineAssembly: InlineAssembly) {
|
override fun visit(inlineAssembly: InlineAssembly) {
|
||||||
val assembly = inlineAssembly.assembly
|
if(inlineAssembly.hasReturnOrRts())
|
||||||
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
|
count++
|
||||||
if (" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
|
||||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
|
|
||||||
)
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
if(" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,6 +308,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
err("subroutines can only be defined in the scope of a block or within another subroutine")
|
err("subroutines can only be defined in the scope of a block or within another subroutine")
|
||||||
|
|
||||||
if(subroutine.isAsmSubroutine) {
|
if(subroutine.isAsmSubroutine) {
|
||||||
|
if(compilerOptions.compTarget.name==VMTarget.NAME)
|
||||||
|
err("cannot use asmsub for vm target, use regular subs")
|
||||||
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
|
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
|
||||||
err("number of asm parameter registers is not the isSameAs as number of parameters")
|
err("number of asm parameter registers is not the isSameAs as number of parameters")
|
||||||
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
|
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
|
||||||
@ -319,12 +321,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||||
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
||||||
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes)
|
||||||
err("parameter '${param.first.name}' should be (u)word (an address) or str")
|
err("parameter '${param.first.name}' should be (u)word (an address) or str")
|
||||||
}
|
}
|
||||||
else if(param.second.statusflag!=null) {
|
else if(param.second.statusflag!=null) {
|
||||||
if (param.first.type != DataType.UBYTE)
|
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BOOL)
|
||||||
err("parameter '${param.first.name}' should be ubyte")
|
err("parameter '${param.first.name}' should be bool or ubyte")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
||||||
@ -334,12 +336,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||||
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes)
|
||||||
err("return type #${index + 1} should be (u)word/address")
|
err("return type #${index + 1} should be (u)word/address")
|
||||||
}
|
}
|
||||||
else if(pair.second.statusflag!=null) {
|
else if(pair.second.statusflag!=null) {
|
||||||
if (pair.first != DataType.UBYTE)
|
if (pair.first != DataType.UBYTE && pair.first != DataType.BOOL)
|
||||||
err("return type #${index + 1} should be ubyte")
|
err("return type #${index + 1} should be bool or ubyte")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,7 +520,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
val sourceDatatype = assignment.value.inferType(program)
|
val sourceDatatype = assignment.value.inferType(program)
|
||||||
if (sourceDatatype.isUnknown) {
|
if (sourceDatatype.isUnknown) {
|
||||||
if (assignment.value !is FunctionCallExpression)
|
if (assignment.value !is FunctionCallExpression)
|
||||||
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
|
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
|
||||||
} else {
|
} else {
|
||||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
|
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
|
||||||
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
|
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
|
||||||
@ -843,6 +845,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
else if(expr.operator == "~") {
|
else if(expr.operator == "~") {
|
||||||
if(dt !in IntegerDatatypes)
|
if(dt !in IntegerDatatypes)
|
||||||
errors.err("can only use bitwise invert on integer types", expr.position)
|
errors.err("can only use bitwise invert on integer types", expr.position)
|
||||||
|
if(dt==DataType.BOOL)
|
||||||
|
errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position)
|
||||||
}
|
}
|
||||||
super.visit(expr)
|
super.visit(expr)
|
||||||
}
|
}
|
||||||
@ -852,8 +856,15 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
val leftIDt = expr.left.inferType(program)
|
val leftIDt = expr.left.inferType(program)
|
||||||
val rightIDt = expr.right.inferType(program)
|
val rightIDt = expr.right.inferType(program)
|
||||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
if(!leftIDt.isKnown || !rightIDt.isKnown) {
|
||||||
|
// check if maybe one of the operands is a label, this would need a '&'
|
||||||
|
if (!leftIDt.isKnown && expr.left !is FunctionCallExpression)
|
||||||
|
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.left.position)
|
||||||
|
if (!rightIDt.isKnown && expr.right !is FunctionCallExpression)
|
||||||
|
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.right.position)
|
||||||
|
|
||||||
return // hopefully this error will be detected elsewhere
|
return // hopefully this error will be detected elsewhere
|
||||||
|
}
|
||||||
|
|
||||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||||
@ -1005,7 +1016,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
// It's not (yet) possible to handle these multiple return values because assignments
|
// It's not (yet) possible to handle these multiple return values because assignments
|
||||||
// are only to a single unique target at the same time.
|
// are only to a single unique target at the same time.
|
||||||
// EXCEPTION:
|
// EXCEPTION:
|
||||||
// if the asmsub returns multiple values and one of them is via a status register bit,
|
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
|
||||||
// it *is* possible to handle them by just actually assigning the register value and
|
// it *is* possible to handle them by just actually assigning the register value and
|
||||||
// dealing with the status bit as just being that, the status bit after the call.
|
// dealing with the status bit as just being that, the status bit after the call.
|
||||||
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||||
@ -1037,6 +1048,23 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(targetStatement!=null) {
|
if(targetStatement!=null) {
|
||||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||||
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
|
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
|
||||||
|
|
||||||
|
if(functionCallStatement.void) {
|
||||||
|
when(targetStatement) {
|
||||||
|
is BuiltinFunctionPlaceholder -> {
|
||||||
|
if(!builtinFunctionReturnType(targetStatement.name).isKnown)
|
||||||
|
errors.warn("redundant void", functionCallStatement.position)
|
||||||
|
}
|
||||||
|
is Label -> {
|
||||||
|
errors.warn("redundant void", functionCallStatement.position)
|
||||||
|
}
|
||||||
|
is Subroutine -> {
|
||||||
|
if(targetStatement.returntypes.isEmpty())
|
||||||
|
errors.warn("redundant void", functionCallStatement.position)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val funcName = functionCallStatement.target.nameInSource
|
val funcName = functionCallStatement.target.nameInSource
|
||||||
@ -1244,8 +1272,14 @@ internal class AstChecker(private val program: Program,
|
|||||||
val elementDt = containment.element.inferType(program)
|
val elementDt = containment.element.inferType(program)
|
||||||
val iterableDt = containment.iterable.inferType(program)
|
val iterableDt = containment.iterable.inferType(program)
|
||||||
|
|
||||||
if(containment.parent is BinaryExpression)
|
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
|
||||||
errors.err("containment check is currently not supported inside complex expressions", containment.position)
|
val parentBinexpr = containment.parent as? BinaryExpression
|
||||||
|
if(parentBinexpr!=null) {
|
||||||
|
// only supported if compared to 1 or 0, more complex expressions aren't supported in the 6502 code-gen.
|
||||||
|
if(parentBinexpr.operator!="==" || parentBinexpr.right.constValue(program)?.number !in listOf(0.0, 1.0))
|
||||||
|
errors.err("containment check is currently not supported inside complex expressions", containment.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(iterableDt.isIterable && containment.iterable !is RangeExpression) {
|
if(iterableDt.isIterable && containment.iterable !is RangeExpression) {
|
||||||
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
|
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
|
||||||
|
@ -27,6 +27,21 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
|
|||||||
val boolRemover = BoolRemover(this)
|
val boolRemover = BoolRemover(this)
|
||||||
boolRemover.visit(this)
|
boolRemover.visit(this)
|
||||||
boolRemover.applyModifications()
|
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)
|
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
|
||||||
fixer.visit(this)
|
fixer.visit(this)
|
||||||
while (errors.noErrors() && fixer.applyModifications() > 0) {
|
while (errors.noErrors() && fixer.applyModifications() > 0) {
|
||||||
@ -130,35 +145,19 @@ internal fun Program.variousCleanups(errors: IErrorReporter, options: Compilatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.moveMainAndStartToFirst() {
|
internal fun Program.moveMainBlockAsFirst() {
|
||||||
// The module containing the program entrypoint is moved to the first in the sequence.
|
// The module containing the program entrypoint is moved to the first in the sequence.
|
||||||
// the "main" block containing the entrypoint is moved to the top in there,
|
// the "main" block containing the entrypoint is moved to the top in there.
|
||||||
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
|
||||||
|
|
||||||
// sortModules()
|
val module = this.entrypoint.definingModule
|
||||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
val block = this.entrypoint.definingBlock
|
||||||
val start = this.entrypoint
|
moveModuleToFront(module)
|
||||||
val mod = start.definingModule
|
module.remove(block)
|
||||||
val block = start.definingBlock
|
val afterDirective = module.statements.indexOfFirst { it !is Directive }
|
||||||
moveModuleToFront(mod)
|
|
||||||
mod.remove(block)
|
|
||||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
|
||||||
if(afterDirective<0)
|
if(afterDirective<0)
|
||||||
mod.statements.add(block)
|
module.statements.add(block)
|
||||||
else
|
else
|
||||||
mod.statements.add(afterDirective, block)
|
module.statements.add(afterDirective, block)
|
||||||
block.remove(start)
|
|
||||||
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
|
||||||
if(afterDirective<0)
|
|
||||||
block.statements.add(start)
|
|
||||||
else
|
|
||||||
block.statements.add(afterDirective, start)
|
|
||||||
|
|
||||||
// overwrite the directives in the module containing the entrypoint
|
|
||||||
for(directive in directives) {
|
|
||||||
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
|
||||||
modules[0].statements.add(0, directive)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
||||||
@ -169,16 +168,9 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
|
internal fun Subroutine.hasRtsInAsm(): Boolean {
|
||||||
val instructions =
|
|
||||||
if(compTarget.name == VMTarget.NAME)
|
|
||||||
listOf(" return", "\treturn", " jump", "\tjump")
|
|
||||||
else
|
|
||||||
listOf(" rti", "\trti", " rts", "\trts", " jmp", "\tjmp", " bra", "\tbra")
|
|
||||||
return statements
|
return statements
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filterIsInstance<InlineAssembly>()
|
.filterIsInstance<InlineAssembly>()
|
||||||
.any {
|
.any { it.hasReturnOrRts() }
|
||||||
instructions.any { instr->instr in it.assembly }
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import prog8.ast.walk.IAstVisitor
|
|||||||
import prog8.code.core.ICompilationTarget
|
import prog8.code.core.ICompilationTarget
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
import prog8.compiler.BuiltinFunctions
|
import prog8.compiler.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
@ -28,9 +29,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block) {
|
override fun visit(block: Block) {
|
||||||
if(block.name in compTarget.machine.opcodeNames)
|
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
|
||||||
|
|
||||||
val existing = blocks[block.name]
|
val existing = blocks[block.name]
|
||||||
if(existing!=null) {
|
if(existing!=null) {
|
||||||
if(block.isInLibrary)
|
if(block.isInLibrary)
|
||||||
@ -50,9 +48,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
if(decl.name in BuiltinFunctions)
|
if(decl.name in BuiltinFunctions)
|
||||||
errors.err("builtin function cannot be redefined", decl.position)
|
errors.err("builtin function cannot be redefined", decl.position)
|
||||||
|
|
||||||
if(decl.name in compTarget.machine.opcodeNames)
|
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
|
||||||
|
|
||||||
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
|
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
|
||||||
if(existingInSameScope!=null && existingInSameScope!==decl)
|
if(existingInSameScope!=null && existingInSameScope!==decl)
|
||||||
nameError(decl.name, decl.position, existingInSameScope)
|
nameError(decl.name, decl.position, existingInSameScope)
|
||||||
@ -74,9 +69,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
if(subroutine.name in compTarget.machine.opcodeNames) {
|
if(subroutine.name in BuiltinFunctions) {
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
|
||||||
} else if(subroutine.name in BuiltinFunctions) {
|
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
errors.err("builtin function cannot be redefined", subroutine.position)
|
errors.err("builtin function cannot be redefined", subroutine.position)
|
||||||
} else {
|
} else {
|
||||||
@ -86,7 +79,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
|
|
||||||
val existing = subroutine.lookup(listOf(subroutine.name))
|
val existing = subroutine.lookup(listOf(subroutine.name))
|
||||||
if (existing != null && existing !== subroutine) {
|
if (existing != null && existing !== subroutine) {
|
||||||
if(existing.parent!==existing.parent)
|
if(existing.parent!==subroutine.parent && existing is Subroutine)
|
||||||
nameShadowWarning(subroutine.name, existing.position, subroutine)
|
nameShadowWarning(subroutine.name, existing.position, subroutine)
|
||||||
else
|
else
|
||||||
nameError(subroutine.name, existing.position, subroutine)
|
nameError(subroutine.name, existing.position, subroutine)
|
||||||
@ -107,10 +100,12 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subroutine.name == subroutine.definingBlock.name) {
|
if(compTarget.name != VMTarget.NAME) {
|
||||||
// subroutines cannot have the same name as their enclosing block,
|
if (subroutine.name == subroutine.definingBlock.name) {
|
||||||
// because this causes symbol scoping issues in the resulting assembly source
|
// subroutines cannot have the same name as their enclosing block,
|
||||||
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
|
// because this causes symbol scoping issues in the resulting assembly source
|
||||||
|
errors.err("name conflict '${subroutine.name}', also defined at ${subroutine.definingBlock.position} (64tass scope nesting limitation)", subroutine.position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,11 +113,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(label: Label) {
|
override fun visit(label: Label) {
|
||||||
if(label.name in compTarget.machine.opcodeNames)
|
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
|
||||||
|
|
||||||
if(label.name in BuiltinFunctions) {
|
if(label.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
|
||||||
errors.err("builtin function cannot be redefined", label.position)
|
errors.err("builtin function cannot be redefined", label.position)
|
||||||
} else {
|
} else {
|
||||||
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
|
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
|
||||||
@ -150,6 +141,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
|||||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||||
|
|
||||||
private fun visitFunctionCall(call: IFunctionCall) {
|
private fun visitFunctionCall(call: IFunctionCall) {
|
||||||
|
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
|
||||||
|
val target = call.target.targetStatement(program)
|
||||||
|
if(target==null) {
|
||||||
|
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
when (val target = call.target.targetStatement(program)) {
|
when (val target = call.target.targetStatement(program)) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
val expectedNumberOfArgs: Int = target.parameters.size
|
val expectedNumberOfArgs: Int = target.parameters.size
|
||||||
|
@ -18,9 +18,8 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
|||||||
|
|
||||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
if(parent !is VarDecl) {
|
if(parent !is VarDecl) {
|
||||||
// TODO move this / remove this, and make the codegen better instead.
|
if(options.compTarget.name == VMTarget.NAME)
|
||||||
// If the expression is pointervar[idx] where pointervar is uword and not a real array,
|
return noModifications // vm codegen deals correctly with all cases
|
||||||
// replace it by a @(pointervar+idx) expression.
|
|
||||||
// Don't replace the initializer value in a vardecl - this will be moved to a separate
|
// Don't replace the initializer value in a vardecl - this will be moved to a separate
|
||||||
// assignment statement soon in after(VarDecl)
|
// assignment statement soon in after(VarDecl)
|
||||||
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
|
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
|
||||||
@ -29,15 +28,16 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
if(options.compTarget.name==VMTarget.NAME)
|
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
|
||||||
return noModifications // vm codegen deals correctly with all cases
|
// into account the case where the index value is a word type.
|
||||||
|
// The replacement here is to fix missing cases in the 6502 codegen.
|
||||||
|
// TODO make the 6502 codegen better so this work around can be removed
|
||||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||||
if(parent is AssignTarget) {
|
if(parent is AssignTarget) {
|
||||||
val assignment = parent.parent as? Assignment
|
val assignment = parent.parent as? Assignment
|
||||||
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
|
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
|
||||||
// ONLY for a constant assignment, or direct variable assignment, the codegen contains correct optimized code.
|
// the codegen contains correct optimized code ONLY for a constant assignment, or direct variable assignment.
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.
|
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.
|
||||||
|
@ -120,16 +120,19 @@ class AstPreprocessor(val program: Program,
|
|||||||
movements.add(IAstModification.InsertFirst(decl, parentscope))
|
movements.add(IAstModification.InsertFirst(decl, parentscope))
|
||||||
replacements.add(IAstModification.Remove(decl, scope))
|
replacements.add(IAstModification.Remove(decl, scope))
|
||||||
} else {
|
} else {
|
||||||
|
val declToInsert: VarDecl
|
||||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||||
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
||||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||||
decl.value = null
|
decl.value = null
|
||||||
decl.allowInitializeWithZero = false
|
decl.allowInitializeWithZero = false
|
||||||
|
declToInsert = decl.copy()
|
||||||
} else {
|
} else {
|
||||||
replacements.add(IAstModification.Remove(decl, scope))
|
replacements.add(IAstModification.Remove(decl, scope))
|
||||||
|
declToInsert = decl
|
||||||
}
|
}
|
||||||
movements.add(IAstModification.InsertFirst(decl, parentscope))
|
movements.add(IAstModification.InsertFirst(declToInsert, parentscope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return movements + replacements
|
return movements + replacements
|
||||||
@ -138,12 +141,15 @@ class AstPreprocessor(val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
// this has to be done here becuse otherwise the string / range literal values will have been replaced by variables
|
|
||||||
if(expr.operator=="in") {
|
if(expr.operator=="in") {
|
||||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||||
}
|
}
|
||||||
|
if(expr.operator=="not in") {
|
||||||
|
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||||
|
val notContainment = PrefixExpression("not", containment, expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, notContainment, parent))
|
||||||
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,11 +37,6 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||||
// move all subroutines to the bottom of the block
|
|
||||||
val subs = block.statements.filterIsInstance<Subroutine>()
|
|
||||||
block.statements.removeAll(subs)
|
|
||||||
block.statements.addAll(subs)
|
|
||||||
|
|
||||||
// adjust global variables initialization
|
// adjust global variables initialization
|
||||||
if(options.dontReinitGlobals) {
|
if(options.dontReinitGlobals) {
|
||||||
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
||||||
@ -135,15 +130,20 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
val mods = mutableListOf<IAstModification>()
|
val mods = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
// and if an assembly block doesn't contain a rts/rti.
|
||||||
if (!subroutine.isAsmSubroutine) {
|
if (!subroutine.isAsmSubroutine) {
|
||||||
if(subroutine.statements.isEmpty() ||
|
if(subroutine.isEmpty()) {
|
||||||
(!subroutine.hasRtsInAsm(options.compTarget)
|
|
||||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
|
||||||
&& subroutine.statements.last() !is Subroutine
|
|
||||||
&& subroutine.statements.last() !is Return)) {
|
|
||||||
val returnStmt = Return(null, subroutine.position)
|
val returnStmt = Return(null, subroutine.position)
|
||||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
|
} else {
|
||||||
|
val last = subroutine.statements.last()
|
||||||
|
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
|
||||||
|
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
|
||||||
|
if(lastStatement !is Return) {
|
||||||
|
val returnStmt = Return(null, subroutine.position)
|
||||||
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,11 +164,21 @@ internal class BeforeAsmAstChanger(val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!subroutine.inline || !options.optimize) {
|
if (!subroutine.inline || !options.optimize) {
|
||||||
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) {
|
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm()) {
|
||||||
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
// make sure the NOT INLINED asm subroutine actually has a rts at the end
|
||||||
// (non-asm routines get a Return statement as needed, above)
|
// (non-asm routines get a Return statement as needed, above)
|
||||||
val instruction = if(options.compTarget.name==VMTarget.NAME) " return\n" else " rts\n"
|
mods += if(options.compTarget.name==VMTarget.NAME)
|
||||||
mods += IAstModification.InsertLast(InlineAssembly(instruction, Position.DUMMY), subroutine)
|
IAstModification.InsertLast(InlineAssembly(" return\n", true, Position.DUMMY), subroutine)
|
||||||
|
else
|
||||||
|
IAstModification.InsertLast(InlineAssembly(" rts\n", false, Position.DUMMY), subroutine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subroutine.isNotEmpty() && subroutine.statements.last() is Return) {
|
||||||
|
// maybe the last return can be removed because there is a fall-through prevention above it
|
||||||
|
val lastStatementBefore = subroutine.statements.reversed().drop(1).firstOrNull { it !is Subroutine }
|
||||||
|
if(lastStatementBefore is Return) {
|
||||||
|
mods += IAstModification.Remove(subroutine.statements.last(), subroutine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,4 +129,18 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
|||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(expr.operator=="<<" || expr.operator==">>") {
|
||||||
|
val shifts = expr.right.constValue(program)
|
||||||
|
if(shifts!=null) {
|
||||||
|
val dt = expr.left.inferType(program)
|
||||||
|
if(dt.istype(DataType.UBYTE) && shifts.number>=8.0)
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
|
if(dt.istype(DataType.UWORD) && shifts.number>=16.0)
|
||||||
|
errors.warn("shift always results in 0", expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package prog8.compiler.astprocessing
|
|||||||
|
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.ast.statements.SubroutineParameter
|
import prog8.ast.statements.SubroutineParameter
|
||||||
@ -14,7 +15,8 @@ internal class BoolRemover(val program: Program) : AstWalker() {
|
|||||||
|
|
||||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
if(typecast.type == DataType.BOOL) {
|
if(typecast.type == DataType.BOOL) {
|
||||||
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(DataType.UBYTE, 0.0, typecast.position), typecast.position)
|
val valueDt = typecast.expression.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||||
|
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(valueDt, 0.0, typecast.position), typecast.position)
|
||||||
return listOf(IAstModification.ReplaceNode(typecast, notZero, parent))
|
return listOf(IAstModification.ReplaceNode(typecast, notZero, parent))
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
|
@ -10,8 +10,7 @@ import prog8.code.core.IErrorReporter
|
|||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
|
|
||||||
|
|
||||||
internal class CodeDesugarer(val program: Program,
|
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||||
private val errors: IErrorReporter) : AstWalker() {
|
|
||||||
|
|
||||||
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
|
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
|
||||||
// Several changes have already been done by the StatementReorderer !
|
// Several changes have already been done by the StatementReorderer !
|
||||||
@ -23,6 +22,7 @@ internal class CodeDesugarer(val program: Program,
|
|||||||
// - replace while and do-until loops by just jumps.
|
// - replace while and do-until loops by just jumps.
|
||||||
// - replace peek() and poke() by direct memory accesses.
|
// - replace peek() and poke() by direct memory accesses.
|
||||||
// - repeat-forever loops replaced by label+jump.
|
// - repeat-forever loops replaced by label+jump.
|
||||||
|
// - pointer[word] replaced by @(pointer+word)
|
||||||
|
|
||||||
|
|
||||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||||
@ -135,4 +135,40 @@ _after:
|
|||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
// replace pointervar[word] by @(pointervar+word) to avoid the
|
||||||
|
// "array indexing is limited to byte size 0..255" error for pointervariables.
|
||||||
|
val indexExpr = arrayIndexedExpression.indexer.indexExpr
|
||||||
|
val indexerDt = indexExpr.inferType(program)
|
||||||
|
if(indexerDt.isWords) {
|
||||||
|
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)!!
|
||||||
|
if(arrayVar.datatype==DataType.UWORD) {
|
||||||
|
val add: Expression =
|
||||||
|
if(indexExpr.constValue(program)?.number==0.0)
|
||||||
|
arrayIndexedExpression.arrayvar.copy()
|
||||||
|
else
|
||||||
|
BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexExpr, arrayIndexedExpression.position)
|
||||||
|
return if(parent is AssignTarget) {
|
||||||
|
// assignment to array
|
||||||
|
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||||
|
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
||||||
|
} else {
|
||||||
|
// read from array
|
||||||
|
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(expr.operator=="in") {
|
||||||
|
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,10 @@ import prog8.ast.Program
|
|||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.ast.*
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.CompilationOptions
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.Position
|
|
||||||
import prog8.code.core.SourceCode
|
import prog8.code.core.SourceCode
|
||||||
import prog8.compiler.BuiltinFunctions
|
import prog8.compiler.BuiltinFunctions
|
||||||
import prog8.compiler.builtinFunctionReturnType
|
import prog8.compiler.builtinFunctionReturnType
|
||||||
@ -22,7 +23,7 @@ import kotlin.io.path.isRegularFile
|
|||||||
/**
|
/**
|
||||||
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
|
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
|
||||||
*/
|
*/
|
||||||
class IntermediateAstMaker(val program: Program) {
|
class IntermediateAstMaker(private val program: Program, private val symbolTable: SymbolTable, private val options: CompilationOptions) {
|
||||||
fun transform(): PtProgram {
|
fun transform(): PtProgram {
|
||||||
val ptProgram = PtProgram(
|
val ptProgram = PtProgram(
|
||||||
program.name,
|
program.name,
|
||||||
@ -109,19 +110,9 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun targetOf(identifier: IdentifierReference): Pair<List<String>, DataType> {
|
|
||||||
val target=identifier.targetStatement(program)!! as INamedStatement
|
|
||||||
val targetname = if(target.name in program.builtinFunctions.names)
|
|
||||||
listOf("<builtin>", target.name)
|
|
||||||
else
|
|
||||||
target.scopedName
|
|
||||||
val type = identifier.inferType(program).getOr(DataType.UNDEFINED)
|
|
||||||
return Pair(targetname, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun transform(identifier: IdentifierReference): PtIdentifier {
|
private fun transform(identifier: IdentifierReference): PtIdentifier {
|
||||||
val (target, type) = targetOf(identifier)
|
val (target, type) = identifier.targetNameAndType(program)
|
||||||
return PtIdentifier(identifier.nameInSource, target, type, identifier.position)
|
return PtIdentifier(target, type, identifier.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transform(srcBlock: Block): PtBlock {
|
private fun transform(srcBlock: Block): PtBlock {
|
||||||
@ -140,16 +131,16 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
}
|
}
|
||||||
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
|
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
|
||||||
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position)
|
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position)
|
||||||
if(vardecls.isNotEmpty()) block.add(makeScopeVarsDecls(vardecls, srcBlock.position))
|
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
|
||||||
for (stmt in statements)
|
for (stmt in statements)
|
||||||
block.add(transformStatement(stmt))
|
block.add(transformStatement(stmt))
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeScopeVarsDecls(vardecls: List<Statement>, position: Position): PtNode {
|
private fun makeScopeVarsDecls(vardecls: Iterable<Statement>): Iterable<PtNamedNode> {
|
||||||
val decls = PtScopeVarsDecls(position)
|
val decls = mutableListOf<PtNamedNode>()
|
||||||
vardecls.forEach {
|
vardecls.forEach {
|
||||||
decls.add(transformStatement(it as VarDecl))
|
decls.add(transformStatement(it as VarDecl) as PtNamedNode)
|
||||||
}
|
}
|
||||||
return decls
|
return decls
|
||||||
}
|
}
|
||||||
@ -197,7 +188,7 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
"%asminclude" -> {
|
"%asminclude" -> {
|
||||||
val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source)
|
val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source)
|
||||||
val assembly = result.getOrElse { throw it }
|
val assembly = result.getOrElse { throw it }
|
||||||
PtInlineAssembly(assembly, directive.position)
|
PtInlineAssembly(assembly.trimEnd().trimStart('\r', '\n'), false, directive.position)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// other directives don't output any code (but could end up in option flags somewhere else)
|
// other directives don't output any code (but could end up in option flags somewhere else)
|
||||||
@ -218,7 +209,7 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
|
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
|
||||||
val (target, type) = targetOf(srcCall.target)
|
val (target, type) = srcCall.target.targetNameAndType(program)
|
||||||
val call = PtFunctionCall(target,true, type, srcCall.position)
|
val call = PtFunctionCall(target,true, type, srcCall.position)
|
||||||
for (arg in srcCall.args)
|
for (arg in srcCall.args)
|
||||||
call.add(transformExpression(arg))
|
call.add(transformExpression(arg))
|
||||||
@ -226,7 +217,7 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
|
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
|
||||||
val (target, _) = targetOf(srcCall.target)
|
val (target, _) = srcCall.target.targetNameAndType(program)
|
||||||
val type = srcCall.inferType(program).getOrElse {
|
val type = srcCall.inferType(program).getOrElse {
|
||||||
throw FatalAstException("unknown dt $srcCall")
|
throw FatalAstException("unknown dt $srcCall")
|
||||||
}
|
}
|
||||||
@ -251,8 +242,10 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
return ifelse
|
return ifelse
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transform(srcNode: InlineAssembly): PtInlineAssembly =
|
private fun transform(srcNode: InlineAssembly): PtInlineAssembly {
|
||||||
PtInlineAssembly(srcNode.assembly, srcNode.position)
|
val assembly = srcNode.assembly.trimEnd().trimStart('\r', '\n')
|
||||||
|
return PtInlineAssembly(assembly, srcNode.isIR, srcNode.position)
|
||||||
|
}
|
||||||
|
|
||||||
private fun transform(srcJump: Jump): PtJump {
|
private fun transform(srcJump: Jump): PtJump {
|
||||||
val identifier = if(srcJump.identifier!=null) transform(srcJump.identifier!!) else null
|
val identifier = if(srcJump.identifier!=null) transform(srcJump.identifier!!) else null
|
||||||
@ -306,13 +299,26 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
sub.parameters.forEach { it.first.parent=sub }
|
sub.parameters.forEach { it.first.parent=sub }
|
||||||
|
|
||||||
if(srcSub.asmAddress==null) {
|
if(srcSub.asmAddress==null) {
|
||||||
var combinedAsm = ""
|
var combinedTrueAsm = ""
|
||||||
for (asm in srcSub.statements)
|
var combinedIrAsm = ""
|
||||||
combinedAsm += (asm as InlineAssembly).assembly + "\n"
|
for (asm in srcSub.statements) {
|
||||||
if(combinedAsm.isNotEmpty())
|
asm as InlineAssembly
|
||||||
sub.add(PtInlineAssembly(combinedAsm, srcSub.statements[0].position))
|
if(asm.isIR)
|
||||||
else
|
combinedIrAsm += asm.assembly + "\n"
|
||||||
sub.add(PtInlineAssembly("", srcSub.position))
|
else
|
||||||
|
combinedTrueAsm += asm.assembly + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if(combinedTrueAsm.isNotEmpty()) {
|
||||||
|
combinedTrueAsm = combinedTrueAsm.trimEnd().trimStart('\r', '\n')
|
||||||
|
sub.add(PtInlineAssembly(combinedTrueAsm, false, srcSub.statements[0].position))
|
||||||
|
}
|
||||||
|
if(combinedIrAsm.isNotEmpty()) {
|
||||||
|
combinedIrAsm = combinedIrAsm.trimEnd().trimStart('\r', '\n')
|
||||||
|
sub.add(PtInlineAssembly(combinedIrAsm, true, srcSub.statements[0].position))
|
||||||
|
}
|
||||||
|
if(combinedIrAsm.isEmpty() && combinedTrueAsm.isEmpty())
|
||||||
|
sub.add(PtInlineAssembly("", true, srcSub.position))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sub
|
return sub
|
||||||
@ -329,8 +335,7 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
srcSub.inline,
|
srcSub.inline,
|
||||||
srcSub.position)
|
srcSub.position)
|
||||||
sub.parameters.forEach { it.parent=sub }
|
sub.parameters.forEach { it.parent=sub }
|
||||||
|
makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
|
||||||
if(vardecls.isNotEmpty()) sub.add(makeScopeVarsDecls(vardecls, sub.position))
|
|
||||||
for (statement in statements)
|
for (statement in statements)
|
||||||
sub.add(transformStatement(statement))
|
sub.add(transformStatement(statement))
|
||||||
|
|
||||||
@ -344,7 +349,7 @@ class IntermediateAstMaker(val program: Program) {
|
|||||||
PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||||
}
|
}
|
||||||
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
|
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
|
||||||
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.position)
|
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +40,33 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(expr.operator=="^" && expr.left.inferType(program) istype DataType.BOOL && expr.right.constValue(program)?.number == 1.0) {
|
||||||
|
// boolean ^ 1 --> not boolean
|
||||||
|
val notExpr = PrefixExpression("not", expr.left, expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, notExpr, parent))
|
||||||
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||||
if(expr.operator == "not") {
|
if(expr.operator == "not") {
|
||||||
|
|
||||||
|
// first check if we're already part of a "boolean" expresion (i.e. comparing against 0)
|
||||||
|
// if so, simplify THAT whole expression rather than making it more complicated
|
||||||
|
if(parent is BinaryExpression && parent.right.constValue(program)?.number==0.0) {
|
||||||
|
if(parent.operator=="==") {
|
||||||
|
// (NOT X)==0 --> X!=0
|
||||||
|
val replacement = BinaryExpression(expr.expression, "!=", NumericLiteral.optimalInteger(0, expr.position), expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
|
||||||
|
} else if(parent.operator=="!=") {
|
||||||
|
// (NOT X)!=0 --> X==0
|
||||||
|
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// not(not(x)) -> x
|
// not(not(x)) -> x
|
||||||
if((expr.expression as? PrefixExpression)?.operator=="not")
|
if((expr.expression as? PrefixExpression)?.operator=="not")
|
||||||
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.*
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.compiler.BuiltinFunctions
|
|
||||||
|
|
||||||
internal class StatementReorderer(val program: Program,
|
internal class StatementReorderer(val program: Program,
|
||||||
val errors: IErrorReporter,
|
val errors: IErrorReporter,
|
||||||
@ -129,15 +127,6 @@ internal class StatementReorderer(val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
if(subroutine.name=="start" && parent is Block) {
|
|
||||||
if(parent.statements.asSequence().filterIsInstance<Subroutine>().first().name!="start") {
|
|
||||||
return listOf(
|
|
||||||
IAstModification.Remove(subroutine, parent),
|
|
||||||
IAstModification.InsertFirst(subroutine, parent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val modifications = mutableListOf<IAstModification>()
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
val subs = subroutine.statements.filterIsInstance<Subroutine>()
|
val subs = subroutine.statements.filterIsInstance<Subroutine>()
|
||||||
@ -195,54 +184,6 @@ internal class StatementReorderer(val program: Program,
|
|||||||
&& maySwapOperandOrder(expr))
|
&& maySwapOperandOrder(expr))
|
||||||
return listOf(IAstModification.SwapOperands(expr))
|
return listOf(IAstModification.SwapOperands(expr))
|
||||||
|
|
||||||
// when using a simple bit shift and assigning it to a variable of a different type,
|
|
||||||
// try to make the bit shifting 'wide enough' to fall into the variable's type.
|
|
||||||
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
|
|
||||||
if(expr.operator=="<<" || expr.operator==">>") {
|
|
||||||
val leftDt = expr.left.inferType(program)
|
|
||||||
when (parent) {
|
|
||||||
is Assignment -> {
|
|
||||||
val targetDt = parent.target.inferType(program)
|
|
||||||
if(leftDt != targetDt) {
|
|
||||||
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
|
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is VarDecl -> {
|
|
||||||
if(leftDt isnot parent.datatype) {
|
|
||||||
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IFunctionCall -> {
|
|
||||||
val argnum = parent.args.indexOf(expr)
|
|
||||||
when (val callee = parent.target.targetStatement(program)) {
|
|
||||||
is Subroutine -> {
|
|
||||||
val paramType = callee.parameters[argnum].type
|
|
||||||
if(leftDt isAssignableTo paramType) {
|
|
||||||
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
|
|
||||||
if(replaced)
|
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BuiltinFunctionPlaceholder -> {
|
|
||||||
val func = BuiltinFunctions.getValue(callee.name)
|
|
||||||
val paramTypes = func.parameters[argnum].possibleDatatypes
|
|
||||||
for(type in paramTypes) {
|
|
||||||
if(leftDt isAssignableTo type) {
|
|
||||||
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
|
|
||||||
if(replaced)
|
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("weird callee")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> return noModifications
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,9 +305,7 @@ internal class StatementReorderer(val program: Program,
|
|||||||
AddressOf(sourceIdent, assign.position),
|
AddressOf(sourceIdent, assign.position),
|
||||||
AddressOf(identifier, assign.position),
|
AddressOf(identifier, assign.position),
|
||||||
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
|
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
|
||||||
),
|
), false, assign.position
|
||||||
true,
|
|
||||||
assign.position
|
|
||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||||
}
|
}
|
||||||
@ -378,7 +317,7 @@ internal class StatementReorderer(val program: Program,
|
|||||||
assign.value as? IdentifierReference ?: assign.value,
|
assign.value as? IdentifierReference ?: assign.value,
|
||||||
identifier
|
identifier
|
||||||
),
|
),
|
||||||
true,
|
false,
|
||||||
assign.position
|
assign.position
|
||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
||||||
|
@ -7,22 +7,27 @@ import prog8.ast.statements.*
|
|||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.code.*
|
import prog8.code.*
|
||||||
import prog8.code.core.ArrayDatatypes
|
import prog8.code.core.ArrayDatatypes
|
||||||
|
import prog8.code.core.CompilationOptions
|
||||||
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal class SymbolTableMaker: IAstVisitor {
|
internal class SymbolTableMaker(private val program: Program, private val options: CompilationOptions): IAstVisitor {
|
||||||
|
|
||||||
private val st = SymbolTable()
|
private val st = SymbolTable()
|
||||||
private val scopestack = Stack<StNode>()
|
private val scopestack = Stack<StNode>()
|
||||||
|
private var dontReinitGlobals = false
|
||||||
|
|
||||||
fun makeFrom(program: Program): SymbolTable {
|
fun make(): SymbolTable {
|
||||||
scopestack.clear()
|
scopestack.clear()
|
||||||
st.children.clear()
|
st.children.clear()
|
||||||
|
dontReinitGlobals = options.dontReinitGlobals
|
||||||
this.visit(program)
|
this.visit(program)
|
||||||
program.builtinFunctions.names.forEach {
|
program.builtinFunctions.names.forEach {
|
||||||
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
|
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
|
||||||
st.add(node)
|
st.add(node)
|
||||||
}
|
}
|
||||||
|
require(scopestack.isEmpty())
|
||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +43,7 @@ internal class SymbolTableMaker: IAstVisitor {
|
|||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
if(subroutine.asmAddress!=null) {
|
if(subroutine.asmAddress!=null) {
|
||||||
val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
|
val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
|
||||||
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmParameterRegisters, subroutine.position)
|
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmReturnvaluesRegisters, subroutine.position)
|
||||||
scopestack.peek().add(node)
|
scopestack.peek().add(node)
|
||||||
// st.origAstLinks[subroutine] = node
|
// st.origAstLinks[subroutine] = node
|
||||||
} else {
|
} else {
|
||||||
@ -57,7 +62,9 @@ internal class SymbolTableMaker: IAstVisitor {
|
|||||||
val node =
|
val node =
|
||||||
when(decl.type) {
|
when(decl.type) {
|
||||||
VarDeclType.VAR -> {
|
VarDeclType.VAR -> {
|
||||||
val initialNumeric = (decl.value as? NumericLiteral)?.number
|
var initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||||
|
if(initialNumeric==0.0)
|
||||||
|
initialNumeric=null // variable will go into BSS and this will be set to 0
|
||||||
val initialStringLit = decl.value as? StringLiteral
|
val initialStringLit = decl.value as? StringLiteral
|
||||||
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||||
val initialArrayLit = decl.value as? ArrayLiteral
|
val initialArrayLit = decl.value as? ArrayLiteral
|
||||||
@ -71,7 +78,13 @@ internal class SymbolTableMaker: IAstVisitor {
|
|||||||
initialStringLit.value.length+1 // include the terminating 0-byte
|
initialStringLit.value.length+1 // include the terminating 0-byte
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
StStaticVariable(decl.name, decl.datatype, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
val bss = if(decl.datatype==DataType.STR)
|
||||||
|
false
|
||||||
|
else if(decl.isArray)
|
||||||
|
initialArray.isNullOrEmpty()
|
||||||
|
else
|
||||||
|
initialNumeric == null
|
||||||
|
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
||||||
}
|
}
|
||||||
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
|
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
|
||||||
VarDeclType.MEMORY -> {
|
VarDeclType.MEMORY -> {
|
||||||
@ -91,8 +104,14 @@ internal class SymbolTableMaker: IAstVisitor {
|
|||||||
return null
|
return null
|
||||||
return arrayLit.value.map {
|
return arrayLit.value.map {
|
||||||
when(it){
|
when(it){
|
||||||
is AddressOf -> StArrayElement(null, it.identifier.nameInSource)
|
is AddressOf -> {
|
||||||
is IdentifierReference -> StArrayElement(null, it.nameInSource)
|
val scopedName = it.identifier.targetNameAndType(program).first
|
||||||
|
StArrayElement(null, scopedName)
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val scopedName = it.targetNameAndType(program).first
|
||||||
|
StArrayElement(null, scopedName)
|
||||||
|
}
|
||||||
is NumericLiteral -> StArrayElement(it.number, null)
|
is NumericLiteral -> StArrayElement(it.number, null)
|
||||||
else -> throw FatalAstException("weird element dt in array literal")
|
else -> throw FatalAstException("weird element dt in array literal")
|
||||||
}
|
}
|
||||||
@ -105,15 +124,15 @@ internal class SymbolTableMaker: IAstVisitor {
|
|||||||
// st.origAstLinks[label] = node
|
// st.origAstLinks[label] = node
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(fcall: BuiltinFunctionCall) {
|
override fun visit(bfc: BuiltinFunctionCall) {
|
||||||
if(fcall.name=="memory") {
|
if(bfc.name=="memory") {
|
||||||
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
||||||
val name = (fcall.args[0] as StringLiteral).value
|
val name = (bfc.args[0] as StringLiteral).value
|
||||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||||
val size = (fcall.args[1] as NumericLiteral).number.toUInt()
|
val size = (bfc.args[1] as NumericLiteral).number.toUInt()
|
||||||
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
|
val align = (bfc.args[2] as NumericLiteral).number.toUInt()
|
||||||
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, fcall.position))
|
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, bfc.position))
|
||||||
}
|
}
|
||||||
super.visit(fcall)
|
super.visit(bfc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
|||||||
val rightCv = expr.right.constValue(program)
|
val rightCv = expr.right.constValue(program)
|
||||||
|
|
||||||
if(leftDt.isKnown && rightDt.isKnown) {
|
if(leftDt.isKnown && rightDt.isKnown) {
|
||||||
|
|
||||||
|
if(expr.operator=="<<" && leftDt.isBytes) {
|
||||||
|
// uword ww = 1 << shift --> make the '1' a word constant
|
||||||
|
val leftConst = expr.left.constValue(program)
|
||||||
|
if(leftConst!=null) {
|
||||||
|
val leftConstAsWord =
|
||||||
|
if(leftDt.istype(DataType.UBYTE))
|
||||||
|
NumericLiteral(DataType.UWORD, leftConst.number, leftConst.position)
|
||||||
|
else
|
||||||
|
NumericLiteral(DataType.WORD, leftConst.number, leftConst.position)
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
if (parent is Assignment) {
|
||||||
|
if (parent.target.inferType(program).isWords) {
|
||||||
|
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
|
||||||
|
if(rightDt.isBytes)
|
||||||
|
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
|
||||||
|
}
|
||||||
|
} else if (parent is TypecastExpression && parent.type == DataType.UWORD && parent.parent is Assignment) {
|
||||||
|
val assign = parent.parent as Assignment
|
||||||
|
if (assign.target.inferType(program).isWords) {
|
||||||
|
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
|
||||||
|
if(rightDt.isBytes)
|
||||||
|
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(modifications.isNotEmpty())
|
||||||
|
return modifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) {
|
if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||||
// see if any of the operands needs conversion to bool
|
// see if any of the operands needs conversion to bool
|
||||||
val modifications = mutableListOf<IAstModification>()
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
@ -6,8 +6,7 @@ import prog8.ast.Node
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.AnonymousScope
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
@ -63,10 +62,18 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
val nextAssign = assignment.nextSibling() as? Assignment
|
// remove duplicated assignments, but not if it's a memory mapped IO register
|
||||||
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
|
val isIO = try {
|
||||||
if(!nextAssign.isAugmentable && nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates
|
assignment.target.isIOAddress(options.compTarget.machine)
|
||||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
} catch (_: FatalAstException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
if(!isIO) {
|
||||||
|
val nextAssign = assignment.nextSibling() as? Assignment
|
||||||
|
if (nextAssign != null && nextAssign.target.isSameAs(assignment.target, program)) {
|
||||||
|
if (!nextAssign.isAugmentable && nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates
|
||||||
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -173,6 +180,19 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkArray(variable: VarDecl): Iterable<IAstModification> {
|
||||||
|
return if(variable.value==null) {
|
||||||
|
val arraySpec = variable.arraysize!!
|
||||||
|
val size = arraySpec.indexExpr.constValue(program)?.number?.toInt() ?: throw FatalAstException("no array size")
|
||||||
|
return if(size==0)
|
||||||
|
replaceWithFalse()
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
else
|
||||||
|
checkArray((variable.value as ArrayLiteral).value)
|
||||||
|
}
|
||||||
|
|
||||||
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
|
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
|
||||||
if(stringVal.value.isEmpty())
|
if(stringVal.value.isEmpty())
|
||||||
return replaceWithFalse()
|
return replaceWithFalse()
|
||||||
@ -196,8 +216,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
|||||||
return checkString(stringVal)
|
return checkString(stringVal)
|
||||||
}
|
}
|
||||||
in ArrayDatatypes -> {
|
in ArrayDatatypes -> {
|
||||||
val array = (variable.value as ArrayLiteral).value
|
return checkArray(variable)
|
||||||
return checkArray(array)
|
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@ -219,5 +238,22 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
|||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(branch.truepart.isEmpty() && branch.elsepart.isEmpty()) {
|
||||||
|
errors.warn("removing empty conditional branch", branch.position)
|
||||||
|
return listOf(IAstModification.Remove(branch, parent as IStatementContainer))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty()) {
|
||||||
|
errors.warn("removing empty if-else statement", ifElse.position)
|
||||||
|
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import prog8.ast.Program
|
|||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
|
import prog8.code.core.ByteDatatypes
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
@ -82,7 +83,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
|||||||
if(mismatch>=0) {
|
if(mismatch>=0) {
|
||||||
val actual = argtypes[mismatch]
|
val actual = argtypes[mismatch]
|
||||||
val expected = consideredParamTypes[mismatch]
|
val expected = consideredParamTypes[mismatch]
|
||||||
return if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
|
return if(actual==DataType.BOOL && expected in ByteDatatypes)
|
||||||
|
null // a bool is just 1 or 0.
|
||||||
|
else if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
|
||||||
null // specifying a 1 or 0 as a BOOL is okay
|
null // specifying a 1 or 0 as a BOOL is okay
|
||||||
else
|
else
|
||||||
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
|
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
|
||||||
@ -91,6 +94,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
|||||||
if(target.asmReturnvaluesRegisters.size>1) {
|
if(target.asmReturnvaluesRegisters.size>1) {
|
||||||
// multiple return values will NOT work inside an expression.
|
// multiple return values will NOT work inside an expression.
|
||||||
// they MIGHT work in a regular assignment or just a function call statement.
|
// they MIGHT work in a regular assignment or just a function call statement.
|
||||||
|
// EXCEPTION:
|
||||||
|
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
|
||||||
|
// it *is* possible to handle them by just actually assigning the register value and
|
||||||
|
// dealing with the status bit as just being that, the status bit after the call.
|
||||||
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||||
if (call !is FunctionCallStatement) {
|
if (call !is FunctionCallStatement) {
|
||||||
val checkParent =
|
val checkParent =
|
||||||
@ -99,7 +106,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
|||||||
else
|
else
|
||||||
parent
|
parent
|
||||||
if (checkParent !is Assignment && checkParent !is VarDecl) {
|
if (checkParent !is Assignment && checkParent !is VarDecl) {
|
||||||
return Pair("can't use subroutine call that returns multiple return values here", call.position)
|
val (returnRegisters, _) = target.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||||
|
if (returnRegisters.size>1) {
|
||||||
|
return Pair("can't use subroutine call that returns multiple return values here", call.position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,8 +52,8 @@ class TestAstChecks: FunSpec({
|
|||||||
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldBe null
|
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldBe null
|
||||||
errors.errors.size shouldBe 2
|
errors.errors.size shouldBe 2
|
||||||
errors.warnings.size shouldBe 0
|
errors.warnings.size shouldBe 0
|
||||||
errors.errors[0] shouldContain ":7:28: assignment value is invalid"
|
errors.errors[0] shouldContain ":7:28: invalid assignment value, maybe forgot '&'"
|
||||||
errors.errors[1] shouldContain ":8:28: assignment value is invalid"
|
errors.errors[1] shouldContain ":8:28: invalid assignment value, maybe forgot '&'"
|
||||||
}
|
}
|
||||||
|
|
||||||
test("can't do str or array expression without using address-of") {
|
test("can't do str or array expression without using address-of") {
|
||||||
|
@ -44,18 +44,18 @@ class TestBuiltinFunctions: FunSpec({
|
|||||||
conv.returns.reg shouldBe RegisterOrPair.A
|
conv.returns.reg shouldBe RegisterOrPair.A
|
||||||
}
|
}
|
||||||
|
|
||||||
test("not-pure func with fixed type") {
|
test("not-pure func with varying result value type") {
|
||||||
val func = BuiltinFunctions.getValue("rnd")
|
val func = BuiltinFunctions.getValue("cmp")
|
||||||
func.name shouldBe "rnd"
|
func.name shouldBe "cmp"
|
||||||
func.parameters.size shouldBe 0
|
func.parameters.size shouldBe 2
|
||||||
func.pure shouldBe false
|
func.pure shouldBe false
|
||||||
func.returnType shouldBe DataType.UBYTE
|
func.returnType shouldBe null
|
||||||
|
|
||||||
val conv = func.callConvention(emptyList())
|
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UWORD))
|
||||||
conv.params.size shouldBe 0
|
conv.params.size shouldBe 2
|
||||||
conv.returns.dt shouldBe DataType.UBYTE
|
conv.returns.dt shouldBe null
|
||||||
conv.returns.floatFac1 shouldBe false
|
conv.returns.floatFac1 shouldBe false
|
||||||
conv.returns.reg shouldBe RegisterOrPair.A
|
conv.returns.reg shouldBe null
|
||||||
}
|
}
|
||||||
|
|
||||||
test("func without return type") {
|
test("func without return type") {
|
||||||
|
@ -112,7 +112,7 @@ class TestCallgraph: FunSpec({
|
|||||||
"""
|
"""
|
||||||
val result = compileText(C64Target(), false, sourcecode)!!
|
val result = compileText(C64Target(), false, sourcecode)!!
|
||||||
val graph = CallGraph(result.program)
|
val graph = CallGraph(result.program)
|
||||||
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 9
|
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 5
|
||||||
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
|
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
|
||||||
empties.size shouldBe 3
|
empties.size shouldBe 3
|
||||||
empties[0].position.line shouldBe 4
|
empties[0].position.line shouldBe 4
|
||||||
|
@ -8,7 +8,6 @@ import prog8.compiler.CompilationResult
|
|||||||
import prog8.compiler.CompilerArguments
|
import prog8.compiler.CompilerArguments
|
||||||
import prog8.compiler.compileProgram
|
import prog8.compiler.compileProgram
|
||||||
import prog8tests.helpers.*
|
import prog8tests.helpers.*
|
||||||
import prog8tests.helpers.compileText
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.absolute
|
import kotlin.io.path.absolute
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
@ -34,7 +33,6 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
|||||||
quietAssembler = true,
|
quietAssembler = true,
|
||||||
asmListfile = false,
|
asmListfile = false,
|
||||||
experimentalCodegen = false,
|
experimentalCodegen = false,
|
||||||
keepIR = false,
|
|
||||||
compilationTarget = target.name,
|
compilationTarget = target.name,
|
||||||
evalStackBaseAddress = null,
|
evalStackBaseAddress = null,
|
||||||
symbolDefs = emptyMap(),
|
symbolDefs = emptyMap(),
|
||||||
@ -100,14 +98,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
|||||||
"circles",
|
"circles",
|
||||||
"cobramk3-gfx",
|
"cobramk3-gfx",
|
||||||
"colorbars",
|
"colorbars",
|
||||||
|
"cube3d",
|
||||||
"datetime",
|
"datetime",
|
||||||
|
"diskspeed",
|
||||||
|
"fileseek",
|
||||||
"highresbitmap",
|
"highresbitmap",
|
||||||
"kefrenbars",
|
"kefrenbars",
|
||||||
|
"keyboardhandler",
|
||||||
"mandelbrot",
|
"mandelbrot",
|
||||||
"mandelbrot-gfx-colors",
|
"mandelbrot-gfx-colors",
|
||||||
"multipalette",
|
"multipalette",
|
||||||
"rasterbars",
|
"rasterbars",
|
||||||
"sincos",
|
"sincos",
|
||||||
|
"snow",
|
||||||
"tehtriz",
|
"tehtriz",
|
||||||
"testgfx2",
|
"testgfx2",
|
||||||
),
|
),
|
||||||
@ -185,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
|||||||
val (displayName, filepath) = prepareTestFiles(it, false, target)
|
val (displayName, filepath) = prepareTestFiles(it, false, target)
|
||||||
test(displayName) {
|
test(displayName) {
|
||||||
val src = filepath.readText()
|
val src = filepath.readText()
|
||||||
compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null
|
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||||
compileText(target, false, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -40,7 +40,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
|||||||
strLits[0].value shouldBe "main.bar"
|
strLits[0].value shouldBe "main.bar"
|
||||||
strLits[1].value shouldBe "foo.bar"
|
strLits[1].value shouldBe "foo.bar"
|
||||||
strLits[0].definingScope.name shouldBe "main"
|
strLits[0].definingScope.name shouldBe "main"
|
||||||
strLits[1].definingScope.name shouldBe "foo"
|
strLits[1].definingScope.name shouldBe "foobar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
|||||||
quietAssembler = true,
|
quietAssembler = true,
|
||||||
asmListfile = false,
|
asmListfile = false,
|
||||||
experimentalCodegen = false,
|
experimentalCodegen = false,
|
||||||
keepIR = false,
|
|
||||||
compilationTarget = Cx16Target.NAME,
|
compilationTarget = Cx16Target.NAME,
|
||||||
evalStackBaseAddress = null,
|
evalStackBaseAddress = null,
|
||||||
symbolDefs = emptyMap(),
|
symbolDefs = emptyMap(),
|
||||||
@ -87,7 +86,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
|||||||
|
|
||||||
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||||
val sourcedirs = listOf("${fixturesDir}")
|
val sourcedirs = listOf("$fixturesDir")
|
||||||
compileFile(filepath.fileName, sourcedirs) shouldNotBe null
|
compileFile(filepath.fileName, sourcedirs) shouldNotBe null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
61
compiler/test/TestGoldenRam.kt
Normal file
61
compiler/test/TestGoldenRam.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package prog8tests
|
||||||
|
|
||||||
|
import com.github.michaelbull.result.expectError
|
||||||
|
import com.github.michaelbull.result.getOrThrow
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
|
|
||||||
|
|
||||||
|
class TestGoldenRam: FunSpec({
|
||||||
|
|
||||||
|
val options = CompilationOptions(
|
||||||
|
OutputType.RAW,
|
||||||
|
CbmPrgLauncherType.NONE,
|
||||||
|
ZeropageType.FULL,
|
||||||
|
listOf((0x00u..0xffu)),
|
||||||
|
floats = true,
|
||||||
|
noSysInit = false,
|
||||||
|
compTarget = VMTarget(),
|
||||||
|
loadAddress = 999u
|
||||||
|
)
|
||||||
|
|
||||||
|
test("empty golden ram allocations") {
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val golden = GoldenRam(options, UIntRange.EMPTY)
|
||||||
|
val result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||||
|
result.expectError { "should not be able to allocate anything" }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("regular golden ram allocations") {
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val golden = GoldenRam(options, 0x400u until 0x800u)
|
||||||
|
|
||||||
|
var result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||||
|
var alloc = result.getOrThrow()
|
||||||
|
alloc.size shouldBe 1
|
||||||
|
alloc.address shouldBe 0x400u
|
||||||
|
result = golden.allocate(listOf("test"), DataType.STR, 100, null, errors)
|
||||||
|
alloc = result.getOrThrow()
|
||||||
|
alloc.size shouldBe 100
|
||||||
|
alloc.address shouldBe 0x401u
|
||||||
|
|
||||||
|
repeat(461) {
|
||||||
|
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
|
||||||
|
alloc = result.getOrThrow()
|
||||||
|
alloc.size shouldBe 2
|
||||||
|
}
|
||||||
|
|
||||||
|
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
|
||||||
|
result.expectError { "just 1 more byte available" }
|
||||||
|
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||||
|
alloc = result.getOrThrow()
|
||||||
|
alloc.size shouldBe 1
|
||||||
|
alloc.address shouldBe golden.region.last
|
||||||
|
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||||
|
result.expectError { "nothing more available" }
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
@ -11,10 +11,16 @@ class TestLaunchEmu: FunSpec({
|
|||||||
test("test launch virtualmachine via target") {
|
test("test launch virtualmachine via target") {
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
|
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
|
||||||
tmpfile.writeText("""<PROGRAM NAME=test>
|
tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PROGRAM NAME="test">
|
||||||
<OPTIONS>
|
<OPTIONS>
|
||||||
</OPTIONS>
|
</OPTIONS>
|
||||||
|
|
||||||
|
<ASMSYMBOLS>
|
||||||
|
</ASMSYMBOLS>
|
||||||
|
|
||||||
|
<BSS>
|
||||||
|
</BSS>
|
||||||
<VARIABLES>
|
<VARIABLES>
|
||||||
</VARIABLES>
|
</VARIABLES>
|
||||||
|
|
||||||
@ -27,7 +33,7 @@ class TestLaunchEmu: FunSpec({
|
|||||||
<INITGLOBALS>
|
<INITGLOBALS>
|
||||||
</INITGLOBALS>
|
</INITGLOBALS>
|
||||||
|
|
||||||
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]>
|
<BLOCK NAME="main" ADDRESS="null" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
|
||||||
</BLOCK>
|
</BLOCK>
|
||||||
</PROGRAM>
|
</PROGRAM>
|
||||||
""")
|
""")
|
||||||
|
@ -15,6 +15,7 @@ import prog8.ast.statements.*
|
|||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
import prog8.code.target.C64Target
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.code.target.Cx16Target
|
||||||
import prog8.compiler.printProgram
|
import prog8.compiler.printProgram
|
||||||
import prog8tests.helpers.*
|
import prog8tests.helpers.*
|
||||||
|
|
||||||
@ -736,4 +737,60 @@ class TestOptimization: FunSpec({
|
|||||||
val stmts = result.program.entrypoint.statements
|
val stmts = result.program.entrypoint.statements
|
||||||
stmts.size shouldBe 3
|
stmts.size shouldBe 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("repeated assignments to IO register should remain") {
|
||||||
|
val srcX16="""
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
ubyte @shared xx
|
||||||
|
xx = 42
|
||||||
|
xx = 42 ; removed
|
||||||
|
xx = 42 ; removed
|
||||||
|
cx16.VERA_DATA0 = 0
|
||||||
|
cx16.VERA_DATA0 = 0
|
||||||
|
cx16.VERA_DATA0 = 0
|
||||||
|
@($9fff) = 0
|
||||||
|
@($9fff) = 0
|
||||||
|
@($9fff) = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
var result = compileText(Cx16Target(), true, srcX16, writeAssembly = true)!!
|
||||||
|
var statements = result.program.entrypoint.statements
|
||||||
|
statements.size shouldBe 9
|
||||||
|
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||||
|
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
|
||||||
|
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
|
||||||
|
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
|
||||||
|
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
|
||||||
|
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
|
||||||
|
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
|
||||||
|
|
||||||
|
val srcC64="""
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
ubyte @shared xx
|
||||||
|
xx = 42
|
||||||
|
xx = 42 ;removed
|
||||||
|
xx = 42 ;removed
|
||||||
|
c64.EXTCOL = 0
|
||||||
|
c64.EXTCOL = 0
|
||||||
|
c64.EXTCOL = 0
|
||||||
|
@(53281) = 0
|
||||||
|
@(53281) = 0
|
||||||
|
@(53281) = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
result = compileText(C64Target(), true, srcC64, writeAssembly = true)!!
|
||||||
|
statements = result.program.entrypoint.statements
|
||||||
|
statements.size shouldBe 9
|
||||||
|
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||||
|
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
|
||||||
|
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
|
||||||
|
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
|
||||||
|
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
|
||||||
|
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
|
||||||
|
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user