mirror of
https://github.com/irmen/prog8.git
synced 2025-06-17 01:23:34 +00:00
Compare commits
323 Commits
Author | SHA1 | Date | |
---|---|---|---|
0371ffa4ce | |||
88ce9300bc | |||
0e3d75cfeb | |||
630c8a5faa | |||
905921a684 | |||
bff3c4f95c | |||
4c8898a639 | |||
97df33ab1a | |||
ef46fb2685 | |||
d5d6dd3614 | |||
6c233c6a0a | |||
6db715d879 | |||
ab02e8a546 | |||
8cbfe64f19 | |||
68336a76c5 | |||
393e914a86 | |||
ffb54110e9 | |||
533d825f1a | |||
c65279b672 | |||
f9926beeef | |||
add8a777d8 | |||
3fc49c001e | |||
24f37e2062 | |||
f465b2e2a0 | |||
ce00e49a89 | |||
d494f9d66b | |||
c35a183a64 | |||
9cdd5fe7f2 | |||
c21428215e | |||
64d5af46f5 | |||
25846ea18a | |||
798383596d | |||
9ca71bc937 | |||
5407429ec0 | |||
ee5c94f6db | |||
91045afbee | |||
3f64782023 | |||
f8d35f9502 | |||
ea78d3ec9a | |||
e056a28316 | |||
0bea721c2e | |||
e1b89494d0 | |||
cd8e7f3912 | |||
50604c25c2 | |||
aa6b2357d8 | |||
5b2d29bef6 | |||
a296d26328 | |||
d01a26ec61 | |||
efd7d6f0c0 | |||
b55be093be | |||
7c1d5cadd7 | |||
dd1592b03b | |||
9b37ac483f | |||
090820958e | |||
ac21e1be5c | |||
5196443b26 | |||
c8531cbeb1 | |||
c560abedba | |||
9b952fbc44 | |||
ccdf05e922 | |||
c3d74f2ae9 | |||
f47498888c | |||
5665a7f0cb | |||
b8178c6c8d | |||
4a0f15eb88 | |||
c4f53fe525 | |||
8c93ec52de | |||
befe0fff2a | |||
b6a837cbea | |||
4861973899 | |||
c593e4b500 | |||
5bf78c20d4 | |||
5c672130e6 | |||
d8214d4f12 | |||
64d1f09ce0 | |||
47d0f0ea40 | |||
2d85fd093e | |||
d936568b76 | |||
4598a83e8e | |||
f4bf00ad31 | |||
07fde7f6cc | |||
729209574e | |||
f28206d989 | |||
0c81b32cac | |||
11216017cb | |||
a7b9f53967 | |||
1fa2e2e37d | |||
f67d5faeb7 | |||
5cbf859458 | |||
629ed74d09 | |||
ca2af2ca63 | |||
52ab089615 | |||
01461a196d | |||
04832f052a | |||
c8b2c8ae50 | |||
1b81c7fb22 | |||
9ccda0247e | |||
a7df4dcf25 | |||
d91f47c791 | |||
a9ac4e7f44 | |||
fc3ec57437 | |||
266f6ab919 | |||
6218c1c00b | |||
cc81d6fe82 | |||
69f9102f2d | |||
beb9275982 | |||
abe48713f2 | |||
82cfaf2fbb | |||
3d3bc4738f | |||
2d0746f5a4 | |||
9c71e2f1c8 | |||
134fd62da8 | |||
2afd283582 | |||
c66734bab0 | |||
8e56a61f95 | |||
d265271148 | |||
b40e397b28 | |||
35ff1d996a | |||
deea0b05cb | |||
c50c9ca545 | |||
a819b4a5a5 | |||
df2d7d4734 | |||
79ce4098cf | |||
374464a1f8 | |||
c8d0bf27af | |||
6e4ae034b2 | |||
52b560e72d | |||
9b971ad222 | |||
3613162d09 | |||
3a272e998d | |||
d4c750beb4 | |||
84b31e65e1 | |||
7b802bfd3d | |||
f9c4632b8d | |||
e4764cd8a6 | |||
dd78a3a686 | |||
94c06e13f4 | |||
e8bebe5a75 | |||
5b0e1b4f9e | |||
8c0a93779b | |||
9241479da4 | |||
8ffca93cd5 | |||
7fea0c124a | |||
20dbdb20d2 | |||
e6b8e2e8be | |||
7c5b7f77cc | |||
de84547a21 | |||
44676756ae | |||
b399b0f182 | |||
1152191f48 | |||
af1b07ad44 | |||
b8113fff1e | |||
ff6948cf2d | |||
fd25e85d59 | |||
c07cd72e85 | |||
e2c101206c | |||
92276b5769 | |||
a2133f61a8 | |||
199adbbcf0 | |||
dc316fd7b4 | |||
025183602f | |||
db4619a9d9 | |||
451e527b7c | |||
54dd3a00df | |||
03c5dab79d | |||
1fdee861e8 | |||
c12bf991b3 | |||
78a097585d | |||
39132327cc | |||
dc32318cec | |||
592f74124c | |||
e5e63cc5ac | |||
f40e0f786d | |||
ebd9f1471b | |||
d76547ead4 | |||
4600772e05 | |||
ed597423cd | |||
f20ca06f85 | |||
a636d3f394 | |||
043df18daa | |||
96996bf18e | |||
f350137a14 | |||
b7a6f3ec75 | |||
6c34672549 | |||
e779a07bce | |||
9a36e8ba3b | |||
c968bacb01 | |||
25199dfb43 | |||
48fed4e6fb | |||
fc253237c9 | |||
589948c7f4 | |||
7e69690605 | |||
95f498ba9b | |||
fd07ae5225 | |||
8acd94fc89 | |||
1436480eab | |||
448d176c24 | |||
fd269453a4 | |||
b3b380964c | |||
6e9025ebf2 | |||
3922691b3c | |||
0545b77cf4 | |||
6b3f39fa1a | |||
3114ab87dc | |||
00bc99cc7b | |||
540b3ae2f4 | |||
dbfe4140e1 | |||
d3675ec254 | |||
ded2483fc0 | |||
e62ea388e0 | |||
f20356e9be | |||
d282a2d846 | |||
4641ac46e7 | |||
ba9268a09e | |||
fb9902c536 | |||
5318ba6c6e | |||
fd5ebef488 | |||
d9e4f39ddc | |||
435b9d8973 | |||
0ea70ba656 | |||
92a07b87d2 | |||
c3c82282ba | |||
adc15c24ef | |||
dddf9a9396 | |||
9ca6860ffa | |||
f7dd388954 | |||
6012839f0e | |||
8e9cbab053 | |||
aaf375a57b | |||
3cce985f03 | |||
c59df6ec20 | |||
5c3f41f64d | |||
cf3523f49f | |||
db794752cb | |||
bceaebe856 | |||
3916de2921 | |||
9e0f8c1a97 | |||
0cbc56b82e | |||
b95608f68a | |||
b6e5dbd06c | |||
914f19be86 | |||
f09bcf3fcf | |||
d0b18dec8e | |||
75d486b124 | |||
4914609485 | |||
75bd66326a | |||
8f904f75bb | |||
549c598f51 | |||
ed68d604d6 | |||
f83752f43b | |||
86c22636eb | |||
30d20a453b | |||
fe29d8a23f | |||
694d088160 | |||
6aabbffc62 | |||
7b59bc8d12 | |||
79d0fb0b52 | |||
edf56d34f8 | |||
623329fb33 | |||
9f0074eef9 | |||
6733253826 | |||
f117805129 | |||
c75b1581d2 | |||
109e118aba | |||
201b77d5b6 | |||
a5ca08f33d | |||
86210c4513 | |||
988a3e4446 | |||
0f5cd22bb7 | |||
2f5bed36b3 | |||
5b6534bb28 | |||
e31e5b2477 | |||
07d5fafe2e | |||
e08da659e5 | |||
8a4979f44c | |||
e67464325f | |||
94c9b0d23b | |||
e9ec310d8a | |||
c78d1e3c39 | |||
e94319145f | |||
3f3b01b5f6 | |||
19a2791c65 | |||
4e8ccf0ef3 | |||
f1a7d5ecf7 | |||
8b05abb80d | |||
48c9349ce9 | |||
117d848466 | |||
9a2df072cc | |||
99c62aab36 | |||
224278e07a | |||
74b69e191e | |||
8cda8a727c | |||
a3c0c7c96f | |||
4403e4ed62 | |||
9b209823f6 | |||
b2cb125bd4 | |||
5e8f767642 | |||
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 |
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
|
9
.idea/kotlinc.xml
generated
Normal file
9
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Kotlin2JvmCompilerArguments">
|
||||||
|
<option name="jvmTarget" value="11" />
|
||||||
|
</component>
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="version" value="1.8.20-release-327" />
|
||||||
|
</component>
|
||||||
|
</project>
|
7
.idea/libraries/antlr_antlr4.xml
generated
7
.idea/libraries/antlr_antlr4.xml
generated
@ -1,17 +1,16 @@
|
|||||||
<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.11.1">
|
<properties maven-id="org.antlr:antlr4:4.12.0">
|
||||||
<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.11.1/antlr4-4.11.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.12.0/antlr4-4.12.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.11.1/antlr4-runtime-4.11.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.12.0/antlr4-runtime-4.12.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/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.4/ST4-4.3.4.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.1.4/javax.json-1.1.4.jar!/" />
|
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
30
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
30
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
@ -1,23 +1,23 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="github.hypfvieh.dbus.java" type="repository">
|
<library name="github.hypfvieh.dbus.java" type="repository">
|
||||||
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
|
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.2" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.1/dbus-java-3.3.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.2/dbus-java-3.3.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.6/jnr-unixsocket-0.38.6.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.17/jnr-unixsocket-0.38.17.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.2/jnr-ffi-2.2.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.11/jnr-ffi-2.2.11.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9-native.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.1/asm-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.2/asm-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.2/asm-analysis-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.2/asm-util-9.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.3/jnr-constants-0.10.3.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.4/jnr-enxio-0.32.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.13/jnr-enxio-0.32.13.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.5/jnr-posix-3.1.5.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.15/jnr-posix-3.1.15.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
18
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
18
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
@ -1,21 +1,21 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.3.2" />
|
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.5.5" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.3.2/kotest-assertions-core-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.5.5/kotest-assertions-core-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.4/kotlinx-coroutines-jdk8-1.6.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.5.5/kotest-common-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.5.5/kotest-assertions-api-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
22
.idea/libraries/io_kotest_property_jvm.xml
generated
22
.idea/libraries/io_kotest_property_jvm.xml
generated
@ -1,22 +1,22 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="io.kotest.property.jvm" type="repository">
|
<library name="io.kotest.property.jvm" type="repository">
|
||||||
<properties maven-id="io.kotest:kotest-property-jvm:5.3.2" />
|
<properties maven-id="io.kotest:kotest-property-jvm:5.5.5" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.3.2/kotest-property-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.5.5/kotest-property-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/curious-odd-man/rgxgen/1.3/rgxgen-1.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.5.5/kotest-common-jvm-5.5.5.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.5.5/kotest-assertions-api-jvm-5.5.5.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.4/kotlinx-coroutines-jdk8-1.6.4.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/curious-odd-man/rgxgen/1.4/rgxgen-1.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
64
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
64
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
@ -1,46 +1,50 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.3.2" />
|
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.5.5" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.3.2/kotest-runner-junit5-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.5.5/kotest-runner-junit5-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.3.2/kotest-framework-api-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.5.5/kotest-framework-api-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.6.4/kotlinx-coroutines-test-jvm-1.6.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.6.1/kotlinx-coroutines-test-jvm-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.3.2/kotest-framework-engine-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.5.5/kotest-common-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.146/classgraph-4.8.146.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.5.5/kotest-framework-engine-jvm-5.5.5.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.154/classgraph-4.8.154.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.6.1/kotlinx-coroutines-debug-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.6.4/kotlinx-coroutines-debug-1.6.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.3.2/kotest-framework-discovery-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.3.2/kotest-assertions-core-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.5.5/kotest-framework-discovery-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.5.5/kotest-assertions-core-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.3.2/kotest-extensions-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.4/kotlinx-coroutines-jdk8-1.6.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.5.5/kotest-assertions-api-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.12.3/mockk-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.5.5/kotest-extensions-jvm-5.5.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.12.3/mockk-common-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-jvm/1.13.1/mockk-jvm-1.13.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.12.3/mockk-dsl-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.13.1/mockk-dsl-jvm-1.13.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.12.3/mockk-dsl-jvm-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.13.1/mockk-agent-jvm-1.13.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.12.3/mockk-agent-jvm-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.2/objenesis-3.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.12.3/mockk-agent-api-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api-jvm/1.13.1/mockk-agent-api-jvm-1.13.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.12.3/mockk-agent-common-1.12.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-core-jvm/1.13.1/mockk-core-jvm-1.13.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.1/objenesis-3.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.12.6/byte-buddy-1.12.6.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.13.2/junit-4.13.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.12.6/byte-buddy-agent-1.12.6.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.3.2/kotest-framework-concurrency-jvm-5.3.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.2/junit-jupiter-5.8.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.2/junit-jupiter-params-5.8.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.2/junit-jupiter-engine-5.8.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.6.4/kotlinx-coroutines-core-1.6.4.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.5.5/kotest-framework-concurrency-jvm-5.5.5.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.7.2/junit-platform-suite-api-1.7.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.7.2/junit-platform-suite-api-1.7.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.7.2/junit-platform-launcher-1.7.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.7.2/junit-platform-launcher-1.7.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
|
4
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
4
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
@ -1,8 +1,8 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.4" />
|
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.5" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.4/kotlinx-cli-jvm-0.3.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
6
.idea/libraries/slf4j_simple.xml
generated
6
.idea/libraries/slf4j_simple.xml
generated
@ -1,9 +1,9 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="slf4j.simple" type="repository">
|
<library name="slf4j.simple" type="repository">
|
||||||
<properties maven-id="org.slf4j:slf4j-simple:1.7.36" />
|
<properties maven-id="org.slf4j:slf4j-simple:2.0.7" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.36/slf4j-simple-1.7.36.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.7/slf4j-simple-2.0.7.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
19
.idea/libraries/takes.xml
generated
19
.idea/libraries/takes.xml
generated
@ -1,15 +1,16 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="takes" type="repository">
|
<library name="takes" type="repository">
|
||||||
<properties maven-id="org.takes:takes:1.20" />
|
<properties maven-id="org.takes:takes:1.24.4" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.20/takes-1.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.24.4/takes-1.24.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.50/cactoos-0.50.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.54.0/cactoos-0.54.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/javax/xml/bind/jaxb-api/2.3.0/jaxb-api-2.3.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/javax/xml/bind/jaxb-api/2.4.0-b180830.0359/jaxb-api-2.4.0-b180830.0359.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-text/1.4/commons-text-1.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-core/4.0.0/jaxb-core-4.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.7/commons-lang3-3.7.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/jakarta/xml/bind/jakarta.xml.bind-api/4.0.0/jakarta.xml.bind-api-4.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-core/2.3.0/jaxb-core-2.3.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/jakarta/activation/jakarta.activation-api/2.1.0/jakarta.activation-api-2.1.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-impl/2.3.0/jaxb-impl-2.3.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/angus/angus-activation/1.0.0/angus-activation-1.0.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-impl/4.0.0/jaxb-impl-4.0.0.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>
|
@ -7,13 +7,9 @@ version: 2
|
|||||||
|
|
||||||
# Set the version of Python and other tools you might need
|
# Set the version of Python and other tools you might need
|
||||||
build:
|
build:
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.9"
|
python: "3.11"
|
||||||
# You can also specify other tool versions:
|
|
||||||
# nodejs: "16"
|
|
||||||
# rust: "1.55"
|
|
||||||
# golang: "1.17"
|
|
||||||
|
|
||||||
# Build documentation in the docs/ directory with Sphinx
|
# Build documentation in the docs/ directory with Sphinx
|
||||||
sphinx:
|
sphinx:
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
#### Just a few remarks upfront:
|
|
||||||
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
|
|
||||||
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
|
|
||||||
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
|
|
||||||
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
|
|
||||||
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
|
|
||||||
|
|
||||||
#### Problems with `compilerAst`:
|
|
||||||
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
|
|
||||||
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
|
|
||||||
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
|
|
||||||
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
|
|
||||||
|
|
||||||
#### Steps to take, in conceptual (!) order:
|
|
||||||
|
|
||||||
(note: all these steps have been implemented, rejected or otherwise solved now.)
|
|
||||||
|
|
||||||
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
|
|
||||||
- from the local file system (use case: user programs)
|
|
||||||
- from resources (prog8lib)
|
|
||||||
- from plain strings (for testing)
|
|
||||||
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
|
|
||||||
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
|
|
||||||
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
|
|
||||||
- plus, optionally, method's for registering/unregistering a listener with the parser
|
|
||||||
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
|
|
||||||
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
|
|
||||||
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
|
|
||||||
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
|
|
||||||
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
|
|
||||||
6. move `IStringEncoding` to module `compiler`
|
|
||||||
7. same with `ModuleImporter`, then rewrite that (addressing #46)
|
|
||||||
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
|
|
||||||
9. re-think `IStringEncoding` to address #38
|
|
||||||
|
|
@ -76,7 +76,9 @@ IntelliJ IDEA with the Kotlin plugin).
|
|||||||
|
|
||||||
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
||||||
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 a recent emulator version (R42 or newer) for the CommanderX16, such as [x16emu](https://cx16forum.com/forum/viewforum.php?f=30)
|
||||||
|
(preferred, this is the official emulator. If required, source code is [here](https://github.com/X16Community/x16-emulator/)).
|
||||||
|
There is also [Box16](https://github.com/indigodarkwolf/box16) which has powerful debugging features.
|
||||||
|
|
||||||
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
|
**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.
|
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package prog8.code
|
package prog8.code
|
||||||
|
|
||||||
|
import prog8.code.ast.PtNode
|
||||||
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
@ -7,19 +9,32 @@ import prog8.code.core.*
|
|||||||
* Tree structure containing all symbol definitions in the program
|
* Tree structure containing all symbol definitions in the program
|
||||||
* (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(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GLOBAL, astProgram) {
|
||||||
/**
|
/**
|
||||||
* 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>()
|
private var cachedFlat: Map<String, StNode>? = null
|
||||||
fun flatten(node: StNode) {
|
|
||||||
result[node.scopedName] = node
|
val flat: Map<String, StNode> get() {
|
||||||
node.children.values.forEach { flatten(it) }
|
if(cachedFlat!=null)
|
||||||
|
return cachedFlat!!
|
||||||
|
|
||||||
|
val result = mutableMapOf<String, StNode>()
|
||||||
|
fun collect(node: StNode) {
|
||||||
|
for(child in node.children) {
|
||||||
|
result[child.value.scopedName] = child.value
|
||||||
|
collect(child.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
children.values.forEach { flatten(it) }
|
collect(this)
|
||||||
result
|
cachedFlat = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetCachedFlat() {
|
||||||
|
cachedFlat = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val allVariables: Collection<StStaticVariable> by lazy {
|
val allVariables: Collection<StStaticVariable> by lazy {
|
||||||
@ -51,10 +66,20 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val allMemorySlabs: Collection<StMemorySlab> by lazy {
|
val allMemorySlabs: Collection<StMemorySlab> by lazy {
|
||||||
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
|
val vars = mutableListOf<StMemorySlab>()
|
||||||
|
fun collect(node: StNode) {
|
||||||
|
for(child in node.children) {
|
||||||
|
if(child.value.type== StNodeType.MEMORYSLAB)
|
||||||
|
vars.add(child.value as StMemorySlab)
|
||||||
|
else
|
||||||
|
collect(child.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collect(this)
|
||||||
|
vars
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>) = flat[scopedName]
|
override fun lookup(scopedName: String) = flat[scopedName]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -75,44 +100,24 @@ enum class StNodeType {
|
|||||||
|
|
||||||
open class StNode(val name: String,
|
open class StNode(val name: String,
|
||||||
val type: StNodeType,
|
val type: StNodeType,
|
||||||
val position: Position,
|
val astNode: PtNode,
|
||||||
val children: MutableMap<String, StNode> = mutableMapOf()
|
val children: MutableMap<String, StNode> = mutableMapOf()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
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 +143,57 @@ open class StNode(val name: String,
|
|||||||
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?,
|
||||||
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
|
||||||
val zpwish: ZeropageWish,
|
val zpwish: ZeropageWish, // used in the variable allocator
|
||||||
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
|
||||||
|
|
||||||
|
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
|
||||||
|
|
||||||
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(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||||
}
|
}
|
||||||
if(onetimeInitializationNumericValue!=null)
|
if(onetimeInitializationNumericValue!=null) {
|
||||||
require(dt in NumericDatatypes)
|
require(dt in NumericDatatypes)
|
||||||
if(onetimeInitializationArrayValue!=null)
|
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
|
||||||
|
}
|
||||||
|
if(onetimeInitializationArrayValue!=null) {
|
||||||
require(dt in ArrayDatatypes)
|
require(dt in ArrayDatatypes)
|
||||||
|
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
|
||||||
|
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
|
||||||
|
}
|
||||||
|
}
|
||||||
if(onetimeInitializationStringValue!=null) {
|
if(onetimeInitializationStringValue!=null) {
|
||||||
require(dt == DataType.STR)
|
require(dt == DataType.STR)
|
||||||
require(length == onetimeInitializationStringValue.first.length+1)
|
require(length == onetimeInitializationStringValue.first.length+1)
|
||||||
@ -177,8 +202,8 @@ class StStaticVariable(name: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
|
class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) :
|
||||||
StNode(name, StNodeType.CONSTANT, position) {
|
StNode(name, StNodeType.CONSTANT, astNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -186,36 +211,41 @@ class StMemVar(name: String,
|
|||||||
val dt: DataType,
|
val dt: DataType,
|
||||||
val address: UInt,
|
val address: UInt,
|
||||||
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) :
|
astNode: PtNode) :
|
||||||
StNode(name, StNodeType.MEMVAR, position) {
|
StNode(name, StNodeType.MEMVAR, astNode) {
|
||||||
|
|
||||||
|
init{
|
||||||
|
if(dt in ArrayDatatypes || dt == DataType.STR)
|
||||||
|
require(length!=null) { "memory mapped array or string must have known length" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StMemorySlab(
|
class StMemorySlab(
|
||||||
name: String,
|
name: String,
|
||||||
val size: UInt,
|
val size: UInt,
|
||||||
val align: UInt,
|
val align: UInt,
|
||||||
position: Position
|
astNode: PtNode
|
||||||
):
|
):
|
||||||
StNode(name, StNodeType.MEMORYSLAB, position) {
|
StNode(name, StNodeType.MEMORYSLAB, astNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
|
||||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
StNode(name, StNodeType.SUBROUTINE, astNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StRomSub(name: String,
|
class StRomSub(name: String,
|
||||||
val address: UInt,
|
val address: UInt?, // null in case of asmsub, specified in case of romsub
|
||||||
val parameters: List<StRomSubParameter>,
|
val parameters: List<StRomSubParameter>,
|
||||||
val returns: List<RegisterOrStatusflag>,
|
val returns: List<StRomSubParameter>,
|
||||||
position: Position) :
|
astNode: PtNode) :
|
||||||
StNode(name, StNodeType.ROMSUB, position) {
|
StNode(name, StNodeType.ROMSUB, astNode)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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>
|
||||||
|
202
codeCore/src/prog8/code/SymbolTableMaker.kt
Normal file
202
codeCore/src/prog8/code/SymbolTableMaker.kt
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
package prog8.code
|
||||||
|
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
|
||||||
|
fun make(): SymbolTable {
|
||||||
|
val st = SymbolTable(program)
|
||||||
|
|
||||||
|
BuiltinFunctions.forEach {
|
||||||
|
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
|
||||||
|
}
|
||||||
|
|
||||||
|
val scopestack = Stack<StNode>()
|
||||||
|
scopestack.push(st)
|
||||||
|
program.children.forEach {
|
||||||
|
addToSt(it, scopestack)
|
||||||
|
}
|
||||||
|
require(scopestack.size==1)
|
||||||
|
|
||||||
|
if(options.compTarget.name != VMTarget.NAME) {
|
||||||
|
listOf(
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_B1", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_B1, null, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_REG", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_REG, null, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_W1", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W1, null, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ZP_SCRATCH_W2", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W2, null, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ESTACK_LO", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_LO, 128u, Position.DUMMY),
|
||||||
|
PtMemMapped("P8ESTACK_HI", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_HI, 128u, Position.DUMMY)
|
||||||
|
).forEach {
|
||||||
|
it.parent = program
|
||||||
|
st.add(StMemVar(it.name, it.type, it.address, it.arraySize?.toInt(), it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
|
||||||
|
val stNode = when(node) {
|
||||||
|
is PtAsmSub -> {
|
||||||
|
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
|
||||||
|
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
|
||||||
|
StRomSub(node.name, node.address, parameters, returns, node)
|
||||||
|
}
|
||||||
|
is PtBlock -> {
|
||||||
|
StNode(node.name, StNodeType.BLOCK, node)
|
||||||
|
}
|
||||||
|
is PtConstant -> {
|
||||||
|
StConstant(node.name, node.type, node.value, node)
|
||||||
|
}
|
||||||
|
is PtLabel -> {
|
||||||
|
StNode(node.name, StNodeType.LABEL, node)
|
||||||
|
}
|
||||||
|
is PtMemMapped -> {
|
||||||
|
StMemVar(node.name, node.type, node.address, node.arraySize?.toInt(), node)
|
||||||
|
}
|
||||||
|
is PtSub -> {
|
||||||
|
val params = node.parameters.map {StSubroutineParameter(it.name, it.type) }
|
||||||
|
StSub(node.name, params, node.returntype, node)
|
||||||
|
}
|
||||||
|
is PtVariable -> {
|
||||||
|
val initialNumeric: Double?
|
||||||
|
val initialString: StString?
|
||||||
|
val initialArray: StArray?
|
||||||
|
val numElements: Int?
|
||||||
|
val value = node.value
|
||||||
|
if(value!=null) {
|
||||||
|
val number = (value as? PtNumber)?.number
|
||||||
|
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
|
||||||
|
when (value) {
|
||||||
|
is PtString -> {
|
||||||
|
initialString = StString(value.value, value.encoding)
|
||||||
|
initialArray = null
|
||||||
|
numElements = value.value.length + 1 // include the terminating 0-byte
|
||||||
|
}
|
||||||
|
is PtArray -> {
|
||||||
|
val array = makeInitialArray(value)
|
||||||
|
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
|
||||||
|
initialString = null
|
||||||
|
numElements = array.size
|
||||||
|
require(node.arraySize?.toInt()==numElements)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
initialString = null
|
||||||
|
initialArray = null
|
||||||
|
numElements = node.arraySize?.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
initialNumeric = null
|
||||||
|
initialArray = null
|
||||||
|
initialString = null
|
||||||
|
numElements = node.arraySize?.toInt()
|
||||||
|
}
|
||||||
|
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
|
||||||
|
}
|
||||||
|
is PtBuiltinFunctionCall -> {
|
||||||
|
if(node.name=="memory") {
|
||||||
|
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
||||||
|
require(node.name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||||
|
val slabname = (node.args[0] as PtString).value
|
||||||
|
val size = (node.args[1] as PtNumber).number.toUInt()
|
||||||
|
val align = (node.args[2] as PtNumber).number.toUInt()
|
||||||
|
// don't add memory slabs in nested scope, just put them in the top level of the ST
|
||||||
|
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
else -> null // node is not present in the ST
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stNode!=null) {
|
||||||
|
scope.peek().add(stNode)
|
||||||
|
scope.push(stNode)
|
||||||
|
}
|
||||||
|
node.children.forEach {
|
||||||
|
addToSt(it, scope)
|
||||||
|
}
|
||||||
|
if(stNode!=null)
|
||||||
|
scope.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||||
|
return value.children.map {
|
||||||
|
when(it) {
|
||||||
|
is PtAddressOf -> StArrayElement(null, it.identifier.name)
|
||||||
|
is PtIdentifier -> StArrayElement(null, it.name)
|
||||||
|
is PtNumber -> StArrayElement(it.number, null)
|
||||||
|
else -> throw AssemblyError("invalid array element $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// override fun visit(decl: VarDecl) {
|
||||||
|
// val node =
|
||||||
|
// when(decl.type) {
|
||||||
|
// VarDeclType.VAR -> {
|
||||||
|
// 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 initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||||
|
// val initialArrayLit = decl.value as? ArrayLiteral
|
||||||
|
// val initialArray = makeInitialArray(initialArrayLit)
|
||||||
|
// if(decl.isArray && decl.datatype !in ArrayDatatypes)
|
||||||
|
// throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
|
||||||
|
// val numElements =
|
||||||
|
// if(decl.isArray)
|
||||||
|
// decl.arraysize!!.constIndex()
|
||||||
|
// else if(initialStringLit!=null)
|
||||||
|
// initialStringLit.value.length+1 // include the terminating 0-byte
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// val bss = if(decl.datatype==DataType.STR)
|
||||||
|
// false
|
||||||
|
// else if(decl.isArray)
|
||||||
|
// initialArray.isNullOrEmpty()
|
||||||
|
// else
|
||||||
|
// initialNumeric == null
|
||||||
|
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||||
|
// StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, astNode, decl.position)
|
||||||
|
// }
|
||||||
|
// VarDeclType.CONST -> {
|
||||||
|
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||||
|
// StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, astNode, decl.position)
|
||||||
|
// }
|
||||||
|
// VarDeclType.MEMORY -> {
|
||||||
|
// val numElements =
|
||||||
|
// if(decl.datatype in ArrayDatatypes)
|
||||||
|
// decl.arraysize!!.constIndex()
|
||||||
|
// else null
|
||||||
|
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||||
|
// StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, astNode, decl.position)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// scopestack.peek().add(node)
|
||||||
|
// // st.origAstLinks[decl] = node
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
|
||||||
|
// if(arrayLit==null)
|
||||||
|
// return null
|
||||||
|
// return arrayLit.value.map {
|
||||||
|
// when(it){
|
||||||
|
// is AddressOf -> {
|
||||||
|
// 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)
|
||||||
|
// else -> throw FatalAstException("weird element dt in array literal")
|
||||||
|
// }
|
||||||
|
// }.toList()
|
||||||
|
// }
|
||||||
|
//
|
@ -1,6 +1,9 @@
|
|||||||
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 prog8.code.core.SourceCode
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
// New simplified AST for the code generator.
|
// New simplified AST for the code generator.
|
||||||
@ -11,16 +14,6 @@ sealed class PtNode(val position: Position) {
|
|||||||
val children = mutableListOf<PtNode>()
|
val children = mutableListOf<PtNode>()
|
||||||
lateinit var parent: PtNode
|
lateinit var parent: PtNode
|
||||||
|
|
||||||
fun printIndented(indent: Int) {
|
|
||||||
print(" ".repeat(indent))
|
|
||||||
print("${this.javaClass.simpleName} ")
|
|
||||||
printProperties()
|
|
||||||
println()
|
|
||||||
children.forEach { it.printIndented(indent+1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun printProperties()
|
|
||||||
|
|
||||||
fun add(child: PtNode) {
|
fun add(child: PtNode) {
|
||||||
children.add(child)
|
children.add(child)
|
||||||
child.parent = this
|
child.parent = this
|
||||||
@ -34,23 +27,24 @@ sealed class PtNode(val position: Position) {
|
|||||||
fun definingBlock() = findParentNode<PtBlock>(this)
|
fun definingBlock() = findParentNode<PtBlock>(this)
|
||||||
fun definingSub() = findParentNode<PtSub>(this)
|
fun definingSub() = findParentNode<PtSub>(this)
|
||||||
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
|
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
|
||||||
|
fun definingISub() = findParentNode<IPtSubroutine>(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtNodeGroup : PtNode(Position.DUMMY) {
|
class PtNodeGroup : PtNode(Position.DUMMY)
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sealed 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,10 +55,6 @@ class PtProgram(
|
|||||||
val memsizer: IMemSizer,
|
val memsizer: IMemSizer,
|
||||||
val encoding: IStringEncoding
|
val encoding: IStringEncoding
|
||||||
) : PtNode(Position.DUMMY) {
|
) : PtNode(Position.DUMMY) {
|
||||||
fun print() = printIndented(0)
|
|
||||||
override fun printProperties() {
|
|
||||||
print("'$name'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun allModuleDirectives(): Sequence<PtDirective> =
|
// fun allModuleDirectives(): Sequence<PtDirective> =
|
||||||
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
|
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
|
||||||
@ -73,7 +63,7 @@ class PtProgram(
|
|||||||
children.asSequence().filterIsInstance<PtBlock>()
|
children.asSequence().filterIsInstance<PtBlock>()
|
||||||
|
|
||||||
fun entrypoint(): PtSub? =
|
fun entrypoint(): PtSub? =
|
||||||
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && it.name == "start" } as PtSub?
|
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start") } as PtSub?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -82,12 +72,9 @@ class PtBlock(name: String,
|
|||||||
val library: Boolean,
|
val library: Boolean,
|
||||||
val forceOutput: Boolean,
|
val forceOutput: Boolean,
|
||||||
val alignment: BlockAlignment,
|
val alignment: BlockAlignment,
|
||||||
|
val source: SourceCode, // taken from the module the block is defined in.
|
||||||
position: Position
|
position: Position
|
||||||
) : PtNamedNode(name, position) {
|
) : PtNamedNode(name, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$name addr=$address library=$library forceOutput=$forceOutput alignment=$alignment")
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class BlockAlignment {
|
enum class BlockAlignment {
|
||||||
NONE,
|
NONE,
|
||||||
WORD,
|
WORD,
|
||||||
@ -97,8 +84,6 @@ class PtBlock(name: String,
|
|||||||
|
|
||||||
|
|
||||||
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
||||||
override fun printProperties() {}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
||||||
@ -106,34 +91,16 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
|
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
|
||||||
override fun printProperties() {
|
|
||||||
print(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtBreakpoint(position: Position): PtNode(position) {
|
class PtBreakpoint(position: Position): PtNode(position)
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) {
|
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
|
||||||
override fun printProperties() {
|
|
||||||
print("filename=$file offset=$offset length=$length")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtNop(position: Position): PtNode(position) {
|
class PtNop(position: Position): PtNode(position)
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -2,8 +2,10 @@ package prog8.code.ast
|
|||||||
|
|
||||||
import prog8.code.core.DataType
|
import prog8.code.core.DataType
|
||||||
import prog8.code.core.Encoding
|
import prog8.code.core.Encoding
|
||||||
|
import prog8.code.core.NumericDatatypes
|
||||||
import prog8.code.core.Position
|
import prog8.code.core.Position
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
|
|
||||||
@ -23,17 +25,13 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(type)
|
|
||||||
}
|
|
||||||
|
|
||||||
infix fun isSameAs(other: PtExpression): Boolean {
|
infix fun isSameAs(other: PtExpression): Boolean {
|
||||||
return when(this) {
|
return when(this) {
|
||||||
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
||||||
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
|
||||||
@ -43,6 +41,69 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infix fun isSameAs(target: PtAssignTarget): Boolean {
|
||||||
|
return when {
|
||||||
|
target.memory != null && this is PtMemoryByte-> {
|
||||||
|
target.memory!!.address isSameAs this.address
|
||||||
|
}
|
||||||
|
target.identifier != null && this is PtIdentifier -> {
|
||||||
|
this.name == target.identifier!!.name
|
||||||
|
}
|
||||||
|
target.array != null && this is PtArrayIndexer -> {
|
||||||
|
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt()
|
||||||
|
|
||||||
|
fun isSimple(): Boolean {
|
||||||
|
return when(this) {
|
||||||
|
is PtAddressOf -> true
|
||||||
|
is PtArray -> true
|
||||||
|
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
|
||||||
|
is PtBinaryExpression -> false
|
||||||
|
is PtBuiltinFunctionCall -> name in arrayOf("msb", "lsb", "peek", "peekw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||||
|
is PtContainmentCheck -> false
|
||||||
|
is PtFunctionCall -> false
|
||||||
|
is PtIdentifier -> true
|
||||||
|
is PtMachineRegister -> true
|
||||||
|
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
|
||||||
|
is PtNumber -> true
|
||||||
|
is PtPrefix -> value.isSimple()
|
||||||
|
is PtRange -> true
|
||||||
|
is PtString -> true
|
||||||
|
is PtTypeCast -> value.isSimple()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun clone(): PtExpression {
|
||||||
|
fun withClonedChildrenFrom(orig: PtExpression, clone: PtExpression): PtExpression {
|
||||||
|
orig.children.forEach { clone.add((it as PtExpression).clone()) }
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
when(this) {
|
||||||
|
is PtAddressOf -> return withClonedChildrenFrom(this, PtAddressOf(position))
|
||||||
|
is PtArray -> return withClonedChildrenFrom(this, PtArray(type, position))
|
||||||
|
is PtArrayIndexer -> return withClonedChildrenFrom(this, PtArrayIndexer(type, position))
|
||||||
|
is PtBinaryExpression -> return withClonedChildrenFrom(this, PtBinaryExpression(operator, type, position))
|
||||||
|
is PtBuiltinFunctionCall -> return withClonedChildrenFrom(this, PtBuiltinFunctionCall(name, void, hasNoSideEffects, type, position))
|
||||||
|
is PtContainmentCheck -> return withClonedChildrenFrom(this, PtContainmentCheck(position))
|
||||||
|
is PtFunctionCall -> return withClonedChildrenFrom(this, PtFunctionCall(name, void, type, position))
|
||||||
|
is PtIdentifier -> return withClonedChildrenFrom(this, PtIdentifier(name, type, position))
|
||||||
|
is PtMachineRegister -> return withClonedChildrenFrom(this, PtMachineRegister(register, type, position))
|
||||||
|
is PtMemoryByte -> return withClonedChildrenFrom(this, PtMemoryByte(position))
|
||||||
|
is PtNumber -> return withClonedChildrenFrom(this, PtNumber(type, number, position))
|
||||||
|
is PtPrefix -> return withClonedChildrenFrom(this, PtPrefix(operator, type, position))
|
||||||
|
is PtRange -> return withClonedChildrenFrom(this, PtRange(type, position))
|
||||||
|
is PtString -> return withClonedChildrenFrom(this, PtString(value, encoding, position))
|
||||||
|
is PtTypeCast -> return withClonedChildrenFrom(this, PtTypeCast(type, position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||||
@ -51,11 +112,15 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtArrayIndexer(type: DataType, position: Position): PtExpression(type, position) {
|
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
|
||||||
val variable: PtIdentifier
|
val variable: PtIdentifier
|
||||||
get() = children[0] as PtIdentifier
|
get() = children[0] as PtIdentifier
|
||||||
val index: PtExpression
|
val index: PtExpression
|
||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(elementType in NumericDatatypes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -66,6 +131,9 @@ class PtArray(type: DataType, position: Position): PtExpression(type, position)
|
|||||||
return false
|
return false
|
||||||
return type==other.type && children == other.children
|
return type==other.type && children == other.children
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val size: Int
|
||||||
|
get() = children.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -81,9 +149,6 @@ class PtBuiltinFunctionCall(val name: String,
|
|||||||
|
|
||||||
val args: List<PtExpression>
|
val args: List<PtExpression>
|
||||||
get() = children.map { it as PtExpression }
|
get() = children.map { it as PtExpression }
|
||||||
override fun printProperties() {
|
|
||||||
print("$name void=$void noSideFx=$hasNoSideEffects")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,10 +158,6 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
|
|||||||
get() = children[0] as PtExpression
|
get() = children[0] as PtExpression
|
||||||
val right: PtExpression
|
val right: PtExpression
|
||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print("$operator -> $type")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -108,7 +169,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) {
|
||||||
@ -119,28 +180,31 @@ 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() {
|
|
||||||
print("${functionName.joinToString(".")} 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 toString(): String {
|
||||||
print("$ref --> $targetName $type")
|
return "[PtIdentifier:$name $type $position]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun copy() = PtIdentifier(name, type, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
|
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
|
||||||
val address: PtExpression
|
val address: PtExpression
|
||||||
get() = children.single() as PtExpression
|
get() = children.single() as PtExpression
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromBoolean(bool: Boolean, position: Position): PtNumber =
|
||||||
|
PtNumber(DataType.UBYTE, if(bool) 1.0 else 0.0, position)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(type==DataType.BOOL)
|
if(type==DataType.BOOL)
|
||||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||||
@ -151,10 +215,6 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print("$number ($type)")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(type, number)
|
override fun hashCode(): Int = Objects.hash(type, number)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -164,6 +224,8 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
|
|||||||
}
|
}
|
||||||
|
|
||||||
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
|
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
|
||||||
|
|
||||||
|
override fun toString() = "PtNumber:$type:$number"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -175,10 +237,6 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
|
|||||||
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
||||||
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
|
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(operator)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -190,15 +248,36 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
|
|||||||
val step: PtNumber
|
val step: PtNumber
|
||||||
get() = children[2] as PtNumber
|
get() = children[2] as PtNumber
|
||||||
|
|
||||||
override fun printProperties() {}
|
fun toConstantIntegerRange(): IntProgression? {
|
||||||
|
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||||
|
return when {
|
||||||
|
fromVal <= toVal -> when {
|
||||||
|
stepVal <= 0 -> IntRange.EMPTY
|
||||||
|
stepVal == 1 -> fromVal..toVal
|
||||||
|
else -> fromVal..toVal step stepVal
|
||||||
|
}
|
||||||
|
else -> when {
|
||||||
|
stepVal >= 0 -> IntRange.EMPTY
|
||||||
|
stepVal == -1 -> fromVal downTo toVal
|
||||||
|
else -> fromVal downTo toVal step abs(stepVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fromLv = from as? PtNumber
|
||||||
|
val toLv = to as? PtNumber
|
||||||
|
val stepLv = step as? PtNumber
|
||||||
|
if(fromLv==null || toLv==null || stepLv==null)
|
||||||
|
return null
|
||||||
|
val fromVal = fromLv.number.toInt()
|
||||||
|
val toVal = toLv.number.toInt()
|
||||||
|
val stepVal = stepLv.number.toInt()
|
||||||
|
return makeRange(fromVal, toVal, stepVal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
|
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
|
||||||
override fun printProperties() {
|
|
||||||
print("$encoding:\"$value\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(value, encoding)
|
override fun hashCode(): Int = Objects.hash(value, encoding)
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if(other==null || other !is PtString)
|
if(other==null || other !is PtString)
|
||||||
@ -214,12 +293,8 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// special node that isn't created from compiling user code, but used internally
|
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
|
||||||
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position) {
|
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
|
||||||
override fun printProperties() {
|
|
||||||
print("reg=$register $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
|
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
|
||||||
|
163
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
163
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
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, skipLibraries: Boolean, output: (text: String) -> Unit) {
|
||||||
|
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||||
|
fun txt(node: PtNode): String {
|
||||||
|
return when(node) {
|
||||||
|
is PtAssignTarget -> "<target>"
|
||||||
|
is PtAssignment -> "<assign>"
|
||||||
|
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
||||||
|
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 -> {
|
||||||
|
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
|
||||||
|
"$numstr ${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.returns.isEmpty()) "" else (if (node.returns.size == 1) "-> ${node.returns[0].second.name.lowercase()}" else "-> ${node.returns.map { it.second.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 = "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)
|
||||||
|
val library = if(node is PtBlock) node.library else node.definingBlock()?.library==true
|
||||||
|
if(!library || !skipLibraries) {
|
||||||
|
if (txt.isNotEmpty())
|
||||||
|
output(" ".repeat(depth) + txt(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
walkAst(root) { node, depth ->
|
||||||
|
val txt = txt(node)
|
||||||
|
val library = if(node is PtBlock) node.library else node.definingBlock()?.library==true
|
||||||
|
if(!library || !skipLibraries) {
|
||||||
|
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)
|
||||||
|
}
|
@ -3,87 +3,54 @@ package prog8.code.ast
|
|||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
|
sealed interface IPtSubroutine {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
class PtAsmSub(
|
class PtAsmSub(
|
||||||
name: String,
|
name: String,
|
||||||
val address: UInt?,
|
val address: UInt?,
|
||||||
val clobbers: Set<CpuRegister>,
|
val clobbers: Set<CpuRegister>,
|
||||||
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
|
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
|
||||||
val returnTypes: List<DataType>, // TODO join with register as Pairs ?
|
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
|
||||||
val retvalRegisters: List<RegisterOrStatusflag>,
|
|
||||||
val inline: Boolean,
|
val inline: Boolean,
|
||||||
position: Position
|
position: Position
|
||||||
) : PtNamedNode(name, position) {
|
) : PtNamedNode(name, position), IPtSubroutine
|
||||||
override fun printProperties() {
|
|
||||||
print("$name inline=$inline")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtSub(
|
class PtSub(
|
||||||
name: String,
|
name: String,
|
||||||
val parameters: List<PtSubroutineParameter>,
|
val parameters: List<PtSubroutineParameter>,
|
||||||
val returntype: DataType?,
|
val returntype: DataType?,
|
||||||
val inline: Boolean,
|
|
||||||
position: Position
|
position: Position
|
||||||
) : PtNamedNode(name, position) {
|
) : PtNamedNode(name, position), IPtSubroutine {
|
||||||
override fun printProperties() {
|
|
||||||
print(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// params and return value should not be str
|
// params and return value should not be str
|
||||||
if(parameters.any{ it.type !in NumericDatatypes })
|
if(parameters.any{ it.type !in NumericDatatypes })
|
||||||
throw AssemblyError("non-numeric parameter")
|
throw AssemblyError("non-numeric parameter")
|
||||||
if(returntype!=null && returntype !in NumericDatatypes)
|
if(returntype!=null && returntype !in NumericDatatypes)
|
||||||
throw AssemblyError("non-numeric returntype $returntype")
|
throw AssemblyError("non-numeric returntype $returntype")
|
||||||
|
parameters.forEach { it.parent=this }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtSubroutineParameter(val name: String, val type: DataType, position: Position): PtNode(position) {
|
class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position)
|
||||||
override fun printProperties() {
|
|
||||||
print("$type $name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtAssignment(position: Position) : PtNode(position) {
|
sealed interface IPtAssignment {
|
||||||
|
val children: MutableList<PtNode>
|
||||||
val target: PtAssignTarget
|
val target: PtAssignTarget
|
||||||
get() = children[0] as PtAssignTarget
|
get() = children[0] as PtAssignTarget
|
||||||
val value: PtExpression
|
val value: PtExpression
|
||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
|
|
||||||
override fun printProperties() { }
|
|
||||||
|
|
||||||
val isInplaceAssign: Boolean by lazy {
|
|
||||||
val target = target.children.single() as PtExpression
|
|
||||||
when(val source = value) {
|
|
||||||
is PtArrayIndexer -> {
|
|
||||||
if(target is PtArrayIndexer && source.type==target.type) {
|
|
||||||
if(target.variable isSameAs source.variable) {
|
|
||||||
target.index isSameAs source.index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName
|
|
||||||
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
|
|
||||||
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
|
|
||||||
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
|
|
||||||
is PtAddressOf -> target is PtAddressOf && target.identifier isSameAs source.identifier
|
|
||||||
is PtPrefix -> {
|
|
||||||
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
|
|
||||||
||
|
|
||||||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName)
|
|
||||||
}
|
|
||||||
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
|
|
||||||
is PtBinaryExpression ->
|
|
||||||
target isSameAs source.left
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PtAssignment(position: Position) : PtNode(position), IPtAssignment
|
||||||
|
|
||||||
|
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment
|
||||||
|
|
||||||
|
|
||||||
class PtAssignTarget(position: Position) : PtNode(position) {
|
class PtAssignTarget(position: Position) : PtNode(position) {
|
||||||
val identifier: PtIdentifier?
|
val identifier: PtIdentifier?
|
||||||
get() = children.single() as? PtIdentifier
|
get() = children.single() as? PtIdentifier
|
||||||
@ -102,7 +69,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {}
|
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -111,10 +78,6 @@ class PtConditionalBranch(val condition: BranchCondition, position: Position) :
|
|||||||
get() = children[0] as PtNodeGroup
|
get() = children[0] as PtNodeGroup
|
||||||
val falseScope: PtNodeGroup
|
val falseScope: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(condition)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -125,20 +88,16 @@ class PtForLoop(position: Position) : PtNode(position) {
|
|||||||
get() = children[1] as PtExpression
|
get() = children[1] as PtExpression
|
||||||
val statements: PtNodeGroup
|
val statements: PtNodeGroup
|
||||||
get() = children[2] as PtNodeGroup
|
get() = children[2] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtIfElse(position: Position) : PtNode(position) {
|
class PtIfElse(position: Position) : PtNode(position) {
|
||||||
val condition: PtBinaryExpression
|
val condition: PtExpression
|
||||||
get() = children[0] as PtBinaryExpression
|
get() = children[0] as PtExpression
|
||||||
val ifScope: PtNodeGroup
|
val ifScope: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
val elseScope: PtNodeGroup
|
val elseScope: PtNodeGroup
|
||||||
get() = children[2] as PtNodeGroup
|
get() = children[2] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -146,10 +105,8 @@ class PtJump(val identifier: PtIdentifier?,
|
|||||||
val address: UInt?,
|
val address: UInt?,
|
||||||
val generatedLabel: String?,
|
val generatedLabel: String?,
|
||||||
position: Position) : PtNode(position) {
|
position: Position) : PtNode(position) {
|
||||||
override fun printProperties() {
|
init {
|
||||||
identifier?.printProperties()
|
identifier?.let {it.parent = this }
|
||||||
if(address!=null) print(address.toHex())
|
|
||||||
if(generatedLabel!=null) print(generatedLabel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,10 +114,6 @@ class PtJump(val identifier: PtIdentifier?,
|
|||||||
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
|
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
|
||||||
val target: PtAssignTarget
|
val target: PtAssignTarget
|
||||||
get() = children.single() as PtAssignTarget
|
get() = children.single() as PtAssignTarget
|
||||||
|
|
||||||
override fun printProperties() {
|
|
||||||
print(operator)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -169,8 +122,6 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
|
|||||||
get() = children[0] as PtExpression
|
get() = children[0] as PtExpression
|
||||||
val statements: PtNodeGroup
|
val statements: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -183,30 +134,26 @@ class PtReturn(position: Position) : PtNode(position) {
|
|||||||
else
|
else
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtVariable(name: String, val type: DataType, var value: PtExpression?, var arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
|
sealed interface IPtVariable {
|
||||||
override fun printProperties() {
|
val name: String
|
||||||
print("$type $name")
|
val type: DataType
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
|
||||||
|
init {
|
||||||
|
value?.let {it.parent=this}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtConstant(name: String, val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position) {
|
class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||||
override fun printProperties() {
|
|
||||||
print("$type $name = $value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) {
|
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||||
override fun printProperties() {
|
|
||||||
print("&$type $name = ${address.toHex()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PtWhen(position: Position) : PtNode(position) {
|
class PtWhen(position: Position) : PtNode(position) {
|
||||||
@ -214,8 +161,6 @@ class PtWhen(position: Position) : PtNode(position) {
|
|||||||
get() = children[0] as PtExpression
|
get() = children[0] as PtExpression
|
||||||
val choices: PtNodeGroup
|
val choices: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
|
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -224,5 +169,4 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
|
|||||||
get() = children[0] as PtNodeGroup
|
get() = children[0] as PtNodeGroup
|
||||||
val statements: PtNodeGroup
|
val statements: PtNodeGroup
|
||||||
get() = children[1] as PtNodeGroup
|
get() = children[1] as PtNodeGroup
|
||||||
override fun printProperties() {}
|
|
||||||
}
|
}
|
||||||
|
113
codeCore/src/prog8/code/core/BuiltinFunctions.kt
Normal file
113
codeCore/src/prog8/code/core/BuiltinFunctions.kt
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package prog8.code.core
|
||||||
|
|
||||||
|
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean)
|
||||||
|
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
|
||||||
|
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
|
||||||
|
override fun toString(): String {
|
||||||
|
val paramConvs = params.mapIndexed { index, it ->
|
||||||
|
when {
|
||||||
|
it.reg!=null -> "$index:${it.reg}"
|
||||||
|
it.variable -> "$index:variable"
|
||||||
|
else -> "$index:???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val returnConv =
|
||||||
|
when {
|
||||||
|
returns.reg!=null -> returns.reg.toString()
|
||||||
|
returns.floatFac1 -> "floatFAC1"
|
||||||
|
else -> "<no returnvalue>"
|
||||||
|
}
|
||||||
|
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
||||||
|
|
||||||
|
class FSignature(val pure: Boolean, // does it have side effects?
|
||||||
|
val parameters: List<FParam>,
|
||||||
|
val returnType: DataType?) {
|
||||||
|
|
||||||
|
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
|
||||||
|
val returns: ReturnConvention = when (returnType) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ReturnConvention(returnType, null, true)
|
||||||
|
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false)
|
||||||
|
null -> ReturnConvention(null, null, false)
|
||||||
|
else -> {
|
||||||
|
// return type depends on arg type
|
||||||
|
when (val paramType = actualParamTypes.first()) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ReturnConvention(paramType, null, true)
|
||||||
|
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
else -> ReturnConvention(paramType, null, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||||
|
actualParamTypes.size==1 -> {
|
||||||
|
// one parameter goes via register/registerpair
|
||||||
|
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||||
|
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||||
|
else -> ParamConvention(paramType, null, false)
|
||||||
|
}
|
||||||
|
CallConvention(listOf(paramConv), returns)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// multiple parameters go via variables
|
||||||
|
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
|
||||||
|
CallConvention(paramConvs, returns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||||
|
// this set of function have no return value and operate in-place:
|
||||||
|
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
|
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
|
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
|
// cmp returns a status in the carry flag, but not a proper return value
|
||||||
|
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
|
||||||
|
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
|
||||||
|
"abs" to FSignature(true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD),
|
||||||
|
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
|
||||||
|
// normal functions follow:
|
||||||
|
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
|
||||||
|
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
|
||||||
|
"sqrt16" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||||
|
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
|
||||||
|
"divmodw" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
|
||||||
|
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||||
|
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||||
|
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||||
|
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||||
|
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
|
||||||
|
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||||
|
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||||
|
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||||
|
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||||
|
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
|
||||||
|
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
|
||||||
|
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
|
||||||
|
"pushw" to FSignature(false, listOf(FParam("value", WordDatatypes)), null),
|
||||||
|
"rsave" to FSignature(false, emptyList(), null),
|
||||||
|
"rsavex" to FSignature(false, emptyList(), null),
|
||||||
|
"rrestore" to FSignature(false, emptyList(), null),
|
||||||
|
"rrestorex" to FSignature(false, emptyList(), null),
|
||||||
|
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
|
)
|
||||||
|
|
||||||
|
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
|
@ -16,11 +16,16 @@ class CompilationOptions(val output: OutputType,
|
|||||||
var slowCodegenWarnings: Boolean = false,
|
var slowCodegenWarnings: Boolean = false,
|
||||||
var optimize: Boolean = false,
|
var optimize: Boolean = false,
|
||||||
var optimizeFloatExpressions: Boolean = false,
|
var optimizeFloatExpressions: Boolean = false,
|
||||||
var dontReinitGlobals: Boolean = false,
|
|
||||||
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 varsHigh: Boolean = false,
|
||||||
|
var useNewExprCode: 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()
|
||||||
)
|
) {
|
||||||
|
init {
|
||||||
|
compTarget.machine.initializeMemoryAreas(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package prog8.code.core
|
|
||||||
|
|
||||||
interface IAssemblyGenerator {
|
|
||||||
fun compileToAssembly(): IAssemblyProgram?
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAssemblyProgram {
|
|
||||||
val name: String
|
|
||||||
fun assemble(options: CompilationOptions): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
|
17
codeCore/src/prog8/code/core/ICodeGeneratorBackend.kt
Normal file
17
codeCore/src/prog8/code/core/ICodeGeneratorBackend.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package prog8.code.core
|
||||||
|
|
||||||
|
import prog8.code.SymbolTable
|
||||||
|
import prog8.code.ast.PtProgram
|
||||||
|
|
||||||
|
interface ICodeGeneratorBackend {
|
||||||
|
fun generate(program: PtProgram,
|
||||||
|
symbolTable: SymbolTable,
|
||||||
|
options: CompilationOptions,
|
||||||
|
errors: IErrorReporter): IAssemblyProgram?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface IAssemblyProgram {
|
||||||
|
val name: String
|
||||||
|
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
|
||||||
|
}
|
@ -7,5 +7,5 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
|||||||
val defaultEncoding: Encoding
|
val defaultEncoding: Encoding
|
||||||
|
|
||||||
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ interface IMachineDefinition {
|
|||||||
var ESTACK_LO: UInt
|
var ESTACK_LO: UInt
|
||||||
var ESTACK_HI: UInt
|
var ESTACK_HI: UInt
|
||||||
val PROGRAM_LOAD_ADDRESS : UInt
|
val PROGRAM_LOAD_ADDRESS : UInt
|
||||||
|
val BSSHIGHRAM_START: UInt
|
||||||
|
val BSSHIGHRAM_END: UInt
|
||||||
|
|
||||||
val cpu: CpuType
|
val cpu: CpuType
|
||||||
var zeropage: Zeropage
|
var zeropage: Zeropage
|
||||||
|
@ -10,5 +10,5 @@ enum class Encoding(val prefix: String) {
|
|||||||
|
|
||||||
interface IStringEncoding {
|
interface IStringEncoding {
|
||||||
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||||
fun decodeString(bytes: List<UByte>, encoding: Encoding): String
|
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class MemAllocationError(message: String) : Exception(message)
|
|||||||
abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
||||||
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
|
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
|
||||||
|
|
||||||
abstract fun allocate(name: List<String>,
|
abstract fun allocate(name: String,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
@ -29,7 +29,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
|
|
||||||
// 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>, VarAllocation>()
|
val allocatedVariables = mutableMapOf<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.
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allocate(name: List<String>,
|
override fun allocate(name: String,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
@ -107,7 +107,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
|
|
||||||
private fun reserve(range: UIntRange) = free.removeAll(range)
|
private fun reserve(range: UIntRange) = free.removeAll(range)
|
||||||
|
|
||||||
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List<String>): UInt {
|
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String): UInt {
|
||||||
require(size>=0)
|
require(size>=0)
|
||||||
free.removeAll(address until address+size.toUInt())
|
free.removeAll(address until address+size.toUInt())
|
||||||
if(name.isNotEmpty()) {
|
if(name.isNotEmpty()) {
|
||||||
@ -131,11 +131,12 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: this class is not yet used
|
||||||
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
||||||
private var nextLocation: UInt = region.first
|
private var nextLocation: UInt = region.first
|
||||||
|
|
||||||
override fun allocate(
|
override fun allocate(
|
||||||
name: List<String>,
|
name: String,
|
||||||
datatype: DataType,
|
datatype: DataType,
|
||||||
numElements: Int?,
|
numElements: Int?,
|
||||||
position: Position?,
|
position: Position?,
|
||||||
|
@ -5,6 +5,7 @@ val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
|||||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||||
|
val PrefixOperators = setOf("+", "-", "~", "not")
|
||||||
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
||||||
|
|
||||||
fun invertedComparisonOperator(operator: String) =
|
fun invertedComparisonOperator(operator: String) =
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package prog8.code.core
|
package prog8.code.core
|
||||||
|
|
||||||
|
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||||
import java.nio.file.InvalidPathException
|
import java.nio.file.InvalidPathException
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.absolute
|
import kotlin.io.path.absolute
|
||||||
@ -7,6 +8,10 @@ import kotlin.io.path.absolute
|
|||||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||||
fun toClickableStr(): String {
|
fun toClickableStr(): String {
|
||||||
|
if(this===DUMMY)
|
||||||
|
return ""
|
||||||
|
if(file.startsWith(libraryFilePrefix))
|
||||||
|
return "$file:$line:$startCol:"
|
||||||
return try {
|
return try {
|
||||||
val path = Path(file).absolute().normalize().toString()
|
val path = Path(file).absolute().normalize().toString()
|
||||||
"file://$path:$line:$startCol:"
|
"file://$path:$line:$startCol:"
|
||||||
|
@ -16,5 +16,7 @@ class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Cb
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NAME = "c64"
|
const val NAME = "c64"
|
||||||
|
|
||||||
|
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ object Encoder: IStringEncoding {
|
|||||||
success = { it }
|
success = { it }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||||
val decoded = when(encoding) {
|
val decoded = when(encoding) {
|
||||||
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
|
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
|
||||||
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
||||||
|
@ -13,14 +13,17 @@ class AtariMachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = 6
|
override val FLOAT_MEM_SIZE = 6
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
|
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive // TODO
|
||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive // TODO
|
||||||
|
|
||||||
|
override val BSSHIGHRAM_START = 0u // TODO
|
||||||
|
override val BSSHIGHRAM_END = 0u // TODO
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return if (compilerOptions.output == OutputType.XEX)
|
return if (compilerOptions.output == OutputType.XEX)
|
||||||
|
@ -13,6 +13,10 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
|
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (options.floats) {
|
||||||
|
throw InternalCompilerException("Atari target doesn't yet support floating point routines")
|
||||||
|
}
|
||||||
|
|
||||||
if (options.floats && options.zeropage !in arrayOf(
|
if (options.floats && options.zeropage !in arrayOf(
|
||||||
ZeropageType.FLOATSAFE,
|
ZeropageType.FLOATSAFE,
|
||||||
ZeropageType.BASICSAFE,
|
ZeropageType.BASICSAFE,
|
||||||
@ -39,9 +43,9 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.code.target.c128
|
package prog8.code.target.c128
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -14,9 +15,13 @@ class C128MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive
|
||||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive
|
||||||
|
|
||||||
|
override val BSSHIGHRAM_START = 0u // TODO
|
||||||
|
override val BSSHIGHRAM_END = 0u // TODO
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
@ -36,7 +41,7 @@ class C128MachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println("\nStarting C-128 emulator x128...")
|
println("\nStarting C-128 emulator x128...")
|
||||||
val viceMonlist = viceMonListName(programNameWithPath.toString())
|
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||||
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
|
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
@ -6,15 +6,22 @@ import prog8.code.core.Zeropage
|
|||||||
import prog8.code.core.ZeropageType
|
import prog8.code.core.ZeropageType
|
||||||
|
|
||||||
|
|
||||||
|
// reference: "Mapping the C128" zero page chapter.
|
||||||
|
|
||||||
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
override val SCRATCH_B1 = 0x9bu // temp storage for a single byte
|
override val SCRATCH_B1 = 0x74u // temp storage for a single byte
|
||||||
override val SCRATCH_REG = 0x9cu // temp storage for a register, must be B1+1
|
override val SCRATCH_REG = 0x75u // temp storage for a register, must be B1+1
|
||||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (options.floats) {
|
||||||
|
throw InternalCompilerException("C128 target doesn't yet support floating point routines")
|
||||||
|
// note: in git commit labeled 'c128: remove floats module' the floats.p8 and floats.asm files are removed,
|
||||||
|
// they could be retrieved again at a later time if the compiler somehow *does* store the fp variables in bank1.
|
||||||
|
}
|
||||||
|
|
||||||
if (options.floats && options.zeropage !in arrayOf(
|
if (options.floats && options.zeropage !in arrayOf(
|
||||||
ZeropageType.FLOATSAFE,
|
ZeropageType.FLOATSAFE,
|
||||||
ZeropageType.BASICSAFE,
|
ZeropageType.BASICSAFE,
|
||||||
@ -24,23 +31,42 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
|
|
||||||
when (options.zeropage) {
|
when (options.zeropage) {
|
||||||
ZeropageType.FULL -> {
|
ZeropageType.FULL -> {
|
||||||
// TODO all c128 usable zero page locations, except the ones used by the system's IRQ routine
|
// $00/$01 are data port IO registers, // $02-$09 are storage locations for JSRFAR and such
|
||||||
free.addAll(0x0au..0xffu) // TODO c128 what about $02-$09?
|
free.addAll(0x0au..0xffu)
|
||||||
// TODO c128 free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
free.removeAll(listOf(0x90u, 0x91u, 0xa0u, 0xa1u, 0xa2u, 0xc0u, 0xccu, 0xcdu, 0xd0u, 0xd1u, 0xd2u, 0xd3u, 0xd4u, 0xd5u, 0xf7u)) // these are updated/used by IRQ
|
||||||
|
}
|
||||||
|
ZeropageType.KERNALSAFE -> {
|
||||||
|
free.addAll(0x0au..0x8fu) // BASIC variables
|
||||||
|
free.addAll(listOf(0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu,
|
||||||
|
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u))
|
||||||
}
|
}
|
||||||
ZeropageType.KERNALSAFE,
|
|
||||||
ZeropageType.FLOATSAFE,
|
ZeropageType.FLOATSAFE,
|
||||||
ZeropageType.BASICSAFE -> {
|
ZeropageType.BASICSAFE -> {
|
||||||
free.clear() // TODO c128 usable zero page addresses
|
free.addAll(listOf(0x0bu, 0x0cu, 0x0du, 0x0eu, 0x0fu, 0x10u, 0x11u, 0x12u, 0x16u, 0x17u, 0x18u, 0x19u, 0x1au))
|
||||||
|
free.addAll(0x1bu..0x23u)
|
||||||
|
free.addAll(listOf(0x3fu, 0x40u, 0x41u, 0x42u, 0x43u, 0x44u, 0x47u, 0x48u, 0x49u, 0x4au, 0x4bu, 0x4cu, 0x4fu,
|
||||||
|
0x55u, 0x56u, 0x57u, 0x58u,
|
||||||
|
0x74u, 0x75u, 0x78u, 0x80u, 0x83u, 0x87u, 0x88u, 0x89u, 0x8au, 0x8bu, 0x8cu, 0x8du, 0x8eu, 0x8fu,
|
||||||
|
0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu,
|
||||||
|
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u
|
||||||
|
))
|
||||||
|
|
||||||
|
// if(options.zeropage==ZeropageType.BASICSAFE) {
|
||||||
|
// can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0
|
||||||
|
free.addAll(listOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
|
||||||
|
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
|
||||||
|
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
|
||||||
|
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
ZeropageType.DONTUSE -> {
|
ZeropageType.DONTUSE -> {
|
||||||
free.clear() // don't use zeropage at all
|
free.clear() // don't use zeropage at all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.code.target.c64
|
package prog8.code.target.c64
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -15,9 +16,13 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||||
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
override var ESTACK_LO = 0xcf00u // $cf00-$cf7f inclusive
|
||||||
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
override var ESTACK_HI = 0xcf80u // $cf80-$cfff inclusive
|
||||||
|
|
||||||
|
override val BSSHIGHRAM_START = 0xc000u
|
||||||
|
override val BSSHIGHRAM_END = ESTACK_LO
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
@ -38,7 +43,7 @@ class C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
for(emulator in listOf("x64sc", "x64")) {
|
for(emulator in listOf("x64sc", "x64")) {
|
||||||
println("\nStarting C-64 emulator $emulator...")
|
println("\nStarting C-64 emulator $emulator...")
|
||||||
val viceMonlist = viceMonListName(programNameWithPath.toString())
|
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||||
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
|
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
@ -44,7 +44,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
// remove the zeropage locations used for floating point operations from the free list
|
// remove the zeropage locations used for floating point operations from the free list
|
||||||
free.removeAll(listOf(
|
free.removeAll(listOf(
|
||||||
0x03, 0x04, 0x10, 0x11, 0x12,
|
0x03, 0x04, 0x05, 0x06, 0x10, 0x11, 0x12,
|
||||||
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
@ -65,9 +65,9 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
|
||||||
@ -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}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
allocatedVariables["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())
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ object AtasciiEncoding {
|
|||||||
return Ok(mapped)
|
return Ok(mapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||||
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
|
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ object IsoEncoding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||||
return try {
|
return try {
|
||||||
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
||||||
} catch (ce: CharConversionException) {
|
} catch (ce: CharConversionException) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.code.target.cx16
|
package prog8.code.target.cx16
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.cbm.Mflpt5
|
import prog8.code.target.cbm.Mflpt5
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -14,9 +15,13 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||||
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
override var ESTACK_LO = 0x0700u // $0700-$077f inclusive
|
||||||
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
override var ESTACK_HI = 0x0780u // $0780-$07ff inclusive
|
||||||
|
|
||||||
|
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
|
||||||
|
override val BSSHIGHRAM_END = 0xc000u // rom starts here.
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
override lateinit var golden: GoldenRam
|
override lateinit var golden: GoldenRam
|
||||||
|
|
||||||
@ -39,7 +44,7 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
emulator = "box16"
|
emulator = "box16"
|
||||||
extraArgs = listOf("-sym", viceMonListName(programNameWithPath.toString()))
|
extraArgs = listOf("-sym", C64Target.viceMonListName(programNameWithPath.toString()))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
|
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
|
||||||
@ -59,7 +64,7 @@ class CX16MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
zeropage = CX16Zeropage(compilerOptions)
|
zeropage = CX16Zeropage(compilerOptions)
|
||||||
golden = GoldenRam(compilerOptions, 0x0600u until 0x0800u)
|
golden = GoldenRam(compilerOptions, 0x0400u until ESTACK_LO)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,9 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|||||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val distictFree = free.distinct()
|
val distinctFree = free.distinct()
|
||||||
free.clear()
|
free.clear()
|
||||||
free.addAll(distictFree)
|
free.addAll(distinctFree)
|
||||||
|
|
||||||
removeReservedFromFreePool()
|
removeReservedFromFreePool()
|
||||||
|
|
||||||
@ -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}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,10 +17,18 @@ 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 val BSSHIGHRAM_START = 0u // not actually used
|
||||||
|
override val BSSHIGHRAM_END = 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 lateinit var golden: GoldenRam // not actually used
|
||||||
|
|
||||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
override fun getFloatAsmBytes(num: Number): String {
|
||||||
|
// little endian binary representation
|
||||||
|
val bits = num.toFloat().toBits().toUInt()
|
||||||
|
val hexStr = bits.toString(16).padStart(8, '0')
|
||||||
|
val parts = hexStr.chunked(2).map { "\$" + it }
|
||||||
|
return parts.joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return listOf("syslib")
|
return listOf("syslib")
|
||||||
@ -44,9 +52,24 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = false
|
override fun isIOAddress(address: UInt): Boolean = false
|
||||||
|
|
||||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
|
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||||
|
zeropage = VirtualZeropage(compilerOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IVirtualMachineRunner {
|
interface IVirtualMachineRunner {
|
||||||
fun runProgram(irSource: String)
|
fun runProgram(irSource: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
|
||||||
|
override val SCRATCH_B1: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
override val SCRATCH_REG: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
override val SCRATCH_W1: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
override val SCRATCH_W2: UInt
|
||||||
|
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||||
|
|
||||||
|
override fun allocateCx16VirtualRegisters() { /* there is no actual zero page in this target to allocate thing in */ }
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
id "org.jetbrains.kotlin.jvm"
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
id "io.kotest" version "0.3.9"
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@ -25,11 +26,11 @@ compileTestKotlin {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':codeCore')
|
implementation project(':codeCore')
|
||||||
implementation project(':compilerAst')
|
|
||||||
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 "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||||
|
|
||||||
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -41,6 +42,22 @@ sourceSets {
|
|||||||
srcDirs = ["${project.projectDir}/res"]
|
srcDirs = ["${project.projectDir}/res"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
srcDir "${project.projectDir}/test"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: there are no unit tests in this module!
|
test {
|
||||||
|
// Enable JUnit 5 (Gradle 4.6+).
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
// Always run tests, even when nothing changed.
|
||||||
|
dependsOn 'cleanTest'
|
||||||
|
|
||||||
|
// Show test results.
|
||||||
|
testLogging {
|
||||||
|
events "skipped", "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,13 +4,15 @@
|
|||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="module" module-name="codeCore" />
|
<orderEntry type="module" module-name="codeCore" />
|
||||||
<orderEntry type="module" module-name="compilerAst" />
|
|
||||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||||
|
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||||
|
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,15 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.code.StConstant
|
||||||
import prog8.ast.expressions.NumericLiteral
|
import prog8.code.StMemVar
|
||||||
import prog8.ast.statements.VarDecl
|
import prog8.code.SymbolTable
|
||||||
import prog8.ast.statements.VarDeclType
|
|
||||||
import prog8.code.core.IMachineDefinition
|
import prog8.code.core.IMachineDefinition
|
||||||
|
|
||||||
|
|
||||||
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||||
|
|
||||||
|
|
||||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, symbolTable: SymbolTable): Int {
|
||||||
|
|
||||||
var numberOfOptimizations = 0
|
var numberOfOptimizations = 0
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
|||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods = optimizeStoreLoadSame(linesByFour, machine, program)
|
mods = optimizeStoreLoadSame(linesByFour, machine, symbolTable)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
@ -52,14 +51,14 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
|||||||
}
|
}
|
||||||
|
|
||||||
var linesByFourteen = getLinesBy(lines, 14)
|
var linesByFourteen = getLinesBy(lines, 14)
|
||||||
mods = optimizeSameAssignments(linesByFourteen, machine, program)
|
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFourteen = getLinesBy(lines, 14)
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
mods = optimizeSamePointerIndexing(linesByFourteen, machine, program)
|
mods = optimizeSamePointerIndexing(linesByFourteen)
|
||||||
if(mods.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
apply(mods, lines)
|
apply(mods, lines)
|
||||||
linesByFourteen = getLinesBy(lines, 14)
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
@ -129,7 +128,11 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
|
|||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
private fun optimizeSameAssignments(
|
||||||
|
linesByFourteen: List<List<IndexedValue<String>>>,
|
||||||
|
machine: IMachineDefinition,
|
||||||
|
symbolTable: SymbolTable
|
||||||
|
): List<Modification> {
|
||||||
|
|
||||||
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
||||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||||
@ -154,8 +157,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val fourthvalue = sixth.substring(4)
|
val fourthvalue = sixth.substring(4)
|
||||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||||
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||||
val address1 = getAddressArg(first, program)
|
val address1 = getAddressArg(first, symbolTable)
|
||||||
val address2 = getAddressArg(second, program)
|
val address2 = getAddressArg(second, symbolTable)
|
||||||
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
||||||
mods.add(Modification(lines[4].index, true, null))
|
mods.add(Modification(lines[4].index, true, null))
|
||||||
mods.add(Modification(lines[5].index, true, null))
|
mods.add(Modification(lines[5].index, true, null))
|
||||||
@ -168,7 +171,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val secondvalue = third.substring(4)
|
val secondvalue = third.substring(4)
|
||||||
if(firstvalue==secondvalue) {
|
if(firstvalue==secondvalue) {
|
||||||
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address))
|
if(address==null || !machine.isIOAddress(address))
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
}
|
}
|
||||||
@ -251,7 +254,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val thirdvalue = third.substring(4)
|
val thirdvalue = third.substring(4)
|
||||||
val fourthvalue = fourth.substring(4)
|
val fourthvalue = fourth.substring(4)
|
||||||
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
overlappingMods = true
|
overlappingMods = true
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
@ -275,7 +278,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val firstvalue = first.substring(4)
|
val firstvalue = first.substring(4)
|
||||||
val thirdvalue = third.substring(4)
|
val thirdvalue = third.substring(4)
|
||||||
if(firstvalue==thirdvalue) {
|
if(firstvalue==thirdvalue) {
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
overlappingMods = true
|
overlappingMods = true
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
@ -295,7 +298,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
val secondvalue = second.substring(4)
|
val secondvalue = second.substring(4)
|
||||||
val thirdvalue = third.substring(4)
|
val thirdvalue = third.substring(4)
|
||||||
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
||||||
val address = getAddressArg(first, program)
|
val address = getAddressArg(first, symbolTable)
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
overlappingMods = true
|
overlappingMods = true
|
||||||
val reg2 = second[2]
|
val reg2 = second[2]
|
||||||
@ -308,26 +311,15 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
|||||||
/*
|
/*
|
||||||
sta A ; or stz double store, remove this first one
|
sta A ; or stz double store, remove this first one
|
||||||
sta A ; or stz
|
sta A ; or stz
|
||||||
*/
|
However, this cannot be done relyably because 'A' could be a constant symbol referring to an I/O address.
|
||||||
if(!overlappingMods && first.isStoreRegOrZero() && second.isStoreRegOrZero()) {
|
We can't see that here and would otherwise delete valid double stores.
|
||||||
if(first[2]==second[2]) {
|
*/
|
||||||
val firstvalue = first.substring(4)
|
|
||||||
val secondvalue = second.substring(4)
|
|
||||||
if(firstvalue==secondvalue) {
|
|
||||||
val address = getAddressArg(first, program)
|
|
||||||
if(address==null || !machine.isIOAddress(address)) {
|
|
||||||
overlappingMods = true
|
|
||||||
mods.add(Modification(lines[0].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
|
||||||
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
|
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
|
||||||
// if Y isn't modified in between we can omit the second LDY:
|
// if Y isn't modified in between we can omit the second LDY:
|
||||||
@ -369,7 +361,11 @@ private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<
|
|||||||
return mods
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
private fun optimizeStoreLoadSame(
|
||||||
|
linesByFour: List<List<IndexedValue<String>>>,
|
||||||
|
machine: IMachineDefinition,
|
||||||
|
symbolTable: SymbolTable
|
||||||
|
): List<Modification> {
|
||||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||||
val mods = mutableListOf<Modification>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (lines in linesByFour) {
|
for (lines in linesByFour) {
|
||||||
@ -397,7 +393,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// no branch instruction follows, we can remove the load instruction
|
// no branch instruction follows, we can remove the load instruction
|
||||||
val address = getAddressArg(lines[2].value, program)
|
val address = getAddressArg(lines[2].value, symbolTable)
|
||||||
address==null || !machine.isIOAddress(address)
|
address==null || !machine.isIOAddress(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,7 +435,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
|||||||
|
|
||||||
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
||||||
|
|
||||||
private fun getAddressArg(line: String, program: Program): UInt? {
|
private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
|
||||||
|
// try to get the constant value address, could return null if it's a symbol instead
|
||||||
val loadArg = line.trimStart().substring(3).trim()
|
val loadArg = line.trimStart().substring(3).trim()
|
||||||
return when {
|
return when {
|
||||||
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
||||||
@ -450,15 +447,11 @@ 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
|
when (val symbol = symbolTable.flat[identifier]) {
|
||||||
if(decl!=null) {
|
is StConstant -> symbol.value.toUInt()
|
||||||
when(decl.type){
|
is StMemVar -> symbol.address
|
||||||
VarDeclType.VAR -> null
|
else -> null
|
||||||
VarDeclType.CONST,
|
|
||||||
VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else null
|
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
else -> loadArg.substring(1).toUIntOrNull()
|
else -> loadArg.substring(1).toUIntOrNull()
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.expressions.ArrayIndexedExpression
|
import prog8.code.ast.*
|
||||||
import prog8.ast.expressions.BuiltinFunctionCall
|
|
||||||
import prog8.ast.expressions.Expression
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.code.core.Cx16VirtualRegisters
|
import prog8.code.core.Cx16VirtualRegisters
|
||||||
import prog8.code.core.RegisterOrPair
|
import prog8.code.core.RegisterOrPair
|
||||||
import prog8.code.core.RegisterOrStatusflag
|
import prog8.code.core.RegisterOrStatusflag
|
||||||
|
|
||||||
|
|
||||||
fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
|
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||||
val order = mutableListOf<Int>()
|
val order = mutableListOf<Int>()
|
||||||
// order is:
|
// order is:
|
||||||
// 1) cx16 virtual word registers,
|
// 1) cx16 virtual word registers,
|
||||||
@ -17,43 +14,45 @@ fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
|
|||||||
// 3) single CPU registers (X last), except A,
|
// 3) single CPU registers (X last), except A,
|
||||||
// 4) CPU Carry status flag
|
// 4) CPU Carry status flag
|
||||||
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
||||||
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
|
val args = sub.parameters.withIndex()
|
||||||
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
|
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||||
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
|
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
|
||||||
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
|
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
|
||||||
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
|
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
|
||||||
|
|
||||||
cx16regs.forEach { order += it.index }
|
cx16regs.forEach { order += it.index }
|
||||||
pairedRegs.forEach { order += it.index }
|
pairedRegs.forEach { order += it.index }
|
||||||
regsWithoutA.forEach {
|
regsWithoutA.forEach {
|
||||||
if(it.value.second.registerOrPair != RegisterOrPair.X)
|
if(it.value.first.registerOrPair != RegisterOrPair.X)
|
||||||
order += it.index
|
order += it.index
|
||||||
}
|
}
|
||||||
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
||||||
rest.forEach { order += it.index }
|
rest.forEach { order += it.index }
|
||||||
regA.forEach { order += it.index }
|
regA.forEach { order += it.index }
|
||||||
require(order.size==sub.parameters.size)
|
require(order.size==sub.parameters.size)
|
||||||
return order
|
return order
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
|
fun asmsub6502ArgsHaveRegisterClobberRisk(
|
||||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
args: List<PtExpression>,
|
||||||
fun isClobberRisk(expr: Expression): Boolean {
|
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
|
||||||
|
): Boolean {
|
||||||
|
fun isClobberRisk(expr: PtExpression): Boolean {
|
||||||
when (expr) {
|
when (expr) {
|
||||||
is ArrayIndexedExpression -> {
|
is PtArrayIndexer -> {
|
||||||
return paramRegisters.any {
|
return params.any {
|
||||||
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is BuiltinFunctionCall -> {
|
is PtBuiltinFunctionCall -> {
|
||||||
if (expr.name == "lsb" || expr.name == "msb")
|
if (expr.name == "lsb" || expr.name == "msb")
|
||||||
return isClobberRisk(expr.args[0])
|
return isClobberRisk(expr.args[0])
|
||||||
if (expr.name == "mkword")
|
if (expr.name == "mkword")
|
||||||
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
||||||
return !expr.isSimple
|
return !expr.isSimple()
|
||||||
}
|
}
|
||||||
else -> return !expr.isSimple
|
else -> return !expr.isSimple()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ package prog8.codegen.cpu6502
|
|||||||
import com.github.michaelbull.result.Ok
|
import com.github.michaelbull.result.Ok
|
||||||
import com.github.michaelbull.result.Result
|
import com.github.michaelbull.result.Result
|
||||||
import com.github.michaelbull.result.mapError
|
import com.github.michaelbull.result.mapError
|
||||||
import prog8.ast.generatedLabelPrefix
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
@ -20,10 +20,10 @@ internal class AssemblyProgram(
|
|||||||
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
|
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
|
||||||
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
|
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
|
||||||
private val binFile = outputDir.resolve("$name.bin")
|
private val binFile = outputDir.resolve("$name.bin")
|
||||||
private val viceMonListFile = outputDir.resolve(viceMonListName(name))
|
private val viceMonListFile = outputDir.resolve(C64Target.viceMonListName(name))
|
||||||
private val listFile = outputDir.resolve("$name.list")
|
private val listFile = outputDir.resolve("$name.list")
|
||||||
|
|
||||||
override fun assemble(options: CompilationOptions): Boolean {
|
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||||
|
|
||||||
val assemblerCommand: List<String>
|
val assemblerCommand: List<String>
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ internal class AssemblyProgram(
|
|||||||
"atari" -> {
|
"atari" -> {
|
||||||
// Atari800XL .xex generation.
|
// Atari800XL .xex generation.
|
||||||
|
|
||||||
// TODO are these options okay?
|
// TODO are these options okay for atari?
|
||||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||||
"--no-monitor"
|
"--no-monitor"
|
||||||
@ -104,7 +104,7 @@ internal class AssemblyProgram(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeGeneratedLabelsFromMonlist() {
|
private fun removeGeneratedLabelsFromMonlist() {
|
||||||
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
|
val pattern = Regex("""al (\w+) \S+prog8_label_.+?""")
|
||||||
val lines = viceMonListFile.toFile().readLines()
|
val lines = viceMonListFile.toFile().readLines()
|
||||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||||
for (line in lines) {
|
for (line in lines) {
|
||||||
@ -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")
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,17 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.code.ast.*
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
internal class ExpressionsAsmGen(private val program: Program,
|
internal class ExpressionsAsmGen(private val program: PtProgram,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen6502Internal,
|
||||||
private val allocator: VariableAllocator) {
|
private val allocator: VariableAllocator) {
|
||||||
|
|
||||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||||
internal fun translateExpression(expression:Expression) {
|
internal fun translateExpression(expression: PtExpression) {
|
||||||
if (this.asmgen.options.slowCodegenWarnings) {
|
if (this.asmgen.options.slowCodegenWarnings) {
|
||||||
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
|
asmgen.errors.warn("slow stack evaluation used for expression", expression.position)
|
||||||
}
|
}
|
||||||
translateExpressionInternal(expression)
|
translateExpressionInternal(expression)
|
||||||
}
|
}
|
||||||
@ -21,41 +20,47 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
// the rest of the methods are all PRIVATE
|
// the rest of the methods are all PRIVATE
|
||||||
|
|
||||||
|
|
||||||
private fun translateExpressionInternal(expression: Expression) {
|
private fun translateExpressionInternal(expression: PtExpression) {
|
||||||
|
|
||||||
when(expression) {
|
when(expression) {
|
||||||
is PrefixExpression -> translateExpression(expression)
|
is PtPrefix -> translateExpression(expression)
|
||||||
is BinaryExpression -> translateExpression(expression)
|
is PtBinaryExpression -> translateExpression(expression)
|
||||||
is ArrayIndexedExpression -> translateExpression(expression)
|
is PtArrayIndexer -> translateExpression(expression)
|
||||||
is TypecastExpression -> translateExpression(expression)
|
is PtTypeCast -> translateExpression(expression)
|
||||||
is AddressOf -> translateExpression(expression)
|
is PtAddressOf -> translateExpression(expression)
|
||||||
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
is PtMemoryByte -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||||
is NumericLiteral -> translateExpression(expression)
|
is PtNumber -> translateExpression(expression)
|
||||||
is IdentifierReference -> translateExpression(expression)
|
is PtIdentifier -> translateExpression(expression)
|
||||||
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
|
is PtFunctionCall -> translateFunctionCallResultOntoStack(expression)
|
||||||
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
|
is PtBuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
|
||||||
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
|
is PtContainmentCheck -> translateContainmentCheck(expression)
|
||||||
is ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
is PtArray, is PtString -> throw AssemblyError("string/array literal value assignment should have been replaced by a variable")
|
||||||
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values")
|
is PtRange -> throw AssemblyError("range expression should have been changed into array values")
|
||||||
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
|
is PtMachineRegister -> throw AssemblyError("machine register ast node should not occur in 6502 codegen it is for IR code")
|
||||||
else -> TODO("missing expression asmgen for $expression")
|
else -> TODO("missing expression asmgen for $expression")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
|
private fun translateContainmentCheck(check: PtContainmentCheck) {
|
||||||
|
asmgen.assignExpressionToRegister(check, RegisterOrPair.A)
|
||||||
|
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateFunctionCallResultOntoStack(call: PtFunctionCall) {
|
||||||
// only for use in nested expression evaluation
|
// only for use in nested expression evaluation
|
||||||
|
|
||||||
val sub = call.target.targetSubroutine(program)!!
|
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||||
|
val sub = symbol!!.astNode as IPtSubroutine
|
||||||
asmgen.saveXbeforeCall(call)
|
asmgen.saveXbeforeCall(call)
|
||||||
asmgen.translateFunctionCall(call, true)
|
asmgen.translateFunctionCall(call)
|
||||||
if(sub.regXasResult()) {
|
if(sub.regXasResult()) {
|
||||||
// store the return value in X somewhere that we can access again below
|
// store the return value in X somewhere that we can access again below
|
||||||
asmgen.out(" stx P8ZP_SCRATCH_REG")
|
asmgen.out(" stx P8ZP_SCRATCH_REG")
|
||||||
}
|
}
|
||||||
asmgen.restoreXafterCall(call)
|
asmgen.restoreXafterCall(call)
|
||||||
|
|
||||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
val returns: List<Pair<RegisterOrStatusflag, DataType>> = sub.returnsWhatWhere()
|
||||||
for ((_, reg) in returns) {
|
for ((reg, _) in returns) {
|
||||||
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
|
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
|
||||||
if (reg.registerOrPair != null) {
|
if (reg.registerOrPair != null) {
|
||||||
when (reg.registerOrPair!!) {
|
when (reg.registerOrPair!!) {
|
||||||
@ -133,9 +138,9 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(typecast: TypecastExpression) {
|
private fun translateExpression(typecast: PtTypeCast) {
|
||||||
translateExpressionInternal(typecast.expression)
|
translateExpressionInternal(typecast.value)
|
||||||
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
when(typecast.value.type) {
|
||||||
DataType.UBYTE, DataType.BOOL -> {
|
DataType.UBYTE, DataType.BOOL -> {
|
||||||
when(typecast.type) {
|
when(typecast.type) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {}
|
DataType.UBYTE, DataType.BYTE -> {}
|
||||||
@ -197,12 +202,12 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(expr: AddressOf) {
|
private fun translateExpression(expr: PtAddressOf) {
|
||||||
val name = asmgen.asmVariableName(expr.identifier)
|
val name = asmgen.asmVariableName(expr.identifier)
|
||||||
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(expr: NumericLiteral) {
|
private fun translateExpression(expr: PtNumber) {
|
||||||
when(expr.type) {
|
when(expr.type) {
|
||||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||||
@ -220,9 +225,9 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(expr: IdentifierReference) {
|
private fun translateExpression(expr: PtIdentifier) {
|
||||||
val varname = asmgen.asmVariableName(expr)
|
val varname = asmgen.asmVariableName(expr)
|
||||||
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
|
when(expr.type) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||||
}
|
}
|
||||||
@ -239,24 +244,54 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(expr: BinaryExpression) {
|
private fun translateExpression(expr: PtBinaryExpression) {
|
||||||
// Uses evalstack to evaluate the given expression.
|
require(!asmgen.options.useNewExprCode)
|
||||||
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
|
|
||||||
val leftIDt = expr.left.inferType(program)
|
|
||||||
val rightIDt = expr.right.inferType(program)
|
|
||||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
|
||||||
throw AssemblyError("can't infer type of both expression operands")
|
|
||||||
|
|
||||||
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
|
// Uses evalstack to evaluate the given expression. THIS IS SLOW AND SHOULD BE AVOIDED!
|
||||||
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
|
if(translateSomewhatOptimized(expr.left, expr.operator, expr.right))
|
||||||
// see if we can apply some optimized routines
|
return
|
||||||
when(expr.operator) {
|
|
||||||
|
val leftDt = expr.left.type
|
||||||
|
val rightDt = expr.right.type
|
||||||
|
|
||||||
|
// compare with zero
|
||||||
|
if(expr.operator in ComparisonOperators) {
|
||||||
|
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
||||||
|
val rightVal = expr.right.asConstInteger()
|
||||||
|
if(rightVal==0)
|
||||||
|
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in ComparisonOperators)
|
||||||
|
return translateCompareStrings(expr.left, expr.operator, expr.right)
|
||||||
|
|
||||||
|
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||||
|
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||||
|
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||||
|
|
||||||
|
// the general, non-optimized cases
|
||||||
|
// TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
translateExpressionInternal(expr.right)
|
||||||
|
when (leftDt) {
|
||||||
|
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||||
|
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||||
|
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
||||||
|
else -> throw AssemblyError("non-numerical datatype")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateSomewhatOptimized(left: PtExpression, operator: String, right: PtExpression): Boolean {
|
||||||
|
val leftDt = left.type
|
||||||
|
val rightDt = right.type
|
||||||
|
when(operator) {
|
||||||
"+" -> {
|
"+" -> {
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
val leftVal = expr.left.constValue(program)?.number?.toInt()
|
val leftVal = left.asConstInteger()
|
||||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
val rightVal = right.asConstInteger()
|
||||||
if (leftVal!=null && leftVal in -4..4) {
|
if (leftVal!=null && leftVal in -4..4) {
|
||||||
translateExpressionInternal(expr.right)
|
translateExpressionInternal(right)
|
||||||
if(rightDt in ByteDatatypes) {
|
if(rightDt in ByteDatatypes) {
|
||||||
val incdec = if(leftVal<0) "dec" else "inc"
|
val incdec = if(leftVal<0) "dec" else "inc"
|
||||||
repeat(leftVal.absoluteValue) {
|
repeat(leftVal.absoluteValue) {
|
||||||
@ -282,11 +317,11 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
else if (rightVal!=null && rightVal in -4..4)
|
else if (rightVal!=null && rightVal in -4..4)
|
||||||
{
|
{
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
if(leftDt in ByteDatatypes) {
|
if(leftDt in ByteDatatypes) {
|
||||||
val incdec = if(rightVal<0) "dec" else "inc"
|
val incdec = if(rightVal<0) "dec" else "inc"
|
||||||
repeat(rightVal.absoluteValue) {
|
repeat(rightVal.absoluteValue) {
|
||||||
@ -312,16 +347,16 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"-" -> {
|
"-" -> {
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
val rightVal = right.asConstInteger()
|
||||||
if (rightVal!=null && rightVal in -4..4)
|
if (rightVal!=null && rightVal in -4..4)
|
||||||
{
|
{
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
if(leftDt in ByteDatatypes) {
|
if(leftDt in ByteDatatypes) {
|
||||||
val incdec = if(rightVal<0) "inc" else "dec"
|
val incdec = if(rightVal<0) "inc" else "dec"
|
||||||
repeat(rightVal.absoluteValue) {
|
repeat(rightVal.absoluteValue) {
|
||||||
@ -347,14 +382,14 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
">>" -> {
|
">>" -> {
|
||||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
val amount = right.asConstInteger()
|
||||||
if(amount!=null) {
|
if(amount!=null) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
when (leftDt) {
|
when (leftDt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if (amount <= 2)
|
if (amount <= 2)
|
||||||
@ -380,17 +415,17 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
|
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
|
||||||
else
|
else
|
||||||
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
|
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
var left = amount
|
var amountLeft = amount
|
||||||
while (left >= 7) {
|
while (amountLeft >= 7) {
|
||||||
asmgen.out(" jsr math.shift_right_uw_7")
|
asmgen.out(" jsr math.shift_right_uw_7")
|
||||||
left -= 7
|
amountLeft -= 7
|
||||||
}
|
}
|
||||||
if (left in 0..2)
|
if (amountLeft in 0..2)
|
||||||
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
repeat(amountLeft) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||||
else
|
else
|
||||||
asmgen.out(" jsr math.shift_right_uw_$left")
|
asmgen.out(" jsr math.shift_right_uw_$amountLeft")
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
if(amount>=16) {
|
if(amount>=16) {
|
||||||
@ -405,27 +440,27 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
sta P8ESTACK_LO+1,x
|
sta P8ESTACK_LO+1,x
|
||||||
sta P8ESTACK_HI+1,x
|
sta P8ESTACK_HI+1,x
|
||||||
+""")
|
+""")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
var left = amount
|
var amountLeft = amount
|
||||||
while (left >= 7) {
|
while (amountLeft >= 7) {
|
||||||
asmgen.out(" jsr math.shift_right_w_7")
|
asmgen.out(" jsr math.shift_right_w_7")
|
||||||
left -= 7
|
amountLeft -= 7
|
||||||
}
|
}
|
||||||
if (left in 0..2)
|
if (amountLeft in 0..2)
|
||||||
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
repeat(amountLeft) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||||
else
|
else
|
||||||
asmgen.out(" jsr math.shift_right_w_$left")
|
asmgen.out(" jsr math.shift_right_w_$amountLeft")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"<<" -> {
|
"<<" -> {
|
||||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
val amount = right.asConstInteger()
|
||||||
if(amount!=null) {
|
if(amount!=null) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
if (leftDt in ByteDatatypes) {
|
if (leftDt in ByteDatatypes) {
|
||||||
if (amount <= 2)
|
if (amount <= 2)
|
||||||
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
||||||
@ -435,78 +470,80 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var left = amount
|
var amountLeft = amount
|
||||||
while (left >= 7) {
|
while (amountLeft >= 7) {
|
||||||
asmgen.out(" jsr math.shift_left_w_7")
|
asmgen.out(" jsr math.shift_left_w_7")
|
||||||
left -= 7
|
amountLeft -= 7
|
||||||
}
|
}
|
||||||
if (left in 0..2)
|
if (amountLeft in 0..2)
|
||||||
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
repeat(amountLeft) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
||||||
else
|
else
|
||||||
asmgen.out(" jsr math.shift_left_w_$left")
|
asmgen.out(" jsr math.shift_left_w_$amountLeft")
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"*" -> {
|
"*" -> {
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
val leftVar = expr.left as? IdentifierReference
|
val leftVar = left as? PtIdentifier
|
||||||
val rightVar = expr.right as? IdentifierReference
|
val rightVar = right as? PtIdentifier
|
||||||
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
|
if(leftVar!=null && rightVar!=null && leftVar==rightVar) {
|
||||||
return translateSquared(leftVar, leftDt)
|
translateSquared(leftVar, leftDt)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val value = expr.right.constValue(program)
|
val value = right as? PtNumber
|
||||||
if(value!=null) {
|
if(value!=null) {
|
||||||
if(rightDt in IntegerDatatypes) {
|
if(rightDt in IntegerDatatypes) {
|
||||||
val amount = value.number.toInt()
|
val amount = value.number.toInt()
|
||||||
if(amount==2) {
|
if(amount==2) {
|
||||||
// optimize x*2 common case
|
// optimize x*2 common case
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
if(leftDt in ByteDatatypes) {
|
if(leftDt in ByteDatatypes) {
|
||||||
asmgen.out(" asl P8ESTACK_LO+1,x")
|
asmgen.out(" asl P8ESTACK_LO+1,x")
|
||||||
} else {
|
} else {
|
||||||
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
|
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
when(rightDt) {
|
when(rightDt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if(amount in asmgen.optimizedByteMultiplications) {
|
if(amount in asmgen.optimizedByteMultiplications) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
if(amount in asmgen.optimizedByteMultiplications) {
|
if(amount in asmgen.optimizedByteMultiplications) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
|
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
|
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if(amount in asmgen.optimizedWordMultiplications) {
|
if(amount in asmgen.optimizedWordMultiplications) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
if(amount in asmgen.optimizedWordMultiplications) {
|
if(amount in asmgen.optimizedWordMultiplications) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
|
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
|
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -516,9 +553,9 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
"/" -> {
|
"/" -> {
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
val rightVal = right.asConstInteger()
|
||||||
if(rightVal!=null && rightVal==2) {
|
if(rightVal!=null && rightVal==2) {
|
||||||
translateExpressionInternal(expr.left)
|
translateExpressionInternal(left)
|
||||||
when (leftDt) {
|
when (leftDt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
asmgen.out(" lsr P8ESTACK_LO+1,x")
|
asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||||
@ -551,41 +588,17 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird dt")
|
else -> throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in ComparisonOperators -> {
|
|
||||||
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
|
||||||
val rightVal = expr.right.constValue(program)?.number
|
|
||||||
if(rightVal==0.0)
|
|
||||||
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
return false
|
||||||
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
|
||||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
|
||||||
|
|
||||||
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in ComparisonOperators) {
|
|
||||||
translateCompareStrings(expr.left, expr.operator, expr.right)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
|
|
||||||
translateExpressionInternal(expr.left)
|
|
||||||
translateExpressionInternal(expr.right)
|
|
||||||
when (leftDt) {
|
|
||||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
|
||||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
|
||||||
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
|
||||||
else -> throw AssemblyError("non-numerical datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
|
private fun translateComparisonWithZero(expr: PtExpression, dt: DataType, operator: String) {
|
||||||
if(expr.isSimple) {
|
if(expr.isSimple()) {
|
||||||
if(operator=="!=") {
|
if(operator=="!=") {
|
||||||
when (dt) {
|
when (dt) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
@ -641,7 +654,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
"<" -> {
|
"<" -> {
|
||||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||||
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position))
|
return translateExpressionInternal(PtNumber.fromBoolean(false, expr.position))
|
||||||
when(dt) {
|
when(dt) {
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
|
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
|
||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
|
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
|
||||||
@ -662,7 +675,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
"<=" -> {
|
"<=" -> {
|
||||||
when(dt) {
|
when(dt) {
|
||||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzeros_b")
|
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzero_sb")
|
||||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
|
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
|
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
|
||||||
@ -671,7 +684,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
">=" -> {
|
">=" -> {
|
||||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||||
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position))
|
return translateExpressionInternal(PtNumber.fromBoolean(true, expr.position))
|
||||||
when(dt) {
|
when(dt) {
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
|
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
|
||||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
|
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
|
||||||
@ -683,7 +696,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
|
private fun translateSquared(variable: PtIdentifier, dt: DataType) {
|
||||||
val asmVar = asmgen.asmVariableName(variable)
|
val asmVar = asmgen.asmVariableName(variable)
|
||||||
when(dt) {
|
when(dt) {
|
||||||
DataType.BYTE, DataType.UBYTE -> {
|
DataType.BYTE, DataType.UBYTE -> {
|
||||||
@ -699,14 +712,12 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(expr: PrefixExpression) {
|
private fun translateExpression(expr: PtPrefix) {
|
||||||
translateExpressionInternal(expr.expression)
|
translateExpressionInternal(expr.value)
|
||||||
val itype = expr.inferType(program)
|
|
||||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
when(expr.operator) {
|
when(expr.operator) {
|
||||||
"+" -> {}
|
"+" -> {}
|
||||||
"-" -> {
|
"-" -> {
|
||||||
when(type) {
|
when(expr.type) {
|
||||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
||||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
||||||
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
|
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
|
||||||
@ -714,7 +725,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"~" -> {
|
"~" -> {
|
||||||
when(type) {
|
when(expr.type) {
|
||||||
in ByteDatatypes ->
|
in ByteDatatypes ->
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda P8ESTACK_LO+1,x
|
lda P8ESTACK_LO+1,x
|
||||||
@ -729,22 +740,18 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
private fun translateExpression(arrayExpr: PtArrayIndexer) {
|
||||||
val elementIDt = arrayExpr.inferType(program)
|
val elementDt = arrayExpr.type
|
||||||
if(!elementIDt.isKnown)
|
val arrayVarName = asmgen.asmVariableName(arrayExpr.variable)
|
||||||
throw AssemblyError("unknown dt")
|
|
||||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
|
||||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
|
|
||||||
|
|
||||||
val arrayVarDecl = arrayExpr.arrayvar.targetVarDecl(program)!!
|
if(arrayExpr.variable.type==DataType.UWORD) {
|
||||||
if(arrayVarDecl.datatype==DataType.UWORD) {
|
|
||||||
// indexing a pointer var instead of a real array or string
|
// indexing a pointer var instead of a real array or string
|
||||||
if(elementDt !in ByteDatatypes)
|
if(elementDt !in ByteDatatypes)
|
||||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||||
if(arrayExpr.inferType(program) isnot DataType.UBYTE)
|
if(arrayExpr.index.type != DataType.UBYTE)
|
||||||
throw AssemblyError("non-array var indexing requires bytes index")
|
throw AssemblyError("non-array var indexing requires bytes index")
|
||||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||||
if(asmgen.isZpVar(arrayExpr.arrayvar)) {
|
if(asmgen.isZpVar(arrayExpr.variable)) {
|
||||||
asmgen.out(" lda ($arrayVarName),y")
|
asmgen.out(" lda ($arrayVarName),y")
|
||||||
} else {
|
} else {
|
||||||
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
||||||
@ -754,7 +761,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val constIndexNum = arrayExpr.indexer.constIndex()
|
val constIndexNum = arrayExpr.index.asConstInteger()
|
||||||
if(constIndexNum!=null) {
|
if(constIndexNum!=null) {
|
||||||
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
|
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
|
||||||
when(elementDt) {
|
when(elementDt) {
|
||||||
@ -881,10 +888,14 @@ internal class ExpressionsAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
|
private fun translateCompareStrings(s1: PtExpression, operator: String, s2: PtExpression) {
|
||||||
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
|
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD)
|
||||||
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
|
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD)
|
||||||
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
|
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
|
||||||
|
compareStringsProcessResultInA(operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compareStringsProcessResultInA(operator: String) {
|
||||||
when(operator) {
|
when(operator) {
|
||||||
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
|
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
|
||||||
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
|
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
|
||||||
|
60
codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt
Normal file
60
codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
|
import prog8.code.ast.IPtSubroutine
|
||||||
|
import prog8.code.ast.PtAsmSub
|
||||||
|
import prog8.code.ast.PtSub
|
||||||
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
|
internal fun IPtSubroutine.regXasResult(): Boolean =
|
||||||
|
(this is PtAsmSub) && this.returns.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||||
|
|
||||||
|
internal fun IPtSubroutine.shouldSaveX(): Boolean =
|
||||||
|
this.regXasResult() || (this is PtAsmSub && (CpuRegister.X in this.clobbers || regXasParam()))
|
||||||
|
|
||||||
|
internal fun PtAsmSub.regXasParam(): Boolean =
|
||||||
|
parameters.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||||
|
|
||||||
|
internal class KeepAresult(val saveOnEntry: Boolean, val saveOnReturn: Boolean)
|
||||||
|
|
||||||
|
internal fun PtAsmSub.shouldKeepA(): KeepAresult {
|
||||||
|
// determine if A's value should be kept when preparing for calling the subroutine, and when returning from it
|
||||||
|
|
||||||
|
// it seems that we never have to save A when calling? will be loaded correctly after setup.
|
||||||
|
// but on return it depends on wether the routine returns something in A.
|
||||||
|
val saveAonReturn = returns.any { it.first.registerOrPair==RegisterOrPair.A || it.first.registerOrPair==RegisterOrPair.AY || it.first.registerOrPair==RegisterOrPair.AX }
|
||||||
|
return KeepAresult(false, saveAonReturn)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
|
||||||
|
when(this) {
|
||||||
|
is PtAsmSub -> {
|
||||||
|
return returns
|
||||||
|
}
|
||||||
|
is PtSub -> {
|
||||||
|
// for non-asm subroutines, determine the return registers based on the type of the return value
|
||||||
|
return if(returntype==null)
|
||||||
|
emptyList()
|
||||||
|
else {
|
||||||
|
val register = when (returntype!!) {
|
||||||
|
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||||
|
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||||
|
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
}
|
||||||
|
listOf(Pair(register, returntype!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun PtSub.returnRegister(): RegisterOrStatusflag? {
|
||||||
|
return when(returntype) {
|
||||||
|
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||||
|
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||||
|
null -> null
|
||||||
|
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +1,43 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import com.github.michaelbull.result.fold
|
import com.github.michaelbull.result.fold
|
||||||
import prog8.ast.Program
|
import prog8.code.ast.*
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.RangeExpression
|
|
||||||
import prog8.ast.statements.ForLoop
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen, private val zeropage: Zeropage) {
|
internal class ForLoopsAsmGen(private val program: PtProgram,
|
||||||
|
private val asmgen: AsmGen6502Internal,
|
||||||
|
private val zeropage: Zeropage) {
|
||||||
|
|
||||||
internal fun translate(stmt: ForLoop) {
|
internal fun translate(stmt: PtForLoop) {
|
||||||
val iterableDt = stmt.iterable.inferType(program)
|
val iterableDt = stmt.iterable.type
|
||||||
if(!iterableDt.isKnown)
|
|
||||||
throw AssemblyError("unknown dt")
|
|
||||||
when(stmt.iterable) {
|
when(stmt.iterable) {
|
||||||
is RangeExpression -> {
|
is PtRange -> {
|
||||||
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
|
val range = (stmt.iterable as PtRange).toConstantIntegerRange()
|
||||||
if(range==null) {
|
if(range==null) {
|
||||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
|
translateForOverNonconstRange(stmt, iterableDt, stmt.iterable as PtRange)
|
||||||
} else {
|
} else {
|
||||||
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
|
translateForOverConstRange(stmt, iterableDt, range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
|
translateForOverIterableVar(stmt, iterableDt, stmt.iterable as PtIdentifier)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
|
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
val modifiedLabel = program.makeLabel("for_modified")
|
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||||
val modifiedLabel2 = program.makeLabel("for_modifiedb")
|
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
val stepsize=range.step.asConstInteger()!!
|
||||||
|
|
||||||
if(stepsize < -1) {
|
if(stepsize < -1) {
|
||||||
val limit = range.to.constValue(program)?.number
|
val limit = range.to.asConstInteger()
|
||||||
if(limit==0.0)
|
if(limit==0)
|
||||||
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,11 +49,11 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
|
|
||||||
val incdec = if(stepsize==1) "inc" else "dec"
|
val incdec = if(stepsize==1) "inc" else "dec"
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt))
|
||||||
asmgen.out(loopLabel)
|
asmgen.out(loopLabel)
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
$modifiedLabel cmp #0 ; modified
|
$modifiedLabel cmp #0 ; modified
|
||||||
@ -70,11 +67,11 @@ $modifiedLabel cmp #0 ; modified
|
|||||||
// bytes, step >= 2 or <= -2
|
// bytes, step >= 2 or <= -2
|
||||||
|
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt))
|
||||||
asmgen.out(loopLabel)
|
asmgen.out(loopLabel)
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if(stepsize>0) {
|
if(stepsize>0) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
@ -102,14 +99,14 @@ $modifiedLabel cmp #0 ; modified
|
|||||||
// words, step 1 or -1
|
// words, step 1 or -1
|
||||||
|
|
||||||
stepsize == 1 || stepsize == -1 -> {
|
stepsize == 1 || stepsize == -1 -> {
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
assignLoopvar(stmt, range)
|
assignLoopvar(stmt, range)
|
||||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sty $modifiedLabel+1
|
sty $modifiedLabel+1
|
||||||
sta $modifiedLabel2+1
|
sta $modifiedLabel2+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname+1
|
lda $varname+1
|
||||||
$modifiedLabel cmp #0 ; modified
|
$modifiedLabel cmp #0 ; modified
|
||||||
@ -136,14 +133,14 @@ $modifiedLabel2 cmp #0 ; modified
|
|||||||
stepsize > 0 -> {
|
stepsize > 0 -> {
|
||||||
|
|
||||||
// (u)words, step >= 2
|
// (u)words, step >= 2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
assignLoopvar(stmt, range)
|
assignLoopvar(stmt, range)
|
||||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sty $modifiedLabel+1
|
sty $modifiedLabel+1
|
||||||
sta $modifiedLabel2+1
|
sta $modifiedLabel2+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
|
|
||||||
if (iterableDt == DataType.ARRAY_UW) {
|
if (iterableDt == DataType.ARRAY_UW) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -184,14 +181,14 @@ $endLabel""")
|
|||||||
else -> {
|
else -> {
|
||||||
|
|
||||||
// (u)words, step <= -2
|
// (u)words, step <= -2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
assignLoopvar(stmt, range)
|
assignLoopvar(stmt, range)
|
||||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sty $modifiedLabel+1
|
sty $modifiedLabel+1
|
||||||
sta $modifiedLabel2+1
|
sta $modifiedLabel2+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
|
|
||||||
if(iterableDt==DataType.ARRAY_UW) {
|
if(iterableDt==DataType.ARRAY_UW) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -237,12 +234,18 @@ $endLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
|
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val iterableName = asmgen.asmVariableName(ident)
|
val iterableName = asmgen.asmVariableName(ident)
|
||||||
val decl = ident.targetVarDecl(program)!!
|
val symbol = asmgen.symbolTable.lookup(ident.name)
|
||||||
|
val decl = symbol!!.astNode as IPtVariable
|
||||||
|
val numElements = when(decl) {
|
||||||
|
is PtConstant -> throw AssemblyError("length of non-array requested")
|
||||||
|
is PtMemMapped -> decl.arraySize
|
||||||
|
is PtVariable -> decl.arraySize
|
||||||
|
}
|
||||||
when(iterableDt) {
|
when(iterableDt) {
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -252,8 +255,8 @@ $endLabel""")
|
|||||||
sty $loopLabel+2
|
sty $loopLabel+2
|
||||||
$loopLabel lda ${65535.toHex()} ; modified
|
$loopLabel lda ${65535.toHex()} ; modified
|
||||||
beq $endLabel
|
beq $endLabel
|
||||||
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
inc $loopLabel+1
|
inc $loopLabel+1
|
||||||
bne $loopLabel
|
bne $loopLabel
|
||||||
@ -262,19 +265,18 @@ $loopLabel lda ${65535.toHex()} ; modified
|
|||||||
$endLabel""")
|
$endLabel""")
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
val length = decl.arraysize!!.constIndex()!!
|
val indexVar = asmgen.makeLabel("for_index")
|
||||||
val indexVar = program.makeLabel("for_index")
|
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy #0
|
ldy #0
|
||||||
$loopLabel sty $indexVar
|
$loopLabel sty $indexVar
|
||||||
lda $iterableName,y
|
lda $iterableName,y
|
||||||
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if(length<=255) {
|
if(numElements!!<=255u) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy $indexVar
|
ldy $indexVar
|
||||||
iny
|
iny
|
||||||
cpy #$length
|
cpy #$numElements
|
||||||
beq $endLabel
|
beq $endLabel
|
||||||
bne $loopLabel""")
|
bne $loopLabel""")
|
||||||
} else {
|
} else {
|
||||||
@ -285,9 +287,9 @@ $loopLabel sty $indexVar
|
|||||||
bne $loopLabel
|
bne $loopLabel
|
||||||
beq $endLabel""")
|
beq $endLabel""")
|
||||||
}
|
}
|
||||||
if(length>=16) {
|
if(numElements>=16u) {
|
||||||
// allocate index var on ZP if possible
|
// allocate index var on ZP if possible
|
||||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||||
result.fold(
|
result.fold(
|
||||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||||
failure = { asmgen.out("$indexVar .byte 0") }
|
failure = { asmgen.out("$indexVar .byte 0") }
|
||||||
@ -298,9 +300,9 @@ $loopLabel sty $indexVar
|
|||||||
asmgen.out(endLabel)
|
asmgen.out(endLabel)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
val length = decl.arraysize!!.constIndex()!! * 2
|
val length = numElements!! * 2u
|
||||||
val indexVar = program.makeLabel("for_index")
|
val indexVar = asmgen.makeLabel("for_index")
|
||||||
val loopvarName = asmgen.asmVariableName(stmt.loopVar)
|
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy #0
|
ldy #0
|
||||||
$loopLabel sty $indexVar
|
$loopLabel sty $indexVar
|
||||||
@ -308,8 +310,8 @@ $loopLabel sty $indexVar
|
|||||||
sta $loopvarName
|
sta $loopvarName
|
||||||
lda $iterableName+1,y
|
lda $iterableName+1,y
|
||||||
sta $loopvarName+1""")
|
sta $loopvarName+1""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if(length<=127) {
|
if(length<=127u) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy $indexVar
|
ldy $indexVar
|
||||||
iny
|
iny
|
||||||
@ -326,9 +328,9 @@ $loopLabel sty $indexVar
|
|||||||
bne $loopLabel
|
bne $loopLabel
|
||||||
beq $endLabel""")
|
beq $endLabel""")
|
||||||
}
|
}
|
||||||
if(length>=16) {
|
if(length>=16u) {
|
||||||
// allocate index var on ZP if possible
|
// allocate index var on ZP if possible
|
||||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||||
result.fold(
|
result.fold(
|
||||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||||
failure = { asmgen.out("$indexVar .byte 0") }
|
failure = { asmgen.out("$indexVar .byte 0") }
|
||||||
@ -346,7 +348,7 @@ $loopLabel sty $indexVar
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
|
||||||
if (range.isEmpty() || range.step==0)
|
if (range.isEmpty() || range.step==0)
|
||||||
throw AssemblyError("empty range or step 0")
|
throw AssemblyError("empty range or step 0")
|
||||||
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
|
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
|
||||||
@ -359,18 +361,18 @@ $loopLabel sty $indexVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// not one of the easy cases, generate more complex code...
|
// not one of the easy cases, generate more complex code...
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
when(iterableDt) {
|
when(iterableDt) {
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
// loop over byte range via loopvar, step >= 2 or <= -2
|
// loop over byte range via loopvar, step >= 2 or <= -2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #${range.first}
|
lda #${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
when (range.step) {
|
when (range.step) {
|
||||||
0, 1, -1 -> {
|
0, 1, -1 -> {
|
||||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||||
@ -430,7 +432,7 @@ $loopLabel""")
|
|||||||
}
|
}
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
// loop over word range via loopvar, step >= 2 or <= -2
|
// loop over word range via loopvar, step >= 2 or <= -2
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
when (range.step) {
|
when (range.step) {
|
||||||
0, 1, -1 -> {
|
0, 1, -1 -> {
|
||||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||||
@ -444,7 +446,7 @@ $loopLabel""")
|
|||||||
sta $varname
|
sta $varname
|
||||||
sty $varname+1
|
sty $varname+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
cmp #<${range.last}
|
cmp #<${range.last}
|
||||||
@ -470,16 +472,16 @@ $loopLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #${range.first}
|
lda #${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
if (range.last == 255) {
|
if (range.last == 255) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
inc $varname
|
inc $varname
|
||||||
@ -496,16 +498,16 @@ $endLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #${range.first}
|
lda #${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
when (range.last) {
|
when (range.last) {
|
||||||
0 -> {
|
0 -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -533,18 +535,18 @@ $endLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<${range.first}
|
lda #<${range.first}
|
||||||
ldy #>${range.first}
|
ldy #>${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
sty $varname+1
|
sty $varname+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
cmp #<${range.last}
|
cmp #<${range.last}
|
||||||
@ -560,18 +562,18 @@ $loopLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) {
|
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||||
val loopLabel = program.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
val endLabel = program.makeLabel("for_end")
|
val endLabel = asmgen.makeLabel("for_end")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.variable)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<${range.first}
|
lda #<${range.first}
|
||||||
ldy #>${range.first}
|
ldy #>${range.first}
|
||||||
sta $varname
|
sta $varname
|
||||||
sty $varname+1
|
sty $varname+1
|
||||||
$loopLabel""")
|
$loopLabel""")
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.statements)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $varname
|
lda $varname
|
||||||
cmp #<${range.last}
|
cmp #<${range.last}
|
||||||
@ -588,10 +590,9 @@ $loopLabel""")
|
|||||||
asmgen.loopEndLabels.pop()
|
asmgen.loopEndLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
|
private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
|
||||||
asmgen.assignExpressionToVariable(
|
asmgen.assignExpressionToVariable(
|
||||||
range.from,
|
range.from,
|
||||||
asmgen.asmVariableName(stmt.loopVar),
|
asmgen.asmVariableName(stmt.variable),
|
||||||
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
|
stmt.variable.type)
|
||||||
stmt.definingSubroutine)
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,6 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.code.ast.*
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.expressions.AddressOf
|
|
||||||
import prog8.ast.expressions.Expression
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.NumericLiteral
|
|
||||||
import prog8.ast.statements.FunctionCallStatement
|
|
||||||
import prog8.ast.statements.InlineAssembly
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.SubroutineParameter
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||||
@ -18,51 +8,60 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
|
|||||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||||
|
|
||||||
|
|
||||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||||
|
|
||||||
internal fun translateFunctionCallStatement(stmt: FunctionCallStatement) {
|
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||||
saveXbeforeCall(stmt)
|
saveXbeforeCall(stmt)
|
||||||
translateFunctionCall(stmt, false)
|
translateFunctionCall(stmt)
|
||||||
restoreXafterCall(stmt)
|
restoreXafterCall(stmt)
|
||||||
// just ignore any result values from the function call.
|
// just ignore any result values from the function call.
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun saveXbeforeCall(stmt: IFunctionCall) {
|
internal fun saveXbeforeCall(stmt: PtFunctionCall) {
|
||||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
val symbol = asmgen.symbolTable.lookup(stmt.name)
|
||||||
|
val sub = symbol!!.astNode as IPtSubroutine
|
||||||
if(sub.shouldSaveX()) {
|
if(sub.shouldSaveX()) {
|
||||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
if(sub is PtAsmSub) {
|
||||||
if(regSaveOnStack)
|
val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
if (regSaveOnStack)
|
||||||
else
|
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||||
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!)
|
else
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
|
||||||
|
} else
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
internal fun restoreXafterCall(stmt: PtFunctionCall) {
|
||||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
val symbol = asmgen.symbolTable.lookup(stmt.name)
|
||||||
|
val sub = symbol!!.astNode as IPtSubroutine
|
||||||
if(sub.shouldSaveX()) {
|
if(sub.shouldSaveX()) {
|
||||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
if(sub is PtAsmSub) {
|
||||||
if(regSaveOnStack)
|
val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
if (regSaveOnStack)
|
||||||
else
|
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||||
|
else
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
|
} else
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
|
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
|
||||||
(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) { // TODO remove isExpression unused parameter
|
internal fun translateFunctionCall(call: PtFunctionCall) {
|
||||||
// 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!!
|
||||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||||
|
|
||||||
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||||
val subAsmName = asmgen.asmSymbolName(call.target)
|
val sub = symbol!!.astNode as IPtSubroutine
|
||||||
|
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||||
|
|
||||||
if(sub.isAsmSubroutine) {
|
if(sub is PtAsmSub) {
|
||||||
argumentsViaRegisters(sub, call)
|
argumentsViaRegisters(sub, call)
|
||||||
if (sub.inline && asmgen.options.optimize) {
|
if (sub.inline && asmgen.options.optimize) {
|
||||||
// inline the subroutine.
|
// inline the subroutine.
|
||||||
@ -70,16 +69,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||||
// (this condition has been enforced by an ast check earlier)
|
// (this condition has been enforced by an ast check earlier)
|
||||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||||
sub.statements.forEach { asmgen.translate(it as InlineAssembly) }
|
sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
|
||||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||||
} else {
|
} else {
|
||||||
asmgen.out(" jsr $subAsmName")
|
asmgen.out(" jsr $subAsmName")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if(sub is PtSub) {
|
||||||
if(sub.inline)
|
|
||||||
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
|
||||||
|
|
||||||
if(optimizeIntArgsViaRegisters(sub)) {
|
if(optimizeIntArgsViaRegisters(sub)) {
|
||||||
if(sub.parameters.size==1) {
|
if(sub.parameters.size==1) {
|
||||||
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
|
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
|
||||||
@ -100,84 +96,82 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
asmgen.out(" jsr $subAsmName")
|
asmgen.out(" jsr $subAsmName")
|
||||||
}
|
}
|
||||||
|
else throw AssemblyError("invalid sub type")
|
||||||
|
|
||||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
|
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
|
||||||
if(sub.parameters.size==1) {
|
if(sub.parameters.size==1) {
|
||||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
|
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||||
} else {
|
} else {
|
||||||
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
|
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
|
||||||
registerArgsViaCpuStackEvaluation(call, sub)
|
registerArgsViaCpuStackEvaluation(call, sub)
|
||||||
} else {
|
} else {
|
||||||
asmsub6502ArgsEvalOrder(sub).forEach {
|
asmsub6502ArgsEvalOrder(sub).forEach {
|
||||||
val param = sub.parameters[it]
|
val param = sub.parameters[it]
|
||||||
val arg = call.args[it]
|
val arg = call.args[it]
|
||||||
argumentViaRegister(sub, IndexedValue(it, param), arg)
|
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerArgsViaCpuStackEvaluation(call: IFunctionCall, callee: Subroutine) {
|
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
|
||||||
// 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) { "register args only for asm subroutine ${callee.position}" }
|
|
||||||
if(callee.parameters.isEmpty())
|
if(callee.parameters.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
// use the cpu hardware stack as intermediate storage for the arguments.
|
// use the cpu hardware stack as intermediate storage for the arguments.
|
||||||
val argOrder = asmsub6502ArgsEvalOrder(callee)
|
val argOrder = asmsub6502ArgsEvalOrder(callee)
|
||||||
argOrder.reversed().forEach {
|
argOrder.reversed().forEach {
|
||||||
asmgen.pushCpuStack(callee.parameters[it].type, call.args[it])
|
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
|
||||||
}
|
}
|
||||||
argOrder.forEach {
|
argOrder.forEach {
|
||||||
val param = callee.parameters[it]
|
val param = callee.parameters[it]
|
||||||
val targetVar = callee.searchParameter(param.name)!!
|
asmgen.popCpuStack(callee, param.second, param.first)
|
||||||
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
|
private fun argumentViaVariable(sub: PtSub, parameter: PtSubroutineParameter, value: PtExpression) {
|
||||||
// pass parameter via a regular variable (not via registers)
|
// pass parameter via a regular variable (not via registers)
|
||||||
val valueIDt = value.inferType(program)
|
if(!isArgumentTypeCompatible(value.type, parameter.type))
|
||||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
if(!isArgumentTypeCompatible(valueDt, parameter.type))
|
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
|
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
|
||||||
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
|
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
|
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
|
||||||
// pass argument via a register parameter
|
// pass argument via a register parameter
|
||||||
val valueIDt = value.inferType(program)
|
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
|
||||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
|
val paramRegister: RegisterOrStatusflag = when(sub) {
|
||||||
|
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
|
||||||
|
is PtSub -> RegisterOrStatusflag(registerOverride!!, null)
|
||||||
|
}
|
||||||
val statusflag = paramRegister.statusflag
|
val statusflag = paramRegister.statusflag
|
||||||
val register = paramRegister.registerOrPair
|
val register = paramRegister.registerOrPair
|
||||||
val requiredDt = parameter.value.type
|
val requiredDt = parameter.value.type
|
||||||
if(requiredDt!=valueDt) {
|
if(requiredDt!=value.type) {
|
||||||
if(valueDt largerThan requiredDt)
|
if(value.type largerThan requiredDt)
|
||||||
throw AssemblyError("can only convert byte values to word param types")
|
throw AssemblyError("can only convert byte values to word param types")
|
||||||
}
|
}
|
||||||
if (statusflag!=null) {
|
if (statusflag!=null) {
|
||||||
if(requiredDt!=valueDt)
|
if(requiredDt!=value.type)
|
||||||
throw AssemblyError("for statusflag, byte value is required")
|
throw AssemblyError("for statusflag, byte value is required")
|
||||||
if (statusflag == Statusflag.Pc) {
|
if (statusflag == Statusflag.Pc) {
|
||||||
// this param needs to be set last, right before the jsr
|
// this param needs to be set last, right before the jsr
|
||||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||||
when(value) {
|
when(value) {
|
||||||
is NumericLiteral -> {
|
is PtNumber -> {
|
||||||
val carrySet = value.number.toInt() != 0
|
val carrySet = value.number.toInt() != 0
|
||||||
asmgen.out(if(carrySet) " sec" else " clc")
|
asmgen.out(if(carrySet) " sec" else " clc")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
val sourceName = asmgen.asmVariableName(value)
|
val sourceName = asmgen.asmVariableName(value)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
pha
|
pha
|
||||||
@ -202,22 +196,25 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
else {
|
else {
|
||||||
// via register or register pair
|
// via register or register pair
|
||||||
register!!
|
register!!
|
||||||
if(requiredDt largerThan valueDt) {
|
if(requiredDt largerThan value.type) {
|
||||||
// we need to sign extend the source, do this via temporary word variable
|
// we need to sign extend the source, do this via temporary word variable
|
||||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
|
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE)
|
||||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
|
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type)
|
||||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
|
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
|
||||||
} else {
|
} else {
|
||||||
|
val scope = value.definingISub()
|
||||||
val target: AsmAssignTarget =
|
val target: AsmAssignTarget =
|
||||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, sub, register = register)
|
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register)
|
||||||
else {
|
else {
|
||||||
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
||||||
AsmAssignTarget.fromRegisters(register, signed, sub, asmgen)
|
AsmAssignTarget.fromRegisters(register, signed, value.position, scope, asmgen)
|
||||||
}
|
}
|
||||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
val src = if(value.type in PassByReferenceDatatypes) {
|
||||||
if(value is IdentifierReference) {
|
if(value is PtIdentifier) {
|
||||||
val addr = AddressOf(value, Position.DUMMY)
|
val addr = PtAddressOf(Position.DUMMY)
|
||||||
|
addr.add(value)
|
||||||
|
addr.parent = sub as PtNode
|
||||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||||
} else {
|
} else {
|
||||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
@ -225,7 +222,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
} else {
|
} else {
|
||||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
}
|
}
|
||||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
|
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.code.ast.PtIdentifier
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.code.ast.PtNumber
|
||||||
import prog8.ast.expressions.NumericLiteral
|
import prog8.code.ast.PtPostIncrDecr
|
||||||
import prog8.ast.statements.PostIncrDecr
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class PostIncrDecrAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||||
internal fun translate(stmt: PostIncrDecr) {
|
internal fun translate(stmt: PtPostIncrDecr) {
|
||||||
val incr = stmt.operator=="++"
|
val incr = stmt.operator=="++"
|
||||||
val targetIdent = stmt.target.identifier
|
val targetIdent = stmt.target.identifier
|
||||||
val targetMemory = stmt.target.memoryAddress
|
val targetMemory = stmt.target.memory
|
||||||
val targetArrayIdx = stmt.target.arrayindexed
|
val targetArrayIdx = stmt.target.array
|
||||||
val scope = stmt.definingSubroutine
|
val scope = stmt.definingISub()
|
||||||
when {
|
when {
|
||||||
targetIdent!=null -> {
|
targetIdent!=null -> {
|
||||||
val what = asmgen.asmVariableName(targetIdent)
|
val what = asmgen.asmVariableName(targetIdent)
|
||||||
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
|
when (stmt.target.type) {
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
if(incr)
|
if(incr)
|
||||||
@ -38,12 +38,12 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetMemory!=null -> {
|
targetMemory!=null -> {
|
||||||
when (val addressExpr = targetMemory.addressExpression) {
|
when (val addressExpr = targetMemory.address) {
|
||||||
is NumericLiteral -> {
|
is PtNumber -> {
|
||||||
val what = addressExpr.number.toHex()
|
val what = addressExpr.number.toHex()
|
||||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
val what = asmgen.asmVariableName(addressExpr)
|
val what = asmgen.asmVariableName(addressExpr)
|
||||||
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
||||||
if(incr)
|
if(incr)
|
||||||
@ -62,9 +62,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetArrayIdx!=null -> {
|
targetArrayIdx!=null -> {
|
||||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.variable)
|
||||||
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
|
val elementDt = targetArrayIdx.type
|
||||||
val constIndex = targetArrayIdx.indexer.constIndex()
|
val constIndex = targetArrayIdx.index.asConstInteger()
|
||||||
if(constIndex!=null) {
|
if(constIndex!=null) {
|
||||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||||
when(elementDt) {
|
when(elementDt) {
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
import prog8.code.*
|
import prog8.code.*
|
||||||
|
import prog8.code.ast.*
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||||
import prog8.compiler.CallGraph
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
@ -20,30 +18,26 @@ import kotlin.math.absoluteValue
|
|||||||
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
|
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
|
||||||
*/
|
*/
|
||||||
internal class ProgramAndVarsGen(
|
internal class ProgramAndVarsGen(
|
||||||
val program: Program,
|
val program: PtProgram,
|
||||||
val options: CompilationOptions,
|
val options: CompilationOptions,
|
||||||
val errors: IErrorReporter,
|
val errors: IErrorReporter,
|
||||||
private val symboltable: SymbolTable,
|
private val symboltable: SymbolTable,
|
||||||
private val functioncallAsmGen: FunctionCallAsmGen,
|
private val functioncallAsmGen: FunctionCallAsmGen,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen6502Internal,
|
||||||
private val allocator: VariableAllocator,
|
private val allocator: VariableAllocator,
|
||||||
private val zeropage: Zeropage
|
private val zeropage: Zeropage
|
||||||
) {
|
) {
|
||||||
private val compTarget = options.compTarget
|
private val compTarget = options.compTarget
|
||||||
private val callGraph = CallGraph(program, true)
|
private val blockVariableInitializers = program.allBlocks().associateWith { it.children.filterIsInstance<PtAssignment>() }
|
||||||
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
|
|
||||||
|
|
||||||
internal fun generate() {
|
internal fun generate() {
|
||||||
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
|
|
||||||
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
|
|
||||||
|
|
||||||
header()
|
header()
|
||||||
val allBlocks = program.allBlocks
|
val allBlocks = program.allBlocks()
|
||||||
if(allBlocks.first().name != "main")
|
if(allBlocks.first().name != "main")
|
||||||
throw AssemblyError("first block should be 'main'")
|
throw AssemblyError("first block should be 'main'")
|
||||||
|
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
program.allBlocks.forEach { block2asm(it) }
|
program.allBlocks().forEach { block2asm(it) }
|
||||||
|
|
||||||
// the global list of all floating point constants for the whole program
|
// the global list of all floating point constants for the whole program
|
||||||
asmgen.out("; global float constants")
|
asmgen.out("; global float constants")
|
||||||
@ -71,7 +65,7 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
|
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
|
||||||
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out(".cpu '$cpu'\n.enc 'none'\n")
|
asmgen.out(".cpu '$cpu'\n.enc 'none'")
|
||||||
|
|
||||||
// the global prog8 variables needed
|
// the global prog8 variables needed
|
||||||
val zp = zeropage
|
val zp = zeropage
|
||||||
@ -94,13 +88,13 @@ internal class ProgramAndVarsGen(
|
|||||||
when(options.output) {
|
when(options.output) {
|
||||||
OutputType.RAW -> {
|
OutputType.RAW -> {
|
||||||
asmgen.out("; ---- raw assembler program ----")
|
asmgen.out("; ---- raw assembler program ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
}
|
}
|
||||||
OutputType.PRG -> {
|
OutputType.PRG -> {
|
||||||
when(options.launcher) {
|
when(options.launcher) {
|
||||||
CbmPrgLauncherType.BASIC -> {
|
CbmPrgLauncherType.BASIC -> {
|
||||||
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
|
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
|
||||||
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.toplevelModule.position)
|
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position)
|
||||||
}
|
}
|
||||||
asmgen.out("; ---- basic program with sys call ----")
|
asmgen.out("; ---- basic program with sys call ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
@ -108,14 +102,14 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out(" .word (+), $year")
|
asmgen.out(" .word (+), $year")
|
||||||
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||||
asmgen.out("+\t.word 0")
|
asmgen.out("+\t.word 0")
|
||||||
asmgen.out("prog8_entrypoint\t; assembly code starts here\n")
|
asmgen.out("prog8_entrypoint\t; assembly code starts here")
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||||
}
|
}
|
||||||
CbmPrgLauncherType.NONE -> {
|
CbmPrgLauncherType.NONE -> {
|
||||||
asmgen.out("; ---- program without basic sys call ----")
|
asmgen.out("; ---- program without basic sys call ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||||
@ -124,7 +118,7 @@ internal class ProgramAndVarsGen(
|
|||||||
}
|
}
|
||||||
OutputType.XEX -> {
|
OutputType.XEX -> {
|
||||||
asmgen.out("; ---- atari xex program ----")
|
asmgen.out("; ---- atari xex program ----")
|
||||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||||
@ -156,8 +150,7 @@ internal class ProgramAndVarsGen(
|
|||||||
asmgen.out(" rts")
|
asmgen.out(" rts")
|
||||||
}
|
}
|
||||||
"c128" -> {
|
"c128" -> {
|
||||||
asmgen.out(" jsr main.start")
|
asmgen.out(" jsr main.start | lda #0 | sta ${"$"}ff00")
|
||||||
// TODO c128: how to bank basic+kernal back in?
|
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||||
else
|
else
|
||||||
@ -168,62 +161,89 @@ internal class ProgramAndVarsGen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun memorySlabs() {
|
private fun memorySlabs() {
|
||||||
asmgen.out("; memory slabs")
|
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||||
asmgen.out("prog8_slabs\t.block")
|
asmgen.out("; memory slabs\n .section slabs_BSS")
|
||||||
for(slab in symboltable.allMemorySlabs) {
|
asmgen.out("prog8_slabs\t.block")
|
||||||
if(slab.align>1u)
|
for (slab in symboltable.allMemorySlabs) {
|
||||||
asmgen.out("\t.align ${slab.align.toHex()}")
|
if (slab.align > 1u)
|
||||||
asmgen.out("${slab.name}\t.fill ${slab.size}")
|
asmgen.out("\t.align ${slab.align.toHex()}")
|
||||||
|
asmgen.out("${slab.name}\t.fill ${slab.size}")
|
||||||
|
}
|
||||||
|
asmgen.out("\t.bend\n .send slabs_BSS")
|
||||||
}
|
}
|
||||||
asmgen.out("\t.bend")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun footer() {
|
private fun footer() {
|
||||||
// program end
|
asmgen.out("; bss sections")
|
||||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
if(options.varsHigh) {
|
||||||
|
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
|
||||||
|
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
|
||||||
|
}
|
||||||
|
// BSS vars in high ram area, memory() slabs just concatenated at the end of the program.
|
||||||
|
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||||
|
asmgen.out(" .dsection slabs_BSS")
|
||||||
|
}
|
||||||
|
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||||
|
asmgen.out(" * = ${options.compTarget.machine.BSSHIGHRAM_START.toHex()}")
|
||||||
|
asmgen.out("prog8_bss_section_start")
|
||||||
|
asmgen.out(" .dsection BSS")
|
||||||
|
asmgen.out(" .cerror * >= ${options.compTarget.machine.BSSHIGHRAM_END.toHex()}, \"too many variables for BSS section\"")
|
||||||
|
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||||
|
} else {
|
||||||
|
// BSS vars followed by memory() slabs, concatenated at the end of the program.
|
||||||
|
asmgen.out("prog8_bss_section_start")
|
||||||
|
asmgen.out(" .dsection BSS")
|
||||||
|
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||||
|
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||||
|
asmgen.out(" .dsection slabs_BSS")
|
||||||
|
}
|
||||||
|
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun block2asm(block: Block) {
|
private fun block2asm(block: PtBlock) {
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out("; ---- block: '${block.name}' ----")
|
asmgen.out("; ---- block: '${block.name}' ----")
|
||||||
if(block.address!=null)
|
if(block.address!=null)
|
||||||
asmgen.out("* = ${block.address!!.toHex()}")
|
asmgen.out("* = ${block.address!!.toHex()}")
|
||||||
else {
|
else {
|
||||||
if("align_word" in block.options())
|
if(block.alignment==PtBlock.BlockAlignment.WORD)
|
||||||
asmgen.out("\t.align 2")
|
asmgen.out("\t.align 2")
|
||||||
else if("align_page" in block.options())
|
else if(block.alignment==PtBlock.BlockAlignment.PAGE)
|
||||||
asmgen.out("\t.align $100")
|
asmgen.out("\t.align $100")
|
||||||
}
|
}
|
||||||
|
|
||||||
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
|
asmgen.out("${block.name}\t" + (if(block.forceOutput) ".block" else ".proc"))
|
||||||
asmgen.outputSourceLine(block)
|
asmgen.outputSourceLine(block)
|
||||||
|
|
||||||
createBlockVariables(block)
|
createBlockVariables(block)
|
||||||
asmsubs2asm(block.statements)
|
asmsubs2asm(block.children)
|
||||||
|
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
|
|
||||||
val initializers = blockVariableInitializers.getValue(block)
|
val initializers = blockVariableInitializers.getValue(block)
|
||||||
val notInitializers = block.statements.filterNot { it in initializers }
|
val notInitializers = block.children.filterNot { it in initializers }
|
||||||
notInitializers.forEach { asmgen.translate(it) }
|
notInitializers.forEach { asmgen.translate(it) }
|
||||||
|
|
||||||
if(!options.dontReinitGlobals) {
|
// generate subroutine to initialize block-level (global) variables
|
||||||
// generate subroutine to initialize block-level (global) variables
|
if (initializers.isNotEmpty()) {
|
||||||
if (initializers.isNotEmpty()) {
|
asmgen.out("prog8_init_vars\t.block")
|
||||||
asmgen.out("prog8_init_vars\t.block\n")
|
initializers.forEach { assign ->
|
||||||
initializers.forEach { assign -> asmgen.translate(assign) }
|
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
|
||||||
asmgen.out(" rts\n .bend")
|
asmgen.translate(assign)
|
||||||
|
// the other variables that should be set to zero are done so as part of the BSS section.
|
||||||
}
|
}
|
||||||
|
asmgen.out(" rts\n .bend")
|
||||||
}
|
}
|
||||||
|
|
||||||
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
asmgen.out(if(block.forceOutput) "\n\t.bend" else "\n\t.pend")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVars(scope: StNode): Map<String, StNode> =
|
private fun getVars(scope: StNode): Map<String, StNode> =
|
||||||
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: PtBlock) {
|
||||||
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)
|
||||||
|
|
||||||
@ -247,18 +267,10 @@ internal class ProgramAndVarsGen(
|
|||||||
nonZpVariables2asm(variables)
|
nonZpVariables2asm(variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun translateSubroutine(sub: Subroutine) {
|
internal fun translateAsmSubroutine(sub: PtAsmSub) {
|
||||||
var onlyVariables = false
|
|
||||||
|
|
||||||
if(sub.inline) {
|
if(sub.inline) {
|
||||||
if(options.optimize) {
|
if(options.optimize) {
|
||||||
if(sub.isAsmSubroutine || callGraph.unused(sub))
|
return
|
||||||
return
|
|
||||||
|
|
||||||
// from an inlined subroutine only the local variables are generated,
|
|
||||||
// all other code statements are omitted in the subroutine itself
|
|
||||||
// (they've been inlined at the call site, remember?)
|
|
||||||
onlyVariables = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +278,7 @@ internal class ProgramAndVarsGen(
|
|||||||
|
|
||||||
val asmStartScope: String
|
val asmStartScope: String
|
||||||
val asmEndScope: String
|
val asmEndScope: String
|
||||||
if(sub.definingBlock.options().contains("force_output")) {
|
if(sub.definingBlock()!!.forceOutput) {
|
||||||
asmStartScope = ".block"
|
asmStartScope = ".block"
|
||||||
asmEndScope = ".bend"
|
asmEndScope = ".bend"
|
||||||
} else {
|
} else {
|
||||||
@ -274,105 +286,130 @@ internal class ProgramAndVarsGen(
|
|||||||
asmEndScope = ".pend"
|
asmEndScope = ".pend"
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sub.isAsmSubroutine) {
|
if(sub.address!=null)
|
||||||
if(sub.asmAddress!=null)
|
return // already done at the memvars section
|
||||||
return // already done at the memvars section
|
|
||||||
|
|
||||||
// asmsub with most likely just an inline asm in it
|
// asmsub with most likely just an inline asm in it
|
||||||
asmgen.out("${sub.name}\t$asmStartScope")
|
asmgen.out("${sub.name}\t$asmStartScope")
|
||||||
sub.statements.forEach { asmgen.translate(it) }
|
sub.children.forEach { asmgen.translate(it) }
|
||||||
asmgen.out(" $asmEndScope\n")
|
asmgen.out(" $asmEndScope")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun translateSubroutine(sub: PtSub) {
|
||||||
|
asmgen.out("")
|
||||||
|
|
||||||
|
val asmStartScope: String
|
||||||
|
val asmEndScope: String
|
||||||
|
if(sub.definingBlock()!!.forceOutput) {
|
||||||
|
asmStartScope = ".block"
|
||||||
|
asmEndScope = ".bend"
|
||||||
} else {
|
} else {
|
||||||
// regular subroutine
|
asmStartScope = ".proc"
|
||||||
asmgen.out("${sub.name}\t$asmStartScope")
|
asmEndScope = ".pend"
|
||||||
|
|
||||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
|
||||||
require(scope.type==StNodeType.SUBROUTINE)
|
|
||||||
val varsInSubroutine = getVars(scope)
|
|
||||||
|
|
||||||
// Zeropage Variables
|
|
||||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
|
|
||||||
zeropagevars2asm(varnames)
|
|
||||||
|
|
||||||
// MemDefs and Consts
|
|
||||||
val mvs = varsInSubroutine
|
|
||||||
.filter { it.value.type==StNodeType.MEMVAR }
|
|
||||||
.map { it.value as StMemVar }
|
|
||||||
val consts = varsInSubroutine
|
|
||||||
.filter { it.value.type==StNodeType.CONSTANT }
|
|
||||||
.map { it.value as StConstant }
|
|
||||||
memdefsAndConsts2asm(mvs, consts)
|
|
||||||
|
|
||||||
asmsubs2asm(sub.statements)
|
|
||||||
|
|
||||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
|
||||||
if(sub.name=="start" && sub.definingBlock.name=="main")
|
|
||||||
entrypointInitialization()
|
|
||||||
|
|
||||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
|
||||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
|
||||||
if(sub.parameters.size==1) {
|
|
||||||
val dt = sub.parameters[0].type
|
|
||||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
|
||||||
if(dt in ByteDatatypes)
|
|
||||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
|
||||||
else
|
|
||||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
|
||||||
} else {
|
|
||||||
require(sub.parameters.size==2)
|
|
||||||
// 2 simple byte args, first in A, second in Y
|
|
||||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
|
||||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
|
||||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
|
||||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!onlyVariables) {
|
|
||||||
asmgen.out("; statements")
|
|
||||||
sub.statements.forEach { asmgen.translate(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
asmgen.out("; variables")
|
|
||||||
val asmGenInfo = asmgen.subroutineExtra(sub)
|
|
||||||
for((dt, name, addr) in asmGenInfo.extraVars) {
|
|
||||||
if(addr!=null)
|
|
||||||
asmgen.out("$name = $addr")
|
|
||||||
else when(dt) {
|
|
||||||
DataType.UBYTE -> asmgen.out("$name .byte 0")
|
|
||||||
DataType.UWORD -> asmgen.out("$name .word 0")
|
|
||||||
else -> throw AssemblyError("weird dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(asmGenInfo.usedRegsaveA) // will probably never occur
|
|
||||||
asmgen.out("prog8_regsaveA .byte 0")
|
|
||||||
if(asmGenInfo.usedRegsaveX)
|
|
||||||
asmgen.out("prog8_regsaveX .byte 0")
|
|
||||||
if(asmGenInfo.usedRegsaveY)
|
|
||||||
asmgen.out("prog8_regsaveY .byte 0")
|
|
||||||
if(asmGenInfo.usedFloatEvalResultVar1)
|
|
||||||
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
|
||||||
if(asmGenInfo.usedFloatEvalResultVar2)
|
|
||||||
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
|
||||||
|
|
||||||
// normal statically allocated variables
|
|
||||||
val variables = varsInSubroutine
|
|
||||||
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
|
||||||
.map { it.value as StStaticVariable }
|
|
||||||
nonZpVariables2asm(variables)
|
|
||||||
|
|
||||||
asmgen.out(" $asmEndScope\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmgen.out("${sub.name}\t$asmStartScope")
|
||||||
|
|
||||||
|
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
||||||
|
require(scope.type==StNodeType.SUBROUTINE)
|
||||||
|
val varsInSubroutine = getVars(scope)
|
||||||
|
|
||||||
|
// Zeropage Variables
|
||||||
|
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
|
||||||
|
zeropagevars2asm(varnames)
|
||||||
|
|
||||||
|
// MemDefs and Consts
|
||||||
|
val mvs = varsInSubroutine
|
||||||
|
.filter { it.value.type==StNodeType.MEMVAR }
|
||||||
|
.map { it.value as StMemVar }
|
||||||
|
val consts = varsInSubroutine
|
||||||
|
.filter { it.value.type==StNodeType.CONSTANT }
|
||||||
|
.map { it.value as StConstant }
|
||||||
|
memdefsAndConsts2asm(mvs, consts)
|
||||||
|
|
||||||
|
asmsubs2asm(sub.children)
|
||||||
|
|
||||||
|
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||||
|
if(sub.name=="start" && sub.definingBlock()!!.name=="main")
|
||||||
|
entrypointInitialization()
|
||||||
|
|
||||||
|
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||||
|
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||||
|
if(sub.parameters.size==1) {
|
||||||
|
val dt = sub.parameters[0].type
|
||||||
|
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
|
||||||
|
if(dt in ByteDatatypes)
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||||
|
else
|
||||||
|
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||||
|
} else {
|
||||||
|
require(sub.parameters.size==2)
|
||||||
|
// 2 simple byte args, first in A, second in Y
|
||||||
|
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
|
||||||
|
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, sub.parameters[1].position, variableAsmName = sub.parameters[1].name)
|
||||||
|
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||||
|
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmgen.out("; statements")
|
||||||
|
sub.children.forEach { asmgen.translate(it) }
|
||||||
|
|
||||||
|
asmgen.out("; variables")
|
||||||
|
asmgen.out(" .section BSS")
|
||||||
|
val asmGenInfo = asmgen.subroutineExtra(sub)
|
||||||
|
for((dt, name, addr) in asmGenInfo.extraVars) {
|
||||||
|
if(addr!=null)
|
||||||
|
asmgen.out("$name = $addr")
|
||||||
|
else when(dt) {
|
||||||
|
DataType.UBYTE -> asmgen.out("$name .byte ?")
|
||||||
|
DataType.UWORD -> asmgen.out("$name .word ?")
|
||||||
|
DataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
else -> throw AssemblyError("weird dt for extravar $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(asmGenInfo.usedRegsaveA) // will probably never occur
|
||||||
|
asmgen.out("prog8_regsaveA .byte ?")
|
||||||
|
if(asmGenInfo.usedRegsaveX)
|
||||||
|
asmgen.out("prog8_regsaveX .byte ?")
|
||||||
|
if(asmGenInfo.usedRegsaveY)
|
||||||
|
asmgen.out("prog8_regsaveY .byte ?")
|
||||||
|
if(asmGenInfo.usedFloatEvalResultVar1)
|
||||||
|
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
if(asmGenInfo.usedFloatEvalResultVar2)
|
||||||
|
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
asmgen.out(" .send BSS")
|
||||||
|
|
||||||
|
// normal statically allocated variables
|
||||||
|
val variables = varsInSubroutine
|
||||||
|
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
||||||
|
.map { it.value as StStaticVariable }
|
||||||
|
nonZpVariables2asm(variables)
|
||||||
|
|
||||||
|
asmgen.out(" $asmEndScope")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun entrypointInitialization() {
|
private fun entrypointInitialization() {
|
||||||
asmgen.out("; program startup initialization")
|
asmgen.out("; program startup initialization")
|
||||||
asmgen.out(" cld")
|
asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
|
||||||
if(!options.dontReinitGlobals) {
|
// set full BSS area to zero
|
||||||
blockVariableInitializers.forEach {
|
asmgen.out("""
|
||||||
if (it.value.isNotEmpty())
|
.if prog8_bss_section_size>0
|
||||||
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
; reset all variables in BSS section to zero
|
||||||
}
|
lda #<prog8_bss_section_start
|
||||||
|
ldy #>prog8_bss_section_start
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldx #<prog8_bss_section_size
|
||||||
|
ldy #>prog8_bss_section_size
|
||||||
|
lda #0
|
||||||
|
jsr prog8_lib.memset
|
||||||
|
.endif""")
|
||||||
|
|
||||||
|
blockVariableInitializers.forEach {
|
||||||
|
if (it.value.isNotEmpty())
|
||||||
|
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
||||||
}
|
}
|
||||||
|
|
||||||
// string and array variables in zeropage that have initializer value, should be initialized
|
// string and array variables in zeropage that have initializer value, should be initialized
|
||||||
@ -420,21 +457,20 @@ internal class ProgramAndVarsGen(
|
|||||||
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
|
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
asmgen.out("""+ tsx
|
asmgen.out("""+
|
||||||
stx prog8_lib.orig_stackpointer ; required for sys.exit()
|
ldx #127 ; init estack ptr (half page)
|
||||||
ldx #255 ; init estack ptr
|
|
||||||
clv
|
clv
|
||||||
clc""")
|
clc""")
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ZpStringWithInitial(
|
private class ZpStringWithInitial(
|
||||||
val name: List<String>,
|
val name: String,
|
||||||
val alloc: MemoryAllocator.VarAllocation,
|
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: MemoryAllocator.VarAllocation,
|
val alloc: MemoryAllocator.VarAllocation,
|
||||||
val value: StArray
|
val value: StArray
|
||||||
)
|
)
|
||||||
@ -443,9 +479,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
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@ -454,36 +491,69 @@ 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
|
||||||
|
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 zpVariables = allocator.zeropageVars.filter { it.key in varNames }.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.startsWith("cx16.r"))
|
||||||
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
|
||||||
asmgen.out("${scopedName.last()} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
|
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
|
||||||
asmgen.out("")
|
asmgen.out("")
|
||||||
asmgen.out("; non-zeropage variables")
|
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
|
||||||
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
|
if(varsNoInit.isNotEmpty()) {
|
||||||
stringvars.forEach {
|
asmgen.out("; non-zeropage variables without initialization value")
|
||||||
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
|
asmgen.out(" .section BSS")
|
||||||
|
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
|
||||||
|
uninitializedVariable2asm(it)
|
||||||
|
}
|
||||||
|
asmgen.out(" .send BSS")
|
||||||
}
|
}
|
||||||
othervars.sortedBy { it.type }.forEach {
|
|
||||||
staticVariable2asm(it)
|
if(varsWithInit.isNotEmpty()) {
|
||||||
|
asmgen.out("; non-zeropage variables")
|
||||||
|
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
|
||||||
|
stringvars.forEach {
|
||||||
|
outputStringvar(
|
||||||
|
it.name,
|
||||||
|
it.onetimeInitializationStringValue!!.second,
|
||||||
|
it.onetimeInitializationStringValue!!.first
|
||||||
|
)
|
||||||
|
}
|
||||||
|
othervars.sortedBy { it.type }.forEach {
|
||||||
|
staticVariable2asm(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uninitializedVariable2asm(variable: StStaticVariable) {
|
||||||
|
when (variable.dt) {
|
||||||
|
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ?")
|
||||||
|
DataType.BYTE -> asmgen.out("${variable.name}\t.char ?")
|
||||||
|
DataType.UWORD -> asmgen.out("${variable.name}\t.word ?")
|
||||||
|
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
|
||||||
|
DataType.FLOAT -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
|
||||||
|
in ArrayDatatypes -> {
|
||||||
|
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
|
||||||
|
asmgen.out("${variable.name}\t.fill $numbytes")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun staticVariable2asm(variable: StStaticVariable) {
|
private fun staticVariable2asm(variable: StStaticVariable) {
|
||||||
val name = variable.name
|
|
||||||
val initialValue: Number =
|
val initialValue: Number =
|
||||||
if(variable.onetimeInitializationNumericValue!=null) {
|
if(variable.onetimeInitializationNumericValue!=null) {
|
||||||
if(variable.dt== DataType.FLOAT)
|
if(variable.dt== DataType.FLOAT)
|
||||||
@ -493,22 +563,22 @@ internal class ProgramAndVarsGen(
|
|||||||
} else 0
|
} else 0
|
||||||
|
|
||||||
when (variable.dt) {
|
when (variable.dt) {
|
||||||
DataType.UBYTE -> asmgen.out("$name\t.byte ${initialValue.toHex()}")
|
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
|
||||||
DataType.BYTE -> asmgen.out("$name\t.char $initialValue")
|
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
|
||||||
DataType.UWORD -> asmgen.out("$name\t.word ${initialValue.toHex()}")
|
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
|
||||||
DataType.WORD -> asmgen.out("$name\t.sint $initialValue")
|
DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if(initialValue==0) {
|
if(initialValue==0) {
|
||||||
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
|
asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
|
||||||
} else {
|
} else {
|
||||||
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
|
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
|
||||||
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
|
asmgen.out("${variable.name}\t.byte $floatFill ; float $initialValue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
throw AssemblyError("all string vars should have been interned into prog")
|
throw AssemblyError("all string vars should have been interned into prog")
|
||||||
}
|
}
|
||||||
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
||||||
else -> {
|
else -> {
|
||||||
throw AssemblyError("weird dt")
|
throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
@ -579,10 +649,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
|
||||||
@ -590,12 +660,12 @@ internal class ProgramAndVarsGen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun asmsubs2asm(statements: List<Statement>) {
|
private fun asmsubs2asm(statements: List<PtNode>) {
|
||||||
statements
|
statements
|
||||||
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null }
|
.filter { it is PtAsmSub && it.address!=null }
|
||||||
.forEach { asmsub ->
|
.forEach { asmsub ->
|
||||||
asmsub as Subroutine
|
asmsub as PtAsmSub
|
||||||
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}")
|
asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,8 +690,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,14 @@ 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>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
|
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
allocateZeropageVariables()
|
allocateZeropageVariables()
|
||||||
|
zeropageVars = zeropage.allocatedVariables
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
|
internal fun isZpVar(scopedName: String) = scopedName in zeropageVars
|
||||||
|
|
||||||
internal fun getFloatAsmConst(number: Double): String {
|
internal fun getFloatAsmConst(number: Double): String {
|
||||||
val asmName = globalFloatConsts[number]
|
val asmName = globalFloatConsts[number]
|
||||||
@ -59,7 +60,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
variable.scopedName,
|
variable.scopedName,
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.astNode.position,
|
||||||
errors
|
errors
|
||||||
)
|
)
|
||||||
result.fold(
|
result.fold(
|
||||||
@ -67,7 +68,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
numVariablesAllocatedInZP++
|
numVariablesAllocatedInZP++
|
||||||
},
|
},
|
||||||
failure = {
|
failure = {
|
||||||
errors.err(it.message!!, variable.position)
|
errors.err(it.message!!, variable.astNode.position)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
variable.scopedName,
|
variable.scopedName,
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.astNode.position,
|
||||||
errors
|
errors
|
||||||
)
|
)
|
||||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||||
@ -88,7 +89,8 @@ 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
|
||||||
@ -97,7 +99,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
variable.scopedName,
|
variable.scopedName,
|
||||||
variable.dt,
|
variable.dt,
|
||||||
variable.length,
|
variable.length,
|
||||||
variable.position,
|
variable.astNode.position,
|
||||||
errors
|
errors
|
||||||
)
|
)
|
||||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||||
@ -124,6 +126,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
collect(st)
|
collect(st)
|
||||||
return vars
|
return vars.sortedBy { it.dt }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
package prog8.codegen.cpu6502.assignment
|
package prog8.codegen.cpu6502.assignment
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.code.ast.*
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.DirectMemoryWrite
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.AsmGen
|
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||||
|
import prog8.codegen.cpu6502.returnsWhatWhere
|
||||||
|
|
||||||
|
|
||||||
internal enum class TargetStorageKind {
|
internal enum class TargetStorageKind {
|
||||||
@ -29,68 +25,65 @@ internal enum class SourceStorageKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen6502Internal,
|
||||||
val datatype: DataType,
|
val datatype: DataType,
|
||||||
val scope: Subroutine?,
|
val scope: IPtSubroutine?,
|
||||||
|
val position: Position,
|
||||||
private val variableAsmName: String? = null,
|
private val variableAsmName: String? = null,
|
||||||
val array: ArrayIndexedExpression? = null,
|
val array: PtArrayIndexer? = null,
|
||||||
val memory: DirectMemoryWrite? = null,
|
val memory: PtMemoryByte? = null,
|
||||||
val register: RegisterOrPair? = null,
|
val register: RegisterOrPair? = null,
|
||||||
val origAstTarget: AssignTarget? = null
|
val origAstTarget: PtAssignTarget? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
val constArrayIndexValue by lazy { array?.index?.asConstInteger()?.toUInt() }
|
||||||
val asmVarname: String by lazy {
|
val asmVarname: String by lazy {
|
||||||
if (array == null)
|
if (array == null)
|
||||||
variableAsmName!!
|
variableAsmName!!
|
||||||
else
|
else
|
||||||
asmgen.asmVariableName(array.arrayvar)
|
asmgen.asmVariableName(array.variable)
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var origAssign: AsmAssignment
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(register!=null && datatype !in NumericDatatypes)
|
if(register!=null && datatype !in NumericDatatypes)
|
||||||
throw AssemblyError("register must be integer or float type")
|
throw AssemblyError("register must be integer or float type")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
|
fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget {
|
||||||
with(assign.target) {
|
with(target) {
|
||||||
val idt = inferType(program)
|
|
||||||
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
|
|
||||||
when {
|
when {
|
||||||
identifier != null -> {
|
identifier != null -> {
|
||||||
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
|
val parameter = asmgen.findSubroutineParameter(identifier!!.name, asmgen)
|
||||||
if (parameter!=null) {
|
if (parameter!=null) {
|
||||||
val sub = parameter.definingSubroutine!!
|
val sub = parameter.definingAsmSub()
|
||||||
if (sub.isAsmSubroutine) {
|
if (sub!=null) {
|
||||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
val reg = sub.parameters.single { it.second===parameter }.first
|
||||||
if(reg.statusflag!=null)
|
if(reg.statusflag!=null)
|
||||||
throw AssemblyError("can't assign value to processor statusflag directly")
|
throw AssemblyError("can't assign value to processor statusflag directly")
|
||||||
else
|
else
|
||||||
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, type, definingSub, target.position, register=reg.registerOrPair, origAstTarget = this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, type, definingSub, target.position, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||||
}
|
}
|
||||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this)
|
||||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this)
|
||||||
else -> throw AssemblyError("weird target")
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, asmgen: AsmGen): AsmAssignTarget =
|
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, pos: Position, scope: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget =
|
||||||
when(registers) {
|
when(registers) {
|
||||||
RegisterOrPair.A,
|
RegisterOrPair.A,
|
||||||
RegisterOrPair.X,
|
RegisterOrPair.X,
|
||||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
|
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, pos, register = registers)
|
||||||
RegisterOrPair.AX,
|
RegisterOrPair.AX,
|
||||||
RegisterOrPair.AY,
|
RegisterOrPair.AY,
|
||||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, pos, register = registers)
|
||||||
RegisterOrPair.FAC1,
|
RegisterOrPair.FAC1,
|
||||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, register = registers)
|
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
|
||||||
RegisterOrPair.R0,
|
RegisterOrPair.R0,
|
||||||
RegisterOrPair.R1,
|
RegisterOrPair.R1,
|
||||||
RegisterOrPair.R2,
|
RegisterOrPair.R2,
|
||||||
@ -106,75 +99,94 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
RegisterOrPair.R12,
|
RegisterOrPair.R12,
|
||||||
RegisterOrPair.R13,
|
RegisterOrPair.R13,
|
||||||
RegisterOrPair.R14,
|
RegisterOrPair.R14,
|
||||||
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, pos, register = registers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isSameAs(left: PtExpression): Boolean =
|
||||||
|
when(kind) {
|
||||||
|
TargetStorageKind.VARIABLE -> {
|
||||||
|
val scopedName: String = if('.' in asmVarname)
|
||||||
|
asmVarname
|
||||||
|
else {
|
||||||
|
val scopeName = (scope as? PtNamedNode)?.scopedName
|
||||||
|
if (scopeName == null) asmVarname else "$scopeName.$asmVarname"
|
||||||
|
}
|
||||||
|
left is PtIdentifier && left.name==scopedName
|
||||||
|
}
|
||||||
|
TargetStorageKind.ARRAY -> {
|
||||||
|
left is PtArrayIndexer && left isSameAs array!!
|
||||||
|
}
|
||||||
|
TargetStorageKind.MEMORY -> {
|
||||||
|
left isSameAs memory!!
|
||||||
|
}
|
||||||
|
TargetStorageKind.REGISTER, TargetStorageKind.STACK -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class AsmAssignSource(val kind: SourceStorageKind,
|
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||||
private val program: Program,
|
private val program: PtProgram,
|
||||||
private val asmgen: AsmGen,
|
private val asmgen: AsmGen6502Internal,
|
||||||
val datatype: DataType,
|
val datatype: DataType,
|
||||||
private val variableAsmName: String? = null,
|
private val variableAsmName: String? = null,
|
||||||
val array: ArrayIndexedExpression? = null,
|
val array: PtArrayIndexer? = null,
|
||||||
val memory: DirectMemoryRead? = null,
|
val memory: PtMemoryByte? = null,
|
||||||
val register: RegisterOrPair? = null,
|
val register: RegisterOrPair? = null,
|
||||||
val number: NumericLiteral? = null,
|
val number: PtNumber? = null,
|
||||||
val expression: Expression? = null
|
val expression: PtExpression? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
val asmVarname: String
|
val asmVarname: String
|
||||||
get() = if(array==null)
|
get() = if(array==null)
|
||||||
variableAsmName!!
|
variableAsmName!!
|
||||||
else
|
else
|
||||||
asmgen.asmVariableName(array.arrayvar)
|
asmgen.asmVariableName(array.variable)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
|
||||||
val cv = value.constValue(program)
|
val cv = value as? PtNumber
|
||||||
if(cv!=null)
|
if(cv!=null)
|
||||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||||
|
|
||||||
return when(value) {
|
return when(value) {
|
||||||
is NumericLiteral -> throw AssemblyError("should have been constant value")
|
// checked above: is PtNumber -> throw AssemblyError("should have been constant value")
|
||||||
is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
is PtString -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||||
is ArrayLiteral -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
is PtArray -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||||
is IdentifierReference -> {
|
is PtIdentifier -> {
|
||||||
val parameter = value.targetVarDecl(program)?.subroutineParameter
|
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
|
||||||
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
|
if(parameter?.definingAsmSub() != null)
|
||||||
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
||||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
|
||||||
val varName=asmgen.asmVariableName(value)
|
val varName=asmgen.asmVariableName(value)
|
||||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||||
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
if(value.type == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
||||||
val regStr = varName.lowercase().substring(5)
|
val regStr = varName.lowercase().substring(5)
|
||||||
val reg = RegisterOrPair.valueOf(regStr.uppercase())
|
val reg = RegisterOrPair.valueOf(regStr.uppercase())
|
||||||
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
|
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, value.type, register = reg)
|
||||||
} else {
|
} else {
|
||||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
|
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, value.type, variableAsmName = varName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DirectMemoryRead -> {
|
is PtMemoryByte -> {
|
||||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||||
}
|
}
|
||||||
is ArrayIndexedExpression -> {
|
is PtArrayIndexer -> {
|
||||||
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
|
||||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
|
||||||
}
|
}
|
||||||
is BuiltinFunctionCall -> {
|
is PtBuiltinFunctionCall -> {
|
||||||
val returnType = value.inferType(program)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
|
||||||
}
|
}
|
||||||
is FunctionCallExpression -> {
|
is PtFunctionCall -> {
|
||||||
val sub = value.target.targetSubroutine(program)!!
|
val symbol = asmgen.symbolTable.lookup(value.name)
|
||||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
|
val sub = symbol!!.astNode as IPtSubroutine
|
||||||
|
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||||
|
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val returnType = value.inferType(program)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,17 +211,27 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class AsmAssignment(val source: AsmAssignSource,
|
internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
|
||||||
val target: AsmAssignTarget,
|
val target: AsmAssignTarget,
|
||||||
val isAugmentable: Boolean,
|
val memsizer: IMemSizer,
|
||||||
memsizer: IMemSizer,
|
val position: Position) {
|
||||||
val position: Position) {
|
|
||||||
|
|
||||||
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 at $position" }
|
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 srcdt=${source.datatype} targetdt=${target.datatype}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class AsmAssignment(source: AsmAssignSource,
|
||||||
|
target: AsmAssignTarget,
|
||||||
|
memsizer: IMemSizer,
|
||||||
|
position: Position): AsmAssignmentBase(source, target, memsizer, position)
|
||||||
|
|
||||||
|
internal class AsmAugmentedAssignment(source: AsmAssignSource,
|
||||||
|
val operator: String,
|
||||||
|
target: AsmAssignTarget,
|
||||||
|
memsizer: IMemSizer,
|
||||||
|
position: Position): AsmAssignmentBase(source, target, memsizer, position)
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
64
codeGenCpu6502/test/Dummies.kt
Normal file
64
codeGenCpu6502/test/Dummies.kt
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package prog8tests.codegencpu6502
|
||||||
|
|
||||||
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
|
internal object DummyMemsizer : IMemSizer {
|
||||||
|
override fun memorySize(dt: DataType) = when(dt) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
DataType.FLOAT -> 5
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
|
||||||
|
DataType.ARRAY_UW -> numElements*2
|
||||||
|
DataType.ARRAY_W -> numElements*2
|
||||||
|
DataType.ARRAY_F -> numElements*5
|
||||||
|
else -> numElements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object DummyStringEncoder : IStringEncoding {
|
||||||
|
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
|
||||||
|
IErrorReporter {
|
||||||
|
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
val warnings = mutableListOf<String>()
|
||||||
|
|
||||||
|
override fun err(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in errors)
|
||||||
|
errors.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun warn(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in warnings)
|
||||||
|
warnings.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun noErrors(): Boolean = errors.isEmpty()
|
||||||
|
|
||||||
|
override fun report() {
|
||||||
|
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||||
|
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||||
|
if(throwExceptionAtReportIfErrors)
|
||||||
|
finalizeNumErrors(errors.size, warnings.size)
|
||||||
|
if(!keepMessagesAfterReporting) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
errors.clear()
|
||||||
|
warnings.clear()
|
||||||
|
}
|
||||||
|
}
|
106
codeGenCpu6502/test/TestCodegen.kt
Normal file
106
codeGenCpu6502/test/TestCodegen.kt
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package prog8tests.codegencpu6502
|
||||||
|
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import prog8.code.SymbolTableMaker
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.C64Target
|
||||||
|
import prog8.codegen.cpu6502.AsmGen6502
|
||||||
|
import java.nio.file.Files
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
|
class TestCodegen: FunSpec({
|
||||||
|
|
||||||
|
fun getTestOptions(): CompilationOptions {
|
||||||
|
val target = C64Target()
|
||||||
|
return CompilationOptions(
|
||||||
|
OutputType.RAW,
|
||||||
|
CbmPrgLauncherType.NONE,
|
||||||
|
ZeropageType.DONTUSE,
|
||||||
|
zpReserved = emptyList(),
|
||||||
|
floats = true,
|
||||||
|
noSysInit = false,
|
||||||
|
compTarget = target,
|
||||||
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("augmented assign on arrays") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// ubyte[] particleX = [1,2,3]
|
||||||
|
// ubyte[] particleDX = [1,2,3]
|
||||||
|
// particleX[2] += particleDX[2]
|
||||||
|
//
|
||||||
|
// word @shared xx = 1
|
||||||
|
// xx = -xx
|
||||||
|
// xx += 42
|
||||||
|
// xx += cx16.r0
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = AsmGen6502()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
|
||||||
|
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val target = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||||
|
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
}
|
||||||
|
it.add(targetIdx)
|
||||||
|
}
|
||||||
|
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
|
||||||
|
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
assign.add(target)
|
||||||
|
assign.add(value)
|
||||||
|
sub.add(assign)
|
||||||
|
|
||||||
|
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||||
|
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
prefixAssign.add(prefixTarget)
|
||||||
|
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
sub.add(prefixAssign)
|
||||||
|
|
||||||
|
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
|
||||||
|
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
numberAssign.add(numberAssignTarget)
|
||||||
|
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
|
||||||
|
sub.add(numberAssign)
|
||||||
|
|
||||||
|
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
cxregAssign.add(cxregAssignTarget)
|
||||||
|
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
|
||||||
|
sub.add(cxregAssign)
|
||||||
|
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
// define the "cx16.r0" virtual register
|
||||||
|
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||||
|
program.add(cx16block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors)!!
|
||||||
|
result.name shouldBe "test"
|
||||||
|
Files.deleteIfExists(Path("${result.name}.asm"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -3,18 +3,19 @@ package prog8.codegen.experimental
|
|||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.ast.PtProgram
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.CompilationOptions
|
||||||
import prog8.code.core.IAssemblyGenerator
|
|
||||||
import prog8.code.core.IAssemblyProgram
|
import prog8.code.core.IAssemblyProgram
|
||||||
|
import prog8.code.core.ICodeGeneratorBackend
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.codegen.intermediate.IRCodeGen
|
import prog8.codegen.intermediate.IRCodeGen
|
||||||
import prog8.intermediate.IRFileWriter
|
import prog8.intermediate.IRFileWriter
|
||||||
|
|
||||||
class CodeGen(private val program: PtProgram,
|
class ExperiCodeGen: ICodeGeneratorBackend {
|
||||||
private val symbolTable: SymbolTable,
|
override fun generate(
|
||||||
private val options: CompilationOptions,
|
program: PtProgram,
|
||||||
private val errors: IErrorReporter
|
symbolTable: SymbolTable,
|
||||||
): IAssemblyGenerator {
|
options: CompilationOptions,
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
errors: IErrorReporter
|
||||||
|
): IAssemblyProgram? {
|
||||||
|
|
||||||
// you could write a code generator directly on the PtProgram AST,
|
// you could write a code generator directly on the PtProgram AST,
|
||||||
// but you can also use the Intermediate Representation to build a codegen on:
|
// but you can also use the Intermediate Representation to build a codegen on:
|
@ -30,7 +30,7 @@ dependencies {
|
|||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
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 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -3,6 +3,7 @@ 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.PrefixOperators
|
||||||
import prog8.code.core.SignedDatatypes
|
import prog8.code.core.SignedDatatypes
|
||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
@ -12,148 +13,143 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
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")
|
||||||
|
|
||||||
return if (assignment.isInplaceAssign)
|
return translateRegularAssign(assignment)
|
||||||
translateInplaceAssign(assignment)
|
|
||||||
else
|
|
||||||
translateRegularAssign(assignment)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
|
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
|
||||||
val ident = assignment.target.identifier
|
if(augAssign.target.children.single() is PtMachineRegister)
|
||||||
val memory = assignment.target.memory
|
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||||
val array = assignment.target.array
|
|
||||||
|
val ident = augAssign.target.identifier
|
||||||
|
val memory = augAssign.target.memory
|
||||||
|
val array = augAssign.target.array
|
||||||
|
|
||||||
return if(ident!=null) {
|
return if(ident!=null) {
|
||||||
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment)
|
assignVarAugmented(ident.name, augAssign)
|
||||||
} 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)
|
assignMemoryAugmented((memory.address as PtNumber).number.toInt(), augAssign)
|
||||||
else
|
else
|
||||||
fallbackAssign(assignment)
|
fallbackAssign(augAssign)
|
||||||
} else if(array!=null) {
|
} else if(array!=null) {
|
||||||
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
|
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
|
||||||
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
|
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
|
||||||
// will be optimized later and have the double assignments removed.
|
// will be optimized later and have the double assignments removed.
|
||||||
fallbackAssign(assignment)
|
fallbackAssign(augAssign)
|
||||||
} else {
|
} else {
|
||||||
fallbackAssign(assignment)
|
fallbackAssign(augAssign)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignSelfInMemoryKnownAddress(
|
private fun assignMemoryAugmented(
|
||||||
address: Int,
|
address: Int,
|
||||||
value: PtExpression,
|
assignment: PtAugmentedAssign
|
||||||
origAssign: PtAssignment
|
|
||||||
): IRCodeChunks {
|
): IRCodeChunks {
|
||||||
val vmDt = codeGen.irType(value.type)
|
val value = assignment.value
|
||||||
when(value) {
|
val vmDt = irType(value.type)
|
||||||
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
|
return when(assignment.operator) {
|
||||||
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
|
"+" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
|
||||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
|
"-" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
|
||||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
|
"*" -> expressionEval.operatorMultiplyInplace(address, null, vmDt, value)
|
||||||
is PtMemoryByte -> {
|
"/" -> expressionEval.operatorDivideInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
"|" -> expressionEval.operatorOrInplace(address, null, vmDt, value)
|
||||||
emptyList() // do nothing, mem=mem null assignment.
|
"&" -> expressionEval.operatorAndInplace(address, null, vmDt, value)
|
||||||
else {
|
"^" -> expressionEval.operatorXorInplace(address, null, vmDt, value)
|
||||||
// read and write a (i/o) memory location to itself.
|
"<<" -> expressionEval.operatorShiftLeftInplace(address, null, vmDt, value)
|
||||||
val tempReg = codeGen.registers.nextFree()
|
">>" -> expressionEval.operatorShiftRightInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||||
val code = IRCodeChunk(null, null)
|
"%=" -> expressionEval.operatorModuloInplace(address, null, vmDt, value)
|
||||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
"==" -> expressionEval.operatorEqualsInplace(address, null, vmDt, value)
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
"!=" -> expressionEval.operatorNotEqualsInplace(address, null, vmDt, value)
|
||||||
listOf(code)
|
"<" -> expressionEval.operatorLessInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||||
}
|
">" -> expressionEval.operatorGreaterInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||||
}
|
"<=" -> expressionEval.operatorLessEqualInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||||
else -> return fallbackAssign(origAssign)
|
">=" -> expressionEval.operatorGreaterEqualInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||||
|
in PrefixOperators -> inplacePrefix(assignment.operator, vmDt, address, null)
|
||||||
|
|
||||||
|
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignSelfInMemory(
|
private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
|
||||||
symbol: String,
|
val value = assignment.value
|
||||||
value: PtExpression,
|
val targetDt = irType(assignment.target.type)
|
||||||
origAssign: PtAssignment
|
return when (assignment.operator) {
|
||||||
): IRCodeChunks {
|
"+=" -> expressionEval.operatorPlusInplace(null, symbol, targetDt, value)
|
||||||
val vmDt = codeGen.irType(value.type)
|
"-=" -> expressionEval.operatorMinusInplace(null, symbol, targetDt, value)
|
||||||
return when(value) {
|
"*=" -> expressionEval.operatorMultiplyInplace(null, symbol, targetDt, value)
|
||||||
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
|
"/=" -> expressionEval.operatorDivideInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
|
||||||
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
|
"|=" -> expressionEval.operatorOrInplace(null, symbol, targetDt, value)
|
||||||
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
|
"&=" -> expressionEval.operatorAndInplace(null, symbol, targetDt, value)
|
||||||
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
"^=" -> expressionEval.operatorXorInplace(null, symbol, targetDt, value)
|
||||||
is PtMemoryByte -> {
|
"<<=" -> expressionEval.operatorShiftLeftInplace(null, symbol, targetDt, value)
|
||||||
val code = IRCodeChunk(null, null)
|
">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
|
||||||
val tempReg = codeGen.registers.nextFree()
|
"%=" -> expressionEval.operatorModuloInplace(null, symbol, targetDt, value)
|
||||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
"==" -> expressionEval.operatorEqualsInplace(null, symbol, targetDt, value)
|
||||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
"!=" -> expressionEval.operatorNotEqualsInplace(null, symbol, targetDt, value)
|
||||||
listOf(code)
|
"<" -> expressionEval.operatorLessInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
|
||||||
}
|
">" -> expressionEval.operatorGreaterInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
|
||||||
|
"<=" -> expressionEval.operatorLessEqualInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
|
||||||
else -> fallbackAssign(origAssign)
|
">=" -> expressionEval.operatorGreaterEqualInplace(null, symbol, targetDt, value.type in SignedDatatypes, value)
|
||||||
|
in PrefixOperators -> inplacePrefix(assignment.operator, targetDt, null, symbol)
|
||||||
|
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
|
private fun fallbackAssign(origAssign: PtAugmentedAssign): 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)
|
val value: PtExpression
|
||||||
}
|
if(origAssign.operator in PrefixOperators) {
|
||||||
|
value = PtPrefix(origAssign.operator, origAssign.value.type, origAssign.value.position)
|
||||||
private fun inplaceBinexpr(
|
value.add(origAssign.value)
|
||||||
operator: String,
|
|
||||||
operand: PtExpression,
|
|
||||||
vmDt: IRDataType,
|
|
||||||
signed: Boolean,
|
|
||||||
knownAddress: Int?,
|
|
||||||
symbol: String?,
|
|
||||||
origAssign: PtAssignment
|
|
||||||
): IRCodeChunks {
|
|
||||||
if(knownAddress!=null) {
|
|
||||||
when (operator) {
|
|
||||||
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"-" -> return expressionEval.operatorMinusInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"*" -> return expressionEval.operatorMultiplyInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"/" -> return expressionEval.operatorDivideInplace(knownAddress, null, vmDt, signed, operand)
|
|
||||||
"|" -> return expressionEval.operatorOrInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"&" -> return expressionEval.operatorAndInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"^" -> return expressionEval.operatorXorInplace(knownAddress, null, vmDt, operand)
|
|
||||||
"<<" -> return expressionEval.operatorShiftLeftInplace(knownAddress, null, vmDt, operand)
|
|
||||||
">>" -> return expressionEval.operatorShiftRightInplace(knownAddress, null, vmDt, signed, operand)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
symbol!!
|
require(origAssign.operator.endsWith('='))
|
||||||
when (operator) {
|
if(codeGen.options.useNewExprCode) {
|
||||||
"+" -> return expressionEval.operatorPlusInplace(null, symbol, vmDt, operand)
|
// X += Y -> temp = X, temp += Y, X = temp
|
||||||
"-" -> return expressionEval.operatorMinusInplace(null, symbol, vmDt, operand)
|
val tempvar = codeGen.getReusableTempvar(origAssign.definingSub()!!, origAssign.target.type)
|
||||||
"*" -> return expressionEval.operatorMultiplyInplace(null, symbol, vmDt, operand)
|
val assign = PtAssignment(origAssign.position)
|
||||||
"/" -> return expressionEval.operatorDivideInplace(null, symbol, vmDt, signed, operand)
|
val target = PtAssignTarget(origAssign.position)
|
||||||
"|" -> return expressionEval.operatorOrInplace(null, symbol, vmDt, operand)
|
target.add(tempvar)
|
||||||
"&" -> return expressionEval.operatorAndInplace(null, symbol, vmDt, operand)
|
assign.add(target)
|
||||||
"^" -> return expressionEval.operatorXorInplace(null, symbol, vmDt, operand)
|
assign.add(origAssign.target.children.single())
|
||||||
"<<" -> return expressionEval.operatorShiftLeftInplace(null, symbol, vmDt, operand)
|
val augAssign = PtAugmentedAssign(origAssign.operator, origAssign.position)
|
||||||
">>" -> return expressionEval.operatorShiftRightInplace(null, symbol, vmDt, signed, operand)
|
augAssign.add(target)
|
||||||
else -> {}
|
augAssign.add(origAssign.value)
|
||||||
|
val assignBack = PtAssignment(origAssign.position)
|
||||||
|
assignBack.add(origAssign.target)
|
||||||
|
assignBack.add(tempvar)
|
||||||
|
return translateRegularAssign(assign) + translate(augAssign) + translateRegularAssign(assignBack)
|
||||||
|
} else {
|
||||||
|
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
|
||||||
|
val left: PtExpression = origAssign.target.children.single() as PtExpression
|
||||||
|
value.add(left)
|
||||||
|
value.add(origAssign.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fallbackAssign(origAssign)
|
val normalAssign = PtAssignment(origAssign.position)
|
||||||
|
normalAssign.add(origAssign.target)
|
||||||
|
normalAssign.add(value)
|
||||||
|
return translateRegularAssign(normalAssign)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
|
private fun inplacePrefix(operator: String, vmDt: IRDataType, address: Int?, symbol: String?): IRCodeChunks {
|
||||||
val code= IRCodeChunk(null, null)
|
val code= IRCodeChunk(null, null)
|
||||||
when(operator) {
|
when(operator) {
|
||||||
"+" -> { }
|
"+" -> { }
|
||||||
"-" -> {
|
"-" -> {
|
||||||
code += if(knownAddress!=null)
|
code += if(address!=null)
|
||||||
IRInstruction(Opcode.NEGM, vmDt, value = knownAddress)
|
IRInstruction(Opcode.NEGM, vmDt, address = address)
|
||||||
else
|
else
|
||||||
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
|
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = symbol)
|
||||||
}
|
}
|
||||||
"~" -> {
|
"~" -> {
|
||||||
val regMask = codeGen.registers.nextFree()
|
val regMask = codeGen.registers.nextFree()
|
||||||
val mask = if(vmDt==IRDataType.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, immediate = mask)
|
||||||
code += if(knownAddress!=null)
|
code += if(address!=null)
|
||||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
|
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, address = address)
|
||||||
else
|
else
|
||||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol)
|
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = symbol)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird prefix operator")
|
else -> throw AssemblyError("weird prefix operator")
|
||||||
}
|
}
|
||||||
@ -162,120 +158,139 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
|
|
||||||
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
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 targetIdent = assignment.target.identifier
|
||||||
val memory = assignment.target.memory
|
val targetMemory = assignment.target.memory
|
||||||
val array = assignment.target.array
|
val targetArray = assignment.target.array
|
||||||
val vmDt = codeGen.irType(assignment.value.type)
|
val valueDt = irType(assignment.value.type)
|
||||||
|
val targetDt = irType(assignment.target.type)
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
var resultRegister = -1
|
|
||||||
var resultFpRegister = -1
|
var valueRegister = -1
|
||||||
|
var valueFpRegister = -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 == IRDataType.FLOAT) {
|
if (valueDt == IRDataType.FLOAT) {
|
||||||
resultFpRegister = codeGen.registers.nextFreeFloat()
|
val tr = expressionEval.translateExpression(assignment.value)
|
||||||
result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
valueFpRegister = tr.resultFpReg
|
||||||
|
addToResult(result, tr, -1, valueFpRegister)
|
||||||
} else {
|
} else {
|
||||||
resultRegister = if (assignment.value is PtMachineRegister) {
|
val extendByteToWord = if(targetDt != valueDt) {
|
||||||
(assignment.value as PtMachineRegister).register
|
// usually an error EXCEPT when a byte is assigned to a word.
|
||||||
|
if(targetDt==IRDataType.WORD && valueDt==IRDataType.BYTE)
|
||||||
|
true
|
||||||
|
else
|
||||||
|
throw AssemblyError("assignment value and target dt mismatch")
|
||||||
|
} else false
|
||||||
|
if (assignment.value is PtMachineRegister) {
|
||||||
|
valueRegister = (assignment.value as PtMachineRegister).register
|
||||||
|
if(extendByteToWord)
|
||||||
|
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister), null)
|
||||||
} else {
|
} else {
|
||||||
val reg = codeGen.registers.nextFree()
|
val tr = expressionEval.translateExpression(assignment.value)
|
||||||
result += expressionEval.translateExpression(assignment.value, reg, -1)
|
valueRegister = tr.resultReg
|
||||||
reg
|
addToResult(result, tr, valueRegister, -1)
|
||||||
|
if(extendByteToWord) {
|
||||||
|
val opcode = if(assignment.value.type in SignedDatatypes) Opcode.EXTS else Opcode.EXT
|
||||||
|
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1 = valueRegister), null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(ident!=null) {
|
|
||||||
val symbol = ident.targetName.joinToString(".")
|
if(targetIdent!=null) {
|
||||||
val instruction = if(zero) {
|
val instruction = if(zero) {
|
||||||
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
|
||||||
} else {
|
} else {
|
||||||
if (vmDt == IRDataType.FLOAT)
|
if (targetDt == IRDataType.FLOAT)
|
||||||
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
|
||||||
else
|
else
|
||||||
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
|
||||||
}
|
}
|
||||||
result += IRCodeChunk(null, null).also { it += instruction }
|
result += IRCodeChunk(null, null).also { it += instruction }
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
else if(array!=null) {
|
else if(targetArray!=null) {
|
||||||
val variable = array.variable.targetName.joinToString(".")
|
val variable = targetArray.variable.name
|
||||||
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
val itemsize = codeGen.program.memsizer.memorySize(targetArray.type)
|
||||||
|
|
||||||
if(array.variable.type==DataType.UWORD) {
|
if(targetArray.variable.type==DataType.UWORD) {
|
||||||
// indexing a pointer var instead of a real array or string
|
// indexing a pointer var instead of a real array or string
|
||||||
if(itemsize!=1)
|
if(itemsize!=1)
|
||||||
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(targetArray.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.registers.nextFree()
|
val tr = expressionEval.translateExpression(targetArray.index)
|
||||||
result += expressionEval.translateExpression(array.index, idxReg, -1)
|
val idxReg = tr.resultReg
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
val code = IRCodeChunk(null, null)
|
val code = IRCodeChunk(null, null)
|
||||||
if(zero) {
|
if(zero) {
|
||||||
// there's no STOREZIX instruction
|
// there's no STOREZIX instruction
|
||||||
resultRegister = codeGen.registers.nextFree()
|
valueRegister = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
code += IRInstruction(Opcode.LOAD, targetDt, reg1=valueRegister, immediate = 0)
|
||||||
}
|
}
|
||||||
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
|
code += IRInstruction(Opcode.STOREIX, targetDt, reg1=valueRegister, reg2=idxReg, labelSymbol = variable)
|
||||||
result += code
|
result += code
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
val fixedIndex = constIntValue(array.index)
|
val fixedIndex = constIntValue(targetArray.index)
|
||||||
if(zero) {
|
if(zero) {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val offset = fixedIndex*itemsize
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = "$variable+$offset") }
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.registers.nextFree()
|
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||||
result += loadIndexReg(array, itemsize, indexReg)
|
result += code
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(vmDt== IRDataType.FLOAT) {
|
if(targetDt== IRDataType.FLOAT) {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val offset = fixedIndex*itemsize
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") }
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = "$variable+$offset") }
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.registers.nextFree()
|
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||||
result += loadIndexReg(array, itemsize, indexReg)
|
result += code
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = valueFpRegister, labelSymbol = variable) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(fixedIndex!=null) {
|
if(fixedIndex!=null) {
|
||||||
val offset = fixedIndex*itemsize
|
val offset = fixedIndex*itemsize
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") }
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = "$variable+$offset") }
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val indexReg = codeGen.registers.nextFree()
|
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||||
result += loadIndexReg(array, itemsize, indexReg)
|
result += code
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
else if(memory!=null) {
|
else if(targetMemory!=null) {
|
||||||
require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
|
require(targetDt == IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
|
||||||
if(zero) {
|
if(zero) {
|
||||||
if(memory.address is PtNumber) {
|
if(targetMemory.address is PtNumber) {
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (targetMemory.address as PtNumber).number.toInt()) }
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||||
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
val addressReg = tr.resultReg
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(memory.address is PtNumber) {
|
if(targetMemory.address is PtNumber) {
|
||||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) }
|
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=(targetMemory.address as PtNumber).number.toInt()) }
|
||||||
result += chunk
|
result += chunk
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||||
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
val addressReg = tr.resultReg
|
||||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,14 +300,29 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
|||||||
throw AssemblyError("weird assigntarget")
|
throw AssemblyError("weird assigntarget")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
|
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int): Pair<IRCodeChunks, Int> {
|
||||||
return if(itemsize==1) {
|
// returns the code to load the Index into the register, which is also return\ed.
|
||||||
expressionEval.translateExpression(array.index, indexReg, -1)
|
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
if(itemsize==1) {
|
||||||
|
val tr = expressionEval.translateExpression(array.index)
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
return Pair(result, tr.resultReg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(codeGen.options.useNewExprCode) {
|
||||||
|
val tr = expressionEval.translateExpression(array.index)
|
||||||
|
result += tr.chunks
|
||||||
|
addInstr(result, IRInstruction(Opcode.MUL, tr.dt, reg1=tr.resultReg, immediate = itemsize), null)
|
||||||
|
return Pair(result, tr.resultReg)
|
||||||
} else {
|
} else {
|
||||||
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
val mult: PtExpression
|
||||||
|
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)
|
||||||
expressionEval.translateExpression(mult, indexReg, -1)
|
val tr = expressionEval.translateExpression(mult)
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
return Pair(result, tr.resultReg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,14 +9,16 @@ 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): IRCodeChunks {
|
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
return when(call.name) {
|
return when(call.name) {
|
||||||
"any" -> funcAny(call, resultRegister)
|
"any" -> funcAny(call)
|
||||||
"all" -> funcAll(call, resultRegister)
|
"all" -> funcAll(call)
|
||||||
"abs" -> funcAbs(call, resultRegister)
|
"abs" -> funcAbs(call)
|
||||||
"cmp" -> funcCmp(call)
|
"cmp" -> funcCmp(call)
|
||||||
"sgn" -> funcSgn(call, resultRegister)
|
"sgn" -> funcSgn(call)
|
||||||
"sqrt16" -> funcSqrt16(call, resultRegister)
|
"sqrt16" -> funcSqrt16(call)
|
||||||
|
"divmod" -> funcDivmod(call, IRDataType.BYTE)
|
||||||
|
"divmodw" -> funcDivmod(call, IRDataType.WORD)
|
||||||
"pop" -> funcPop(call)
|
"pop" -> funcPop(call)
|
||||||
"popw" -> funcPopw(call)
|
"popw" -> funcPopw(call)
|
||||||
"push" -> funcPush(call)
|
"push" -> funcPush(call)
|
||||||
@ -24,43 +26,83 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
"rsave",
|
"rsave",
|
||||||
"rsavex",
|
"rsavex",
|
||||||
"rrestore",
|
"rrestore",
|
||||||
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
|
"rrestorex" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
|
||||||
"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")
|
"msb" -> funcMsb(call)
|
||||||
"msb" -> funcMsb(call, resultRegister)
|
"lsb" -> funcLsb(call)
|
||||||
"lsb" -> funcLsb(call, resultRegister)
|
"memory" -> funcMemory(call)
|
||||||
"memory" -> funcMemory(call, resultRegister)
|
"peek" -> funcPeek(call)
|
||||||
"peek" -> funcPeek(call, resultRegister)
|
"peekw" -> funcPeekW(call)
|
||||||
"peekw" -> funcPeekW(call, resultRegister)
|
|
||||||
"poke" -> funcPoke(call)
|
"poke" -> funcPoke(call)
|
||||||
"pokew" -> funcPokeW(call)
|
"pokew" -> funcPokeW(call)
|
||||||
"pokemon" -> emptyList()
|
"pokemon" -> ExpressionCodeResult.EMPTY // easter egg function
|
||||||
"mkword" -> funcMkword(call, resultRegister)
|
"mkword" -> funcMkword(call)
|
||||||
"sort" -> funcSort(call)
|
"sort" -> funcSort(call)
|
||||||
"reverse" -> funcReverse(call)
|
"reverse" -> funcReverse(call)
|
||||||
"rol" -> funcRolRor(Opcode.ROXL, call, resultRegister)
|
"rol" -> funcRolRor(Opcode.ROXL, call)
|
||||||
"ror" -> funcRolRor(Opcode.ROXR, call, resultRegister)
|
"ror" -> funcRolRor(Opcode.ROXR, call)
|
||||||
"rol2" -> funcRolRor(Opcode.ROL, call, resultRegister)
|
"rol2" -> funcRolRor(Opcode.ROL, call)
|
||||||
"ror2" -> funcRolRor(Opcode.ROR, call, resultRegister)
|
"ror2" -> funcRolRor(Opcode.ROR, call)
|
||||||
|
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
||||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||||
val leftRegister = codeGen.registers.nextFree()
|
|
||||||
val rightRegister = codeGen.registers.nextFree()
|
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
val number = call.args[0]
|
||||||
result += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
val divident = call.args[1]
|
||||||
result += IRCodeChunk(null, null).also {
|
val divisionReg: Int
|
||||||
it += IRInstruction(Opcode.CMP, codeGen.irType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
val remainderReg: Int
|
||||||
|
if(divident is PtNumber) {
|
||||||
|
val tr = exprGen.translateExpression(number)
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
addInstr(result, IRInstruction(Opcode.DIVMOD, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
|
||||||
|
divisionReg = tr.resultReg
|
||||||
|
remainderReg = codeGen.registers.nextFree()
|
||||||
|
} else {
|
||||||
|
val numTr = exprGen.translateExpression(number)
|
||||||
|
addToResult(result, numTr, numTr.resultReg, -1)
|
||||||
|
val dividentTr = exprGen.translateExpression(divident)
|
||||||
|
addToResult(result, dividentTr, dividentTr.resultReg, -1)
|
||||||
|
addInstr(result, IRInstruction(Opcode.DIVMODR, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null)
|
||||||
|
divisionReg = numTr.resultReg
|
||||||
|
remainderReg = dividentTr.resultReg
|
||||||
}
|
}
|
||||||
return result
|
// DIVMOD result convention: on value stack, division and remainder on top.
|
||||||
|
addInstr(result, IRInstruction(Opcode.POP, type, reg1=remainderReg), null)
|
||||||
|
addInstr(result, IRInstruction(Opcode.POP, type, reg1=divisionReg), null)
|
||||||
|
result += assignRegisterTo(call.args[2], divisionReg)
|
||||||
|
result += assignRegisterTo(call.args[3], remainderReg)
|
||||||
|
return ExpressionCodeResult(result, type, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
val left = exprGen.translateExpression(call.args[0])
|
||||||
|
val right = exprGen.translateExpression(call.args[1])
|
||||||
|
addToResult(result, left, left.resultReg, -1)
|
||||||
|
addToResult(result, right, right.resultReg, -1)
|
||||||
|
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(IRDataType.WORD to left.resultReg, IRDataType.WORD to right.resultReg), IRDataType.BYTE to left.resultReg)
|
||||||
|
return ExpressionCodeResult(result, IRDataType.BYTE, left.resultReg, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcCmp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
|
val leftTr = exprGen.translateExpression(call.args[0])
|
||||||
|
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||||
|
val rightTr = exprGen.translateExpression(call.args[1])
|
||||||
|
addToResult(result, rightTr, rightTr.resultReg, -1)
|
||||||
|
val dt = irType(call.args[0].type)
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.CMP, dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
|
||||||
|
}
|
||||||
|
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
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,
|
||||||
@ -71,19 +113,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
else -> throw IllegalArgumentException("weird type")
|
else -> throw IllegalArgumentException("weird type")
|
||||||
}
|
}
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
result += IRCodeChunk(null, null).also {
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
val lengthReg = codeGen.registers.nextFree()
|
||||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
|
||||||
if(resultRegister!=0)
|
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
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,
|
||||||
@ -94,117 +134,122 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
else -> throw IllegalArgumentException("weird type")
|
else -> throw IllegalArgumentException("weird type")
|
||||||
}
|
}
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
result += IRCodeChunk(null, null).also {
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
val lengthReg = codeGen.registers.nextFree()
|
||||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
|
||||||
if(resultRegister!=0)
|
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcAbs(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val sourceDt = call.args.single().type
|
val sourceDt = call.args.single().type
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(sourceDt!=DataType.UWORD) {
|
if(sourceDt==DataType.UWORD)
|
||||||
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
return ExpressionCodeResult.EMPTY
|
||||||
when (sourceDt) {
|
|
||||||
DataType.UBYTE -> {
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
result += IRCodeChunk(null, null).also {
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = resultRegister)
|
when (sourceDt) {
|
||||||
}
|
DataType.UBYTE -> {
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = tr.resultReg)
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||||
val notNegativeLabel = codeGen.createLabelName()
|
|
||||||
val compareReg = codeGen.registers.nextFree()
|
|
||||||
result += IRCodeChunk(null, null).also {
|
|
||||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
|
||||||
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, value=0x80)
|
|
||||||
it += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
|
||||||
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=resultRegister)
|
|
||||||
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultRegister)
|
|
||||||
}
|
|
||||||
result += IRCodeChunk(notNegativeLabel, null)
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
val notNegativeLabel = codeGen.createLabelName()
|
|
||||||
val compareReg = codeGen.registers.nextFree()
|
|
||||||
result += IRCodeChunk(null, null).also {
|
|
||||||
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
|
||||||
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, value=0x8000)
|
|
||||||
it += IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
|
||||||
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=resultRegister)
|
|
||||||
}
|
|
||||||
result += IRCodeChunk(notNegativeLabel, null)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
val notNegativeLabel = codeGen.createLabelName()
|
||||||
|
val compareReg = codeGen.registers.nextFree()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
|
||||||
|
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, immediate = 0x80)
|
||||||
|
it += IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=compareReg, immediate = 0, labelSymbol = notNegativeLabel)
|
||||||
|
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
|
||||||
|
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=tr.resultReg)
|
||||||
|
}
|
||||||
|
result += IRCodeChunk(notNegativeLabel, null)
|
||||||
|
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
val notNegativeLabel = codeGen.createLabelName()
|
||||||
|
val compareReg = codeGen.registers.nextFree()
|
||||||
|
result += IRCodeChunk(null, null).also {
|
||||||
|
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
|
||||||
|
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, immediate = 0x8000)
|
||||||
|
it += IRInstruction(Opcode.BEQ, IRDataType.WORD, reg1=compareReg, immediate = 0, labelSymbol = notNegativeLabel)
|
||||||
|
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
|
||||||
|
}
|
||||||
|
result += IRCodeChunk(notNegativeLabel, null)
|
||||||
|
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val reg = codeGen.registers.nextFree()
|
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
val vmDt = irType(call.type)
|
||||||
|
val tr = exprGen.translateExpression(call.args.single())
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
val resultReg = codeGen.registers.nextFree()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.SGN, codeGen.irType(call.type), reg1 = resultRegister, reg2 = reg)
|
it += IRInstruction(Opcode.SGN, vmDt, reg1 = resultReg, reg2 = tr.resultReg)
|
||||||
}
|
}
|
||||||
return result
|
return ExpressionCodeResult(result, vmDt, resultReg, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcSqrt16(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val reg = codeGen.registers.nextFree()
|
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
val tr = exprGen.translateExpression(call.args.single())
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
val resultReg = codeGen.registers.nextFree()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultRegister, reg2=reg)
|
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
|
||||||
}
|
}
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcPop(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val code = IRCodeChunk(null, null)
|
val code = IRCodeChunk(null, null)
|
||||||
val reg = codeGen.registers.nextFree()
|
val reg = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
|
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
|
||||||
val result = mutableListOf<IRCodeChunkBase>(code)
|
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||||
result += assignRegisterTo(call.args.single(), reg)
|
result += assignRegisterTo(call.args.single(), reg)
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.BYTE, reg, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcPopw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val code = IRCodeChunk(null, null)
|
val code = IRCodeChunk(null, null)
|
||||||
val reg = codeGen.registers.nextFree()
|
val reg = codeGen.registers.nextFree()
|
||||||
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
|
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
|
||||||
val result = mutableListOf<IRCodeChunkBase>(code)
|
val result = mutableListOf<IRCodeChunkBase>(code)
|
||||||
result += assignRegisterTo(call.args.single(), reg)
|
result += assignRegisterTo(call.args.single(), reg)
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.WORD, reg, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcPush(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val reg = codeGen.registers.nextFree()
|
val tr = exprGen.translateExpression(call.args.single())
|
||||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=reg)
|
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg)
|
||||||
}
|
}
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcPushw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val reg = codeGen.registers.nextFree()
|
val tr = exprGen.translateExpression(call.args.single())
|
||||||
result += exprGen.translateExpression(call.args.single(), reg, -1)
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = reg)
|
it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = tr.resultReg)
|
||||||
}
|
}
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
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
|
||||||
@ -213,17 +258,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
else -> throw IllegalArgumentException("weird type to reverse")
|
else -> throw IllegalArgumentException("weird type to reverse")
|
||||||
}
|
}
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
result += IRCodeChunk(null, null).also {
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
val lengthReg = codeGen.registers.nextFree()
|
||||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
|
||||||
}
|
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
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
|
||||||
@ -235,160 +280,173 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
else -> throw IllegalArgumentException("weird type to sort")
|
else -> throw IllegalArgumentException("weird type to sort")
|
||||||
}
|
}
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
result += IRCodeChunk(null, null).also {
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
|
val lengthReg = codeGen.registers.nextFree()
|
||||||
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
|
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
|
||||||
}
|
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val msbReg = codeGen.registers.nextFree()
|
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args[0], msbReg, -1)
|
val msbTr = exprGen.translateExpression(call.args[0])
|
||||||
result += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||||
|
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||||
|
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = resultRegister, reg2 = msbReg)
|
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = lsbTr.resultReg, reg2 = msbTr.resultReg)
|
||||||
}
|
}
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.WORD, lsbTr.resultReg, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcPokeW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
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()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, value = address)
|
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, address = address)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg2 = addressReg)
|
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = tr.resultReg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
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()
|
||||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
val tr = exprGen.translateExpression(call.args[1])
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = valueReg, value = address)
|
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = tr.resultReg, address = address)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val addressTr = exprGen.translateExpression(call.args[0])
|
||||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
val valueTr = exprGen.translateExpression(call.args[1])
|
||||||
|
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunks {
|
private fun funcPoke(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
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()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, value = address)
|
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg2 = addressReg)
|
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = tr.resultReg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
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()
|
||||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
val tr = exprGen.translateExpression(call.args[1])
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueReg, value = address)
|
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val addressTr = exprGen.translateExpression(call.args[0])
|
||||||
result += exprGen.translateExpression(call.args[0], addressReg, -1)
|
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||||
result += exprGen.translateExpression(call.args[1], valueReg, -1)
|
val valueTr = exprGen.translateExpression(call.args[1])
|
||||||
|
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcPeekW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(call.args[0] is PtNumber) {
|
return if(call.args[0] is PtNumber) {
|
||||||
|
val resultRegister = codeGen.registers.nextFree()
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, value = address)
|
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, address = address)
|
||||||
}
|
}
|
||||||
|
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = exprGen.translateExpression(call.args.single())
|
||||||
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
val resultReg = codeGen.registers.nextFree()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg)
|
||||||
}
|
}
|
||||||
|
ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcPeek(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
if(call.args[0] is PtNumber) {
|
return if(call.args[0] is PtNumber) {
|
||||||
|
val resultRegister = codeGen.registers.nextFree()
|
||||||
val address = (call.args[0] as PtNumber).number.toInt()
|
val address = (call.args[0] as PtNumber).number.toInt()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, value = address)
|
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = address)
|
||||||
}
|
}
|
||||||
|
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||||
} else {
|
} else {
|
||||||
val addressReg = codeGen.registers.nextFree()
|
val tr = exprGen.translateExpression(call.args.single())
|
||||||
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
val resultReg = codeGen.registers.nextFree()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg)
|
||||||
}
|
}
|
||||||
|
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val name = (call.args[0] as PtString).value
|
val name = (call.args[0] as PtString).value
|
||||||
val code = IRCodeChunk(null, null)
|
val code = IRCodeChunk(null, null)
|
||||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
|
val resultReg = codeGen.registers.nextFree()
|
||||||
return listOf(code)
|
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
|
||||||
|
return ExpressionCodeResult(code, IRDataType.BYTE, resultReg, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
return exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
return exprGen.translateExpression(call.args.single())
|
||||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
val tr = exprGen.translateExpression(call.args.single())
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
|
val resultReg = codeGen.registers.nextFree()
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultRegister, reg2 = resultRegister)
|
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg)
|
||||||
}
|
}
|
||||||
// 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 result
|
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||||
val vmDt = codeGen.irType(call.args[0].type)
|
val vmDt = irType(call.args[0].type)
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
val tr = exprGen.translateExpression(call.args[0])
|
||||||
|
addToResult(result, tr, tr.resultReg, -1)
|
||||||
result += IRCodeChunk(null, null).also {
|
result += IRCodeChunk(null, null).also {
|
||||||
it += IRInstruction(opcode, vmDt, reg1 = resultRegister)
|
it += IRInstruction(opcode, vmDt, reg1 = tr.resultReg)
|
||||||
}
|
}
|
||||||
result += assignRegisterTo(call.args[0], resultRegister)
|
result += assignRegisterTo(call.args[0], tr.resultReg)
|
||||||
return result
|
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,39 @@
|
|||||||
package prog8.codegen.intermediate
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||||
fun optimize() {
|
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
|
||||||
|
if(!optimizationsEnabled)
|
||||||
|
return optimizeOnlyJoinChunks()
|
||||||
|
|
||||||
|
peepholeOptimize()
|
||||||
|
val remover = IRUnusedCodeRemover(irprog, errors)
|
||||||
|
var totalRemovals = 0
|
||||||
|
do {
|
||||||
|
val numRemoved = remover.optimize()
|
||||||
|
totalRemovals += numRemoved
|
||||||
|
} while(numRemoved>0 && errors.noErrors())
|
||||||
|
errors.report()
|
||||||
|
|
||||||
|
if(totalRemovals>0) {
|
||||||
|
irprog.linkChunks() // re-link again.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeOnlyJoinChunks() {
|
||||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
|
joinChunks(sub)
|
||||||
|
removeEmptyChunks(sub)
|
||||||
|
joinChunks(sub)
|
||||||
|
}
|
||||||
|
irprog.linkChunks() // re-link
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun peepholeOptimize() {
|
||||||
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
|
joinChunks(sub)
|
||||||
removeEmptyChunks(sub)
|
removeEmptyChunks(sub)
|
||||||
joinChunks(sub)
|
joinChunks(sub)
|
||||||
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||||
@ -28,7 +57,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
removeEmptyChunks(sub)
|
removeEmptyChunks(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
irprog.linkChunks() // re-link
|
irprog.linkChunks() // re-link
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeEmptyChunks(sub: IRSubroutine) {
|
private fun removeEmptyChunks(sub: IRSubroutine) {
|
||||||
@ -38,7 +67,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
/*
|
/*
|
||||||
Empty Code chunk with label ->
|
Empty Code chunk with label ->
|
||||||
If next chunk has no label -> move label to next chunk, remove original
|
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 next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: merge both labels into 1)
|
||||||
If is last chunk -> keep chunk in place because of the label.
|
If is last chunk -> keep chunk in place because of the label.
|
||||||
Empty Code chunk without label ->
|
Empty Code chunk without label ->
|
||||||
should not have been generated! ERROR.
|
should not have been generated! ERROR.
|
||||||
@ -63,7 +92,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
if (chunk.label == nextchunk.label)
|
if (chunk.label == nextchunk.label)
|
||||||
removeChunks += index
|
removeChunks += index
|
||||||
else {
|
else {
|
||||||
// TODO: consolidate labels on same chunk
|
// TODO: merge labels on same chunk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,7 +114,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
if(sub.chunks.isEmpty())
|
if(sub.chunks.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
fun mayJoinCodeChunks(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
||||||
if(chunk.label!=null)
|
if(chunk.label!=null)
|
||||||
return false
|
return false
|
||||||
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
||||||
@ -102,12 +131,39 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
chunks += sub.chunks[0]
|
chunks += sub.chunks[0]
|
||||||
for(ix in 1 until sub.chunks.size) {
|
for(ix in 1 until sub.chunks.size) {
|
||||||
val lastChunk = chunks.last()
|
val lastChunk = chunks.last()
|
||||||
if(mayJoin(lastChunk, sub.chunks[ix])) {
|
val candidate = sub.chunks[ix]
|
||||||
lastChunk.instructions += sub.chunks[ix].instructions
|
when(candidate) {
|
||||||
lastChunk.next = sub.chunks[ix].next
|
is IRCodeChunk -> {
|
||||||
|
if(mayJoinCodeChunks(lastChunk, candidate)) {
|
||||||
|
lastChunk.instructions += candidate.instructions
|
||||||
|
lastChunk.next = candidate.next
|
||||||
|
}
|
||||||
|
else
|
||||||
|
chunks += candidate
|
||||||
|
}
|
||||||
|
is IRInlineAsmChunk -> {
|
||||||
|
if(candidate.label!=null)
|
||||||
|
chunks += candidate
|
||||||
|
else if(lastChunk.isEmpty()) {
|
||||||
|
val label = lastChunk.label
|
||||||
|
if(label!=null)
|
||||||
|
chunks += IRInlineAsmChunk(label, candidate.assembly, candidate.isIR, candidate.next)
|
||||||
|
else
|
||||||
|
chunks += candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IRInlineBinaryChunk -> {
|
||||||
|
if(candidate.label!=null)
|
||||||
|
chunks += candidate
|
||||||
|
else if(lastChunk.isEmpty()) {
|
||||||
|
val label = lastChunk.label
|
||||||
|
if(label!=null)
|
||||||
|
chunks += IRInlineBinaryChunk(label, candidate.data, candidate.next)
|
||||||
|
else
|
||||||
|
chunks += candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
chunks += sub.chunks[ix]
|
|
||||||
}
|
}
|
||||||
sub.chunks.clear()
|
sub.chunks.clear()
|
||||||
sub.chunks += chunks
|
sub.chunks += chunks
|
||||||
@ -119,8 +175,8 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
if(ins.opcode== Opcode.PUSH) {
|
if(ins.opcode== Opcode.PUSH) {
|
||||||
if(idx < chunk.instructions.size-1) {
|
if(idx < chunk.instructions.size-1) {
|
||||||
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
val insAfter = chunk.instructions[idx+1]
|
||||||
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
|
if(insAfter.opcode == Opcode.POP) {
|
||||||
if(ins.reg1==insAfter.reg1) {
|
if(ins.reg1==insAfter.reg1) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
@ -143,16 +199,16 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
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.instructions.size-1) {
|
if(idx < chunk.instructions.size-1) {
|
||||||
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
val insAfter = chunk.instructions[idx+1]
|
||||||
if(insAfter?.opcode == ins.opcode) {
|
if(insAfter.opcode == ins.opcode) {
|
||||||
chunk.instructions.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.instructions.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.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
@ -174,14 +230,36 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove useless RETURN
|
// remove useless RETURN
|
||||||
if(ins.opcode == Opcode.RETURN && idx>0) {
|
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR)) {
|
||||||
val previous = chunk.instructions[idx-1] as? IRInstruction
|
val previous = chunk.instructions[idx-1]
|
||||||
if(previous?.opcode in OpcodesThatJump) {
|
if(previous.opcode in OpcodesThatJump) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace subsequent opcodes that jump by just the first
|
||||||
|
if(idx>0 && (ins.opcode in OpcodesThatJump)) {
|
||||||
|
val previous = chunk.instructions[idx-1]
|
||||||
|
if(previous.opcode in OpcodesThatJump) {
|
||||||
|
chunk.instructions.removeAt(idx)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace call + return --> jump
|
||||||
|
// This can no longer be done here on the IR level, with the current CALL opcode that encodes the full subroutine call setup.
|
||||||
|
// If machine code is ever generated from this IR, *that* should possibly optimize the JSR + RTS into a JMP.
|
||||||
|
// if(idx>0 && ins.opcode==Opcode.RETURN) {
|
||||||
|
// val previous = chunk.instructions[idx-1]
|
||||||
|
// if(previous.opcode==Opcode.CALL) {
|
||||||
|
// chunk.instructions[idx-1] = IRInstruction(Opcode.JUMP, address = previous.address, labelSymbol = previous.labelSymbol, branchTarget = previous.branchTarget)
|
||||||
|
// chunk.instructions.removeAt(idx)
|
||||||
|
// changed = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
@ -192,47 +270,47 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||||
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.immediate == 1) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.ADD, Opcode.SUB -> {
|
Opcode.ADD, Opcode.SUB -> {
|
||||||
if (ins.value == 1) {
|
if (ins.immediate == 1) {
|
||||||
chunk.instructions[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.immediate == 0) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.AND -> {
|
Opcode.AND -> {
|
||||||
if (ins.value == 0) {
|
if (ins.immediate == 0) {
|
||||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
|
} else if (ins.immediate == 255 && ins.type == IRDataType.BYTE) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
|
} else if (ins.immediate == 65535 && ins.type == IRDataType.WORD) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.OR -> {
|
Opcode.OR -> {
|
||||||
if (ins.value == 0) {
|
if (ins.immediate == 0) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
|
} else if ((ins.immediate == 255 && ins.type == IRDataType.BYTE) || (ins.immediate == 65535 && ins.type == IRDataType.WORD)) {
|
||||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = ins.immediate)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode.XOR -> {
|
Opcode.XOR -> {
|
||||||
if (ins.value == 0) {
|
if (ins.immediate == 0) {
|
||||||
chunk.instructions.removeAt(idx)
|
chunk.instructions.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
@ -5,34 +5,18 @@ import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
|||||||
import prog8.intermediate.*
|
import prog8.intermediate.*
|
||||||
|
|
||||||
|
|
||||||
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
|
class IRUnusedCodeRemover(
|
||||||
|
private val irprog: IRProgram,
|
||||||
|
private val errors: IErrorReporter
|
||||||
|
) {
|
||||||
fun optimize(): Int {
|
fun optimize(): Int {
|
||||||
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
|
var numRemoved = removeUnusedSubroutines() + removeUnusedAsmSubroutines()
|
||||||
|
|
||||||
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
|
// remove empty blocks
|
||||||
irprog.blocks.reversed().forEach { block ->
|
irprog.blocks.reversed().forEach { block ->
|
||||||
if(block.isEmpty()) {
|
if(block.isEmpty()) {
|
||||||
irprog.blocks.remove(block)
|
irprog.blocks.remove(block)
|
||||||
|
irprog.st.removeTree(block.label)
|
||||||
numRemoved++
|
numRemoved++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,8 +24,106 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
|||||||
return numRemoved
|
return numRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeUnusedSubroutines(): 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)
|
||||||
|
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)
|
||||||
|
irprog.st.removeTree(sub.label)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnusedAsmSubroutines(): Int {
|
||||||
|
val allLabeledAsmsubs = irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRAsmSubroutine>() }
|
||||||
|
.associateBy { it.label }
|
||||||
|
|
||||||
|
var numRemoved = removeSimpleUnlinkedAsmsubs(allLabeledAsmsubs)
|
||||||
|
irprog.blocks.forEach { block ->
|
||||||
|
block.children.filterIsInstance<IRAsmSubroutine>().reversed().forEach { sub ->
|
||||||
|
if(sub.isEmpty()) {
|
||||||
|
if(!sub.position.file.startsWith(libraryFilePrefix)) {
|
||||||
|
errors.warn("unused asmsubroutine ${sub.label}", sub.position)
|
||||||
|
}
|
||||||
|
block.children.remove(sub)
|
||||||
|
irprog.st.removeTree(sub.label)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeSimpleUnlinkedAsmsubs(allSubs: Map<String, IRAsmSubroutine>): Int {
|
||||||
|
val linkedAsmSubs = mutableSetOf<IRAsmSubroutine>()
|
||||||
|
|
||||||
|
// TODO: asmsubs in library modules are never removed, we can't really tell here if they're actually being called or not...
|
||||||
|
|
||||||
|
// check if asmsub is called from another asmsub
|
||||||
|
irprog.blocks.asSequence().forEach { block ->
|
||||||
|
block.children.filterIsInstance<IRAsmSubroutine>().forEach { sub ->
|
||||||
|
if (block.forceOutput || block.library)
|
||||||
|
linkedAsmSubs += sub
|
||||||
|
if (sub.asmChunk.isNotEmpty()) {
|
||||||
|
allSubs.forEach { (label, asmsub) ->
|
||||||
|
if (sub.asmChunk.assembly.contains(label))
|
||||||
|
linkedAsmSubs += asmsub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val inlineAsm = sub.asmChunk.next as? IRInlineAsmChunk
|
||||||
|
if(inlineAsm!=null) {
|
||||||
|
allSubs.forEach { (label, asmsub) ->
|
||||||
|
if (inlineAsm.assembly.contains(label))
|
||||||
|
linkedAsmSubs += asmsub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if asmsub is linked or called from another regular subroutine
|
||||||
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
|
sub.chunks.forEach { chunk ->
|
||||||
|
chunk.instructions.forEach {
|
||||||
|
it.labelSymbol?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
|
||||||
|
// note: branchTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeUnlinkedAsmsubs(linkedAsmSubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnlinkedAsmsubs(linkedAsmSubs: Set<IRAsmSubroutine>): Int {
|
||||||
|
var numRemoved = 0
|
||||||
|
irprog.blocks.asSequence().forEach { block ->
|
||||||
|
block.children.withIndex().reversed().forEach { (index, child) ->
|
||||||
|
if(child is IRAsmSubroutine && child !in linkedAsmSubs) {
|
||||||
|
block.children.removeAt(index)
|
||||||
|
numRemoved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numRemoved
|
||||||
|
}
|
||||||
|
|
||||||
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
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 entrypointSub = irprog.blocks.single { it.label=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||||
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||||
|
|
||||||
fun grow() {
|
fun grow() {
|
||||||
@ -91,7 +173,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeUnlinkedChunks(
|
private fun removeUnlinkedChunks(
|
||||||
linkedChunks: MutableSet<IRCodeChunkBase>
|
linkedChunks: Set<IRCodeChunkBase>
|
||||||
): Int {
|
): Int {
|
||||||
var numRemoved = 0
|
var numRemoved = 0
|
||||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package prog8.codegen.intermediate
|
package prog8.codegen.intermediate
|
||||||
|
|
||||||
import prog8.code.core.AssemblyError
|
|
||||||
import prog8.intermediate.SyscallRegisterBase
|
|
||||||
|
|
||||||
internal class RegisterPool {
|
internal class RegisterPool {
|
||||||
// reserve 0,1,2 for return values of subroutine calls and syscalls
|
// reserve 0,1,2 for return values of subroutine calls and syscalls in IR assembly code
|
||||||
private var firstFree: Int=3
|
private var firstFree: Int=3
|
||||||
private var firstFreeFloat: Int=3
|
private var firstFreeFloat: Int=3
|
||||||
|
|
||||||
@ -14,16 +11,12 @@ internal class RegisterPool {
|
|||||||
fun nextFree(): Int {
|
fun nextFree(): Int {
|
||||||
val result = firstFree
|
val result = firstFree
|
||||||
firstFree++
|
firstFree++
|
||||||
if(firstFree >= SyscallRegisterBase)
|
|
||||||
throw AssemblyError("out of virtual registers (int)")
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextFreeFloat(): Int {
|
fun nextFreeFloat(): Int {
|
||||||
val result = firstFreeFloat
|
val result = firstFreeFloat
|
||||||
firstFreeFloat++
|
firstFreeFloat++
|
||||||
if(firstFreeFloat >= SyscallRegisterBase)
|
|
||||||
throw AssemblyError("out of virtual registers (fp)")
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,31 +3,30 @@ package prog8.codegen.vm
|
|||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.ast.PtProgram
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.CompilationOptions
|
||||||
import prog8.code.core.IAssemblyGenerator
|
|
||||||
import prog8.code.core.IAssemblyProgram
|
import prog8.code.core.IAssemblyProgram
|
||||||
|
import prog8.code.core.ICodeGeneratorBackend
|
||||||
import prog8.code.core.IErrorReporter
|
import prog8.code.core.IErrorReporter
|
||||||
import prog8.codegen.intermediate.IRCodeGen
|
import prog8.codegen.intermediate.IRCodeGen
|
||||||
import prog8.intermediate.IRFileWriter
|
import prog8.intermediate.IRFileWriter
|
||||||
import prog8.intermediate.IRProgram
|
import prog8.intermediate.IRProgram
|
||||||
|
|
||||||
class VmCodeGen(private val program: PtProgram,
|
class VmCodeGen: ICodeGeneratorBackend {
|
||||||
private val symbolTable: SymbolTable,
|
override fun generate(
|
||||||
private val options: CompilationOptions,
|
program: PtProgram,
|
||||||
private val errors: IErrorReporter
|
symbolTable: SymbolTable,
|
||||||
): IAssemblyGenerator {
|
options: CompilationOptions,
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
errors: IErrorReporter
|
||||||
|
): IAssemblyProgram? {
|
||||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||||
val irProgram = irCodeGen.generate()
|
val irProgram = irCodeGen.generate()
|
||||||
|
|
||||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
|
internal class VmAssemblyProgram(override val name: String, internal val irProgram: IRProgram): IAssemblyProgram {
|
||||||
|
|
||||||
override fun assemble(options: CompilationOptions): Boolean {
|
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||||
// the VM reads the IR file from disk.
|
// the VM reads the IR file from disk.
|
||||||
IRFileWriter(irProgram, null).write()
|
IRFileWriter(irProgram, null).write()
|
||||||
return true
|
return true
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import prog8.code.core.DataType
|
import prog8.code.core.*
|
||||||
import prog8.code.core.Encoding
|
|
||||||
import prog8.code.core.IMemSizer
|
|
||||||
import prog8.code.core.IStringEncoding
|
|
||||||
|
|
||||||
|
|
||||||
internal object DummyMemsizer : IMemSizer {
|
internal object DummyMemsizer : IMemSizer {
|
||||||
override fun memorySize(dt: DataType) = 0
|
override fun memorySize(dt: DataType) = when(dt) {
|
||||||
override fun memorySize(arrayDt: DataType, numElements: Int) = 0
|
in ByteDatatypes -> 1
|
||||||
|
DataType.FLOAT -> 5
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
|
||||||
|
DataType.ARRAY_UW -> numElements*2
|
||||||
|
DataType.ARRAY_W -> numElements*2
|
||||||
|
DataType.ARRAY_F -> numElements*5
|
||||||
|
else -> numElements
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object DummyStringEncoder : IStringEncoding {
|
internal object DummyStringEncoder : IStringEncoding {
|
||||||
@ -14,7 +20,43 @@ internal object DummyStringEncoder : IStringEncoding {
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
|
||||||
|
IErrorReporter {
|
||||||
|
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
val warnings = mutableListOf<String>()
|
||||||
|
|
||||||
|
override fun err(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in errors)
|
||||||
|
errors.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun warn(msg: String, position: Position) {
|
||||||
|
val text = "${position.toClickableStr()} $msg"
|
||||||
|
if(text !in warnings)
|
||||||
|
warnings.add(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun noErrors(): Boolean = errors.isEmpty()
|
||||||
|
|
||||||
|
override fun report() {
|
||||||
|
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||||
|
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||||
|
if(throwExceptionAtReportIfErrors)
|
||||||
|
finalizeNumErrors(errors.size, warnings.size)
|
||||||
|
if(!keepMessagesAfterReporting) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
errors.clear()
|
||||||
|
warnings.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import prog8.intermediate.*
|
|||||||
class TestIRPeepholeOpt: FunSpec({
|
class TestIRPeepholeOpt: FunSpec({
|
||||||
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||||
require(chunks.first().label=="main.start")
|
require(chunks.first().label=="main.start")
|
||||||
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
val block = IRBlock("main", null, false, false, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
||||||
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
||||||
chunks.forEach { sub += it }
|
chunks.forEach { sub += it }
|
||||||
block += sub
|
block += sub
|
||||||
@ -40,13 +40,13 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
|
|
||||||
test("remove nops") {
|
test("remove nops") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
|
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42),
|
||||||
IRInstruction(Opcode.NOP),
|
IRInstruction(Opcode.NOP),
|
||||||
IRInstruction(Opcode.NOP)
|
IRInstruction(Opcode.NOP)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 3
|
irProg.chunks().single().instructions.size shouldBe 3
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().single().instructions.size shouldBe 1
|
irProg.chunks().single().instructions.size shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
irProg.chunks().size shouldBe 4
|
irProg.chunks().size shouldBe 4
|
||||||
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().size shouldBe 4
|
irProg.chunks().size shouldBe 4
|
||||||
irProg.chunks()[0].label shouldBe "main.start"
|
irProg.chunks()[0].label shouldBe "main.start"
|
||||||
irProg.chunks()[1].label shouldBe "label"
|
irProg.chunks()[1].label shouldBe "label"
|
||||||
@ -92,7 +92,7 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 6
|
irProg.chunks().single().instructions.size shouldBe 6
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 1
|
instr.size shouldBe 1
|
||||||
instr[0].opcode shouldBe Opcode.CLC
|
instr[0].opcode shouldBe Opcode.CLC
|
||||||
@ -107,7 +107,7 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 1
|
instr.size shouldBe 1
|
||||||
instr[0].opcode shouldBe Opcode.LOADR
|
instr[0].opcode shouldBe Opcode.LOADR
|
||||||
@ -117,31 +117,31 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
|
|
||||||
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, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
|
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 0)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 10
|
irProg.chunks().single().instructions.size shouldBe 10
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.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, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 2
|
irProg.chunks().single().instructions.size shouldBe 2
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 2
|
instr.size shouldBe 2
|
||||||
instr[0].opcode shouldBe Opcode.INC
|
instr[0].opcode shouldBe Opcode.INC
|
||||||
@ -150,40 +150,40 @@ class TestIRPeepholeOpt: FunSpec({
|
|||||||
|
|
||||||
test("remove useless and/or/xor") {
|
test("remove useless and/or/xor") {
|
||||||
val irProg = makeIRProgram(listOf(
|
val irProg = makeIRProgram(listOf(
|
||||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 255),
|
||||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 65535),
|
||||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 200),
|
||||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 60000),
|
||||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
|
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 8
|
irProg.chunks().single().instructions.size shouldBe 8
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.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, IRDataType.BYTE, reg1=42, value = 0),
|
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
|
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 0),
|
||||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
|
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 255),
|
||||||
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
|
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, immediate = 65535)
|
||||||
))
|
))
|
||||||
irProg.chunks().single().instructions.size shouldBe 4
|
irProg.chunks().single().instructions.size shouldBe 4
|
||||||
val opt = IRPeepholeOptimizer(irProg)
|
val opt = IRPeepholeOptimizer(irProg)
|
||||||
opt.optimize()
|
opt.optimize(true, ErrorReporterForTests())
|
||||||
val instr = irProg.chunks().single().instructions
|
val instr = irProg.chunks().single().instructions
|
||||||
instr.size shouldBe 4
|
instr.size shouldBe 4
|
||||||
instr[0].opcode shouldBe Opcode.LOAD
|
instr[0].opcode shouldBe Opcode.LOAD
|
||||||
instr[1].opcode shouldBe Opcode.LOAD
|
instr[1].opcode shouldBe Opcode.LOAD
|
||||||
instr[2].opcode shouldBe Opcode.LOAD
|
instr[2].opcode shouldBe Opcode.LOAD
|
||||||
instr[3].opcode shouldBe Opcode.LOAD
|
instr[3].opcode shouldBe Opcode.LOAD
|
||||||
instr[0].value shouldBe 0
|
instr[0].immediate shouldBe 0
|
||||||
instr[1].value shouldBe 0
|
instr[1].immediate shouldBe 0
|
||||||
instr[2].value shouldBe 255
|
instr[2].immediate shouldBe 255
|
||||||
instr[3].value shouldBe 65535
|
instr[3].immediate shouldBe 65535
|
||||||
}
|
}
|
||||||
})
|
})
|
473
codeGenIntermediate/test/TestVmCodeGen.kt
Normal file
473
codeGenIntermediate/test/TestVmCodeGen.kt
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import prog8.code.SymbolTableMaker
|
||||||
|
import prog8.code.ast.*
|
||||||
|
import prog8.code.core.*
|
||||||
|
import prog8.code.target.VMTarget
|
||||||
|
import prog8.codegen.vm.VmAssemblyProgram
|
||||||
|
import prog8.codegen.vm.VmCodeGen
|
||||||
|
import prog8.intermediate.IRSubroutine
|
||||||
|
import prog8.intermediate.Opcode
|
||||||
|
|
||||||
|
class TestVmCodeGen: FunSpec({
|
||||||
|
|
||||||
|
fun getTestOptions(): CompilationOptions {
|
||||||
|
val target = VMTarget()
|
||||||
|
return CompilationOptions(
|
||||||
|
OutputType.RAW,
|
||||||
|
CbmPrgLauncherType.NONE,
|
||||||
|
ZeropageType.DONTUSE,
|
||||||
|
zpReserved = emptyList(),
|
||||||
|
floats = true,
|
||||||
|
noSysInit = false,
|
||||||
|
compTarget = target,
|
||||||
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("augmented assigns") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// ubyte[] particleX = [1,2,3]
|
||||||
|
// ubyte[] particleDX = [1,2,3]
|
||||||
|
// particleX[2] += particleDX[2]
|
||||||
|
//
|
||||||
|
// word @shared xx = 1
|
||||||
|
// xx = -xx
|
||||||
|
// xx += 42
|
||||||
|
// xx += cx16.r0
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||||
|
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||||
|
|
||||||
|
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val target = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||||
|
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
}
|
||||||
|
it.add(targetIdx)
|
||||||
|
}
|
||||||
|
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
|
||||||
|
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
|
||||||
|
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||||
|
assign.add(target)
|
||||||
|
assign.add(value)
|
||||||
|
sub.add(assign)
|
||||||
|
|
||||||
|
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||||
|
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
prefixAssign.add(prefixTarget)
|
||||||
|
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
sub.add(prefixAssign)
|
||||||
|
|
||||||
|
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
numberAssign.add(numberAssignTarget)
|
||||||
|
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
|
||||||
|
sub.add(numberAssign)
|
||||||
|
|
||||||
|
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||||
|
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||||
|
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||||
|
}
|
||||||
|
cxregAssign.add(cxregAssignTarget)
|
||||||
|
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
|
||||||
|
sub.add(cxregAssign)
|
||||||
|
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
// define the "cx16.r0" virtual register
|
||||||
|
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||||
|
program.add(cx16block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("float comparison expressions against zero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// float @shared f1
|
||||||
|
//
|
||||||
|
// if f1==0
|
||||||
|
// nop
|
||||||
|
// if f1!=0
|
||||||
|
// nop
|
||||||
|
// if f1>0
|
||||||
|
// nop
|
||||||
|
// if f1<0
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.FLOAT, 0.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("float comparison expressions against nonzero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// float @shared f1
|
||||||
|
//
|
||||||
|
// if f1==42
|
||||||
|
// nop
|
||||||
|
// if f1!=42
|
||||||
|
// nop
|
||||||
|
// if f1>42
|
||||||
|
// nop
|
||||||
|
// if f1<42
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("float conditional jump") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// float @shared f1
|
||||||
|
//
|
||||||
|
// if f1==42
|
||||||
|
// goto $c000
|
||||||
|
// if f1>42
|
||||||
|
// goto $c000
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup())
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.f1", DataType.FLOAT, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.FLOAT, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup())
|
||||||
|
sub.add(if2)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("integer comparison expressions against zero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// byte @shared sb1
|
||||||
|
//
|
||||||
|
// if sb1==0
|
||||||
|
// nop
|
||||||
|
// if sb1!=0
|
||||||
|
// nop
|
||||||
|
// if sb1>0
|
||||||
|
// nop
|
||||||
|
// if sb1<0
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.BYTE, 0.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("integer comparison expressions against nonzero") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// byte @shared sb1
|
||||||
|
//
|
||||||
|
// if sb1==42
|
||||||
|
// nop
|
||||||
|
// if sb1!=42
|
||||||
|
// nop
|
||||||
|
// if sb1>42
|
||||||
|
// nop
|
||||||
|
// if sb1<42
|
||||||
|
// nop
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression("!=", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if2)
|
||||||
|
val if3 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp3 = PtBinaryExpression("<", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp3.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp3.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if3.add(cmp3)
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if3.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if3)
|
||||||
|
val if4 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp4 = PtBinaryExpression(">", DataType.BYTE, Position.DUMMY)
|
||||||
|
cmp4.add(PtIdentifier("main.start.sb1", DataType.BYTE, Position.DUMMY))
|
||||||
|
cmp4.add(PtNumber(DataType.BYTE, 42.0, Position.DUMMY))
|
||||||
|
if4.add(cmp4)
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
if4.add(PtNodeGroup().also { it.add(PtNop(Position.DUMMY)) })
|
||||||
|
sub.add(if4)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBeGreaterThan 4
|
||||||
|
}
|
||||||
|
|
||||||
|
test("integer conditional jump") {
|
||||||
|
//main {
|
||||||
|
// sub start() {
|
||||||
|
// ubyte @shared ub1
|
||||||
|
//
|
||||||
|
// if ub1==42
|
||||||
|
// goto $c000
|
||||||
|
// if ub1>42
|
||||||
|
// goto $c000
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||||
|
val if1 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp1 = PtBinaryExpression("==", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp1.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
|
||||||
|
cmp1.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
|
||||||
|
if1.add(cmp1)
|
||||||
|
if1.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
|
||||||
|
if1.add(PtNodeGroup())
|
||||||
|
sub.add(if1)
|
||||||
|
val if2 = PtIfElse(Position.DUMMY)
|
||||||
|
val cmp2 = PtBinaryExpression(">", DataType.UBYTE, Position.DUMMY)
|
||||||
|
cmp2.add(PtIdentifier("main.start.ub1", DataType.UBYTE, Position.DUMMY))
|
||||||
|
cmp2.add(PtNumber(DataType.UBYTE, 42.0, Position.DUMMY))
|
||||||
|
if2.add(cmp2)
|
||||||
|
if2.add(PtNodeGroup().also { it.add(PtJump(null, 0xc000u, null, Position.DUMMY)) })
|
||||||
|
if2.add(PtNodeGroup())
|
||||||
|
sub.add(if2)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test("romsub allowed in ir-codegen") {
|
||||||
|
//main {
|
||||||
|
// romsub $5000 = routine()
|
||||||
|
//
|
||||||
|
// sub start() {
|
||||||
|
// routine()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
val codegen = VmCodeGen()
|
||||||
|
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||||
|
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||||
|
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||||
|
block.add(romsub)
|
||||||
|
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||||
|
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
|
||||||
|
sub.add(call)
|
||||||
|
block.add(sub)
|
||||||
|
program.add(block)
|
||||||
|
|
||||||
|
val options = getTestOptions()
|
||||||
|
val st = SymbolTableMaker(program, options).make()
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||||
|
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||||
|
irChunks.size shouldBe 1
|
||||||
|
val callInstr = irChunks.single().instructions.single()
|
||||||
|
callInstr.opcode shouldBe Opcode.CALL
|
||||||
|
callInstr.address shouldBe 0x5000
|
||||||
|
}
|
||||||
|
})
|
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>
|
@ -4,9 +4,6 @@ import prog8.ast.IStatementContainer
|
|||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.expressions.BinaryExpression
|
import prog8.ast.expressions.BinaryExpression
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.TypecastExpression
|
|
||||||
import prog8.ast.getTempVar
|
|
||||||
import prog8.ast.statements.AssignTarget
|
import prog8.ast.statements.AssignTarget
|
||||||
import prog8.ast.statements.Assignment
|
import prog8.ast.statements.Assignment
|
||||||
import prog8.ast.statements.AssignmentOrigin
|
import prog8.ast.statements.AssignmentOrigin
|
||||||
@ -31,23 +28,6 @@ class BinExprSplitter(private val program: Program, private val options: Compila
|
|||||||
val binExpr = assignment.value as? BinaryExpression
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
if (binExpr != null) {
|
if (binExpr != null) {
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
|
||||||
by attempting to splitting it up into individual simple steps.
|
|
||||||
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
|
|
||||||
|
|
||||||
|
|
||||||
X = BinExpr X = LeftExpr
|
|
||||||
<operator> followed by
|
|
||||||
/ \ IF 'X' not used X = BinExpr
|
|
||||||
/ \ IN expression ==> <operator>
|
|
||||||
/ \ / \
|
|
||||||
LeftExpr. RightExpr. / \
|
|
||||||
X RightExpr.
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||||
if(assignment.target isSameAs binExpr.right)
|
if(assignment.target isSameAs binExpr.right)
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -60,20 +40,6 @@ X = BinExpr X = LeftExpr
|
|||||||
val rightBx = binExpr.right as? BinaryExpression
|
val rightBx = binExpr.right as? BinaryExpression
|
||||||
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
||||||
return noModifications
|
return noModifications
|
||||||
|
|
||||||
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
|
|
||||||
// val dt = assignment.target.inferType(program)
|
|
||||||
// if(!dt.isInteger)
|
|
||||||
// return noModifications
|
|
||||||
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
|
|
||||||
// val assignTempVar = Assignment(
|
|
||||||
// AssignTarget(tempVar, null, null, binExpr.right.position),
|
|
||||||
// binExpr.right, binExpr.right.position
|
|
||||||
// )
|
|
||||||
// return listOf(
|
|
||||||
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
|
|
||||||
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(binExpr.right.isSimple) {
|
if(binExpr.right.isSimple) {
|
||||||
@ -87,30 +53,10 @@ X = BinExpr X = LeftExpr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO further unraveling of binary expression trees into flat statements.
|
// Further unraveling of binary expressions is really complicated here and
|
||||||
// however this should probably be done in a more generic way to also work on
|
// often results in much bigger code, thereby defeating the purpose a bit.
|
||||||
// the expressiontrees that are not used in an assignment statement...
|
// All in all this should probably be fixed in a better code generation backend
|
||||||
}
|
// that doesn't require this at all.
|
||||||
|
|
||||||
val typecast = assignment.value as? TypecastExpression
|
|
||||||
if(typecast!=null) {
|
|
||||||
val origExpr = typecast.expression as? BinaryExpression
|
|
||||||
if(origExpr!=null && options.compTarget.name!=VMTarget.NAME) {
|
|
||||||
// it's a typecast of a binary expression.
|
|
||||||
// we can see if we can unwrap the binary expression by working on a new temporary variable
|
|
||||||
// (that has the type of the expression), and then finally doing the typecast.
|
|
||||||
// Once it's outside the typecast, the regular splitting can commence.
|
|
||||||
val tempvarDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
|
|
||||||
val (tempVarName, _) = program.getTempVar(tempvarDt)
|
|
||||||
val assignTempVar = Assignment(
|
|
||||||
AssignTarget(IdentifierReference(tempVarName, typecast.position), null, null, typecast.position),
|
|
||||||
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
|
|
||||||
)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
|
|
||||||
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVarName, typecast.position), typecast)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
|
@ -354,9 +354,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
|||||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||||
val valueDt = numval.inferType(program)
|
val valueDt = numval.inferType(program)
|
||||||
if(valueDt isnot decl.datatype) {
|
if(valueDt isnot decl.datatype) {
|
||||||
val cast = numval.cast(decl.datatype)
|
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
|
||||||
if(cast.isValid)
|
val cast = numval.cast(decl.datatype)
|
||||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
if (cast.isValid)
|
||||||
|
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -386,9 +388,9 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
|||||||
subleftIsConst: Boolean,
|
subleftIsConst: Boolean,
|
||||||
subrightIsConst: Boolean): IAstModification?
|
subrightIsConst: Boolean): IAstModification?
|
||||||
{
|
{
|
||||||
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
|
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
|
||||||
|
// TODO: this implements only a small set of possible reorderings at this time, we could perhaps add more
|
||||||
|
|
||||||
// TODO: this implements only a small set of possible reorderings at this time, we could think of more
|
|
||||||
if(expr.operator==subExpr.operator) {
|
if(expr.operator==subExpr.operator) {
|
||||||
// both operators are the same.
|
// both operators are the same.
|
||||||
|
|
||||||
|
@ -87,8 +87,27 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
if(forloop!=null && identifier===forloop.loopVar)
|
if(forloop!=null && identifier===forloop.loopVar)
|
||||||
return noModifications
|
return noModifications
|
||||||
|
|
||||||
|
val dt = identifier.inferType(program)
|
||||||
|
if(!dt.isKnown || !dt.isNumeric)
|
||||||
|
return noModifications
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val cval = identifier.constValue(program) ?: return noModifications
|
val cval = identifier.constValue(program) ?: return noModifications
|
||||||
|
val arrayIdx = identifier.parent as? ArrayIndexedExpression
|
||||||
|
if(arrayIdx!=null && cval.type in NumericDatatypes) {
|
||||||
|
// special case when the identifier is used as a pointer var
|
||||||
|
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
|
||||||
|
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
|
||||||
|
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
|
||||||
|
return if(arrayIdx.parent is AssignTarget) {
|
||||||
|
val memwrite = DirectMemoryWrite(add, identifier.position)
|
||||||
|
val assignTarget = AssignTarget(null, null, memwrite, identifier.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||||
|
} else {
|
||||||
|
val memread = DirectMemoryRead(add, identifier.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
return when (cval.type) {
|
return when (cval.type) {
|
||||||
in NumericDatatypes -> listOf(
|
in NumericDatatypes -> listOf(
|
||||||
IAstModification.ReplaceNode(
|
IAstModification.ReplaceNode(
|
||||||
@ -223,6 +242,17 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DataType.ARRAY_BOOL -> {
|
||||||
|
val numericLv = decl.value as? NumericLiteral
|
||||||
|
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||||
|
if(numericLv!=null) {
|
||||||
|
// arraysize initializer is a single int, and we know the size.
|
||||||
|
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
|
||||||
|
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
|
||||||
|
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// nothing to do for this type
|
// nothing to do for this type
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import kotlin.math.abs
|
|||||||
import kotlin.math.log2
|
import kotlin.math.log2
|
||||||
import kotlin.math.pow
|
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
|
||||||
|
|
||||||
class ExpressionSimplifier(private val program: Program,
|
class ExpressionSimplifier(private val program: Program,
|
||||||
private val errors: IErrorReporter,
|
private val errors: IErrorReporter,
|
||||||
@ -187,12 +187,37 @@ class ExpressionSimplifier(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X <= Y-1 ---> X<Y , X >= Y+1 ---> X>Y
|
||||||
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
|
val rightExpr = expr.right as? BinaryExpression
|
||||||
|
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
|
||||||
|
if (expr.operator == "<=" && rightExpr.operator == "-") {
|
||||||
|
expr.operator = "<"
|
||||||
|
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
|
||||||
|
} else if (expr.operator == ">=" && rightExpr.operator == "+") {
|
||||||
|
expr.operator = ">"
|
||||||
|
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
|
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
|
||||||
// for integers: x >= 1 --> x > 0
|
// for integers: x >= 1 --> x > 0
|
||||||
expr.operator = ">"
|
expr.operator = ">"
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for signed integers: X <= -1 => X<0 , X > -1 => X>=0
|
||||||
|
if(leftDt in SignedDatatypes && leftDt!=DataType.FLOAT && rightVal?.number==-1.0) {
|
||||||
|
if(expr.operator=="<=") {
|
||||||
|
expr.operator = "<"
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
|
||||||
|
} else if(expr.operator==">") {
|
||||||
|
expr.operator = ">="
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||||
// unsigned >= 0 --> true
|
// unsigned >= 0 --> true
|
||||||
@ -231,6 +256,18 @@ class ExpressionSimplifier(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
@ -118,9 +118,11 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun makeFullyScoped(identifier: IdentifierReference) {
|
private fun makeFullyScoped(identifier: IdentifierReference) {
|
||||||
val scoped = (identifier.targetStatement(program)!! as INamedStatement).scopedName
|
identifier.targetStatement(program)?.let { target ->
|
||||||
val scopedIdent = IdentifierReference(scoped, identifier.position)
|
val scoped = (target as INamedStatement).scopedName
|
||||||
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
|
val scopedIdent = IdentifierReference(scoped, identifier.position)
|
||||||
|
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
|
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
|
||||||
@ -130,27 +132,30 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun makeFullyScoped(call: FunctionCallStatement) {
|
private fun makeFullyScoped(call: FunctionCallStatement) {
|
||||||
val sub = call.target.targetSubroutine(program)!!
|
call.target.targetSubroutine(program)?.let { sub ->
|
||||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||||
val scopedArgs = makeScopedArgs(call.args)
|
val scopedArgs = makeScopedArgs(call.args)
|
||||||
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
|
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
|
||||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeFullyScoped(call: BuiltinFunctionCall) {
|
private fun makeFullyScoped(call: BuiltinFunctionCall) {
|
||||||
val sub = call.target.targetSubroutine(program)!!
|
call.target.targetSubroutine(program)?.let { sub ->
|
||||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||||
val scopedArgs = makeScopedArgs(call.args)
|
val scopedArgs = makeScopedArgs(call.args)
|
||||||
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
|
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
|
||||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeFullyScoped(call: FunctionCallExpression) {
|
private fun makeFullyScoped(call: FunctionCallExpression) {
|
||||||
val sub = call.target.targetSubroutine(program)!!
|
call.target.targetSubroutine(program)?.let { sub ->
|
||||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||||
val scopedArgs = makeScopedArgs(call.args)
|
val scopedArgs = makeScopedArgs(call.args)
|
||||||
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
|
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
|
||||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeScopedArgs(args: List<Expression>): List<Expression> {
|
private fun makeScopedArgs(args: List<Expression>): List<Expression> {
|
||||||
@ -179,6 +184,7 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
}
|
}
|
||||||
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
|
||||||
|
sub.hasBeenInlined=true
|
||||||
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
|
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
|
||||||
} else {
|
} else {
|
||||||
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
||||||
@ -187,12 +193,16 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
val fcall = toInline.value as? FunctionCallExpression
|
val fcall = toInline.value as? FunctionCallExpression
|
||||||
if(fcall!=null) {
|
if(fcall!=null) {
|
||||||
// insert the function call expression as a void function call directly
|
// insert the function call expression as a void function call directly
|
||||||
|
sub.hasBeenInlined=true
|
||||||
val call = FunctionCallStatement(fcall.target.copy(), fcall.args.map { it.copy() }.toMutableList(), true, fcall.position)
|
val call = FunctionCallStatement(fcall.target.copy(), fcall.args.map { it.copy() }.toMutableList(), true, fcall.position)
|
||||||
listOf(IAstModification.ReplaceNode(origNode, call, parent))
|
listOf(IAstModification.ReplaceNode(origNode, call, parent))
|
||||||
} else
|
} else
|
||||||
noModifications
|
noModifications
|
||||||
}
|
}
|
||||||
else -> listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
|
else -> {
|
||||||
|
sub.hasBeenInlined=true
|
||||||
|
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,8 +231,10 @@ class Inliner(val program: Program): AstWalker() {
|
|||||||
is Return -> {
|
is Return -> {
|
||||||
// is an expression, so we have to have a Return here in the inlined sub
|
// is an expression, so we have to have a Return here in the inlined sub
|
||||||
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
||||||
if(toInline.value!=null)
|
if(toInline.value!=null) {
|
||||||
|
sub.hasBeenInlined=true
|
||||||
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.value!!.copy(), parent))
|
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.value!!.copy(), parent))
|
||||||
|
}
|
||||||
else
|
else
|
||||||
noModifications
|
noModifications
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,16 @@ class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val iterationCount = forLoop.constIterationCount(program)
|
||||||
|
if(iterationCount!=null) {
|
||||||
|
val loopName = forLoop.loopVar.nameInSource
|
||||||
|
if(!forLoop.iterable.referencesIdentifier(loopName) && !forLoop.body.referencesIdentifier(loopName)) {
|
||||||
|
errors.warn("for loop can be replaced with repeat loop", forLoop.position)
|
||||||
|
val repeat = RepeatLoop(NumericLiteral.optimalNumeric(iterationCount, forLoop.position), forLoop.body, forLoop.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop, repeat, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,13 +326,13 @@ class StatementOptimizer(private val program: Program,
|
|||||||
if (rightCv == 0.0) {
|
if (rightCv == 0.0) {
|
||||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0 && compTarget.name!=VMTarget.NAME) {
|
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
|
||||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
repeat(rightCv.toInt()) {
|
repeat(rightCv.toInt()) {
|
||||||
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
|
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
|
||||||
}
|
}
|
||||||
listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
|
return listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,7 +340,7 @@ class StatementOptimizer(private val program: Program,
|
|||||||
if (rightCv == 0.0) {
|
if (rightCv == 0.0) {
|
||||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0 && compTarget.name!=VMTarget.NAME) {
|
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
|
||||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
repeat(rightCv.toInt()) {
|
repeat(rightCv.toInt()) {
|
||||||
@ -374,4 +384,11 @@ class StatementOptimizer(private val program: Program,
|
|||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
return if(unrollLoop.iterations<1)
|
||||||
|
listOf(IAstModification.Remove(unrollLoop, parent as IStatementContainer))
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,13 +64,17 @@ class UnusedCodeRemover(private val program: Program,
|
|||||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||||
if("force_output" !in block.options()) {
|
if("force_output" !in block.options()) {
|
||||||
if (block.containsNoCodeNorVars) {
|
if (block.containsNoCodeNorVars) {
|
||||||
if(block.name != internedStringsModuleName)
|
if(block.name != internedStringsModuleName) {
|
||||||
errors.warn("removing unused block '${block.name}'", block.position)
|
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
|
||||||
|
errors.warn("removing unused block '${block.name}'", block.position)
|
||||||
|
}
|
||||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
if(callgraph.unused(block)) {
|
if(callgraph.unused(block)) {
|
||||||
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR})
|
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR}) {
|
||||||
errors.warn("removing unused block '${block.name}'", block.position)
|
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
|
||||||
|
errors.warn("removing unused block '${block.name}'", block.position)
|
||||||
|
}
|
||||||
program.removeInternedStringsFromRemovedBlock(block)
|
program.removeInternedStringsFromRemovedBlock(block)
|
||||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
@ -81,7 +85,7 @@ class UnusedCodeRemover(private val program: Program,
|
|||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
||||||
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
|
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.isAsmSubroutine) {
|
||||||
if(callgraph.unused(subroutine)) {
|
if(callgraph.unused(subroutine)) {
|
||||||
if(subroutine.containsNoCodeNorVars) {
|
if(subroutine.containsNoCodeNorVars) {
|
||||||
if(!subroutine.definingModule.isLibrary)
|
if(!subroutine.definingModule.isLibrary)
|
||||||
@ -93,9 +97,12 @@ class UnusedCodeRemover(private val program: Program,
|
|||||||
}
|
}
|
||||||
return removals
|
return removals
|
||||||
}
|
}
|
||||||
if(!subroutine.definingModule.isLibrary)
|
if(!subroutine.definingModule.isLibrary && !subroutine.hasBeenInlined) {
|
||||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
errors.warn("unused subroutine '${subroutine.name}'", subroutine.position)
|
||||||
program.removeInternedStringsFromRemovedSubroutine(subroutine)
|
}
|
||||||
|
if(!subroutine.inline) {
|
||||||
|
program.removeInternedStringsFromRemovedSubroutine(subroutine)
|
||||||
|
}
|
||||||
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
|
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +117,7 @@ class UnusedCodeRemover(private val program: Program,
|
|||||||
if (!forceOutput && decl.origin==VarDeclOrigin.USERCODE && !decl.sharedWithAsm) {
|
if (!forceOutput && decl.origin==VarDeclOrigin.USERCODE && !decl.sharedWithAsm) {
|
||||||
val usages = callgraph.usages(decl)
|
val usages = callgraph.usages(decl)
|
||||||
if (usages.isEmpty()) {
|
if (usages.isEmpty()) {
|
||||||
// if(!decl.definingModule.isLibrary)
|
if(!decl.definingModule.isLibrary)
|
||||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||||
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
@ -118,8 +125,8 @@ class UnusedCodeRemover(private val program: Program,
|
|||||||
if(usages.size==1) {
|
if(usages.size==1) {
|
||||||
val singleUse = usages[0].parent
|
val singleUse = usages[0].parent
|
||||||
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!=null && assignment.origin==AssignmentOrigin.VARINIT) {
|
||||||
if(assignment.value.isSimple) {
|
if(assignment.value.isSimple) {
|
||||||
// remove the vardecl
|
// remove the vardecl
|
||||||
if(!decl.definingModule.isLibrary)
|
if(!decl.definingModule.isLibrary)
|
||||||
|
@ -34,14 +34,14 @@ dependencies {
|
|||||||
implementation project(':codeGenIntermediate')
|
implementation project(':codeGenIntermediate')
|
||||||
implementation project(':codeGenExperimental')
|
implementation project(':codeGenExperimental')
|
||||||
implementation project(':virtualmachine')
|
implementation project(':virtualmachine')
|
||||||
implementation 'org.antlr:antlr4-runtime:4.11.1'
|
implementation "org.antlr:antlr4-runtime:4.12.0"
|
||||||
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.5'
|
||||||
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 project(':intermediate')
|
||||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
|
@ -1,664 +0,0 @@
|
|||||||
; --- low level floating point assembly routines for the C128
|
|
||||||
; these are almost all identical to the C64 except for a few details
|
|
||||||
; so we have to have a separate library file for the C128 unfortunately.
|
|
||||||
|
|
||||||
|
|
||||||
FL_ONE_const .byte 129 ; 1.0
|
|
||||||
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
|
|
||||||
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
|
||||||
|
|
||||||
|
|
||||||
floats_store_reg .byte 0 ; temp storage
|
|
||||||
|
|
||||||
|
|
||||||
ub2float .proc
|
|
||||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
|
||||||
; clobbers A, Y
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
ldy P8ZP_SCRATCH_B1
|
|
||||||
lda #0
|
|
||||||
jsr GIVAYF
|
|
||||||
_fac_to_mem ldx P8ZP_SCRATCH_W2
|
|
||||||
ldy P8ZP_SCRATCH_W2+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
b2float .proc
|
|
||||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
|
||||||
; clobbers A, Y
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
lda P8ZP_SCRATCH_B1
|
|
||||||
jsr FREADSA
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
uw2float .proc
|
|
||||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
lda P8ZP_SCRATCH_W1
|
|
||||||
ldy P8ZP_SCRATCH_W1+1
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
w2float .proc
|
|
||||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
ldy P8ZP_SCRATCH_W1
|
|
||||||
lda P8ZP_SCRATCH_W1+1
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
cast_from_uw .proc
|
|
||||||
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
cast_from_w .proc
|
|
||||||
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr GIVAYFAY
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
cast_from_ub .proc
|
|
||||||
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FREADUY
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
cast_from_b .proc
|
|
||||||
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FREADSA
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
cast_as_uw_into_ya .proc ; also used for float 2 ub
|
|
||||||
; -- cast float at A/Y to uword into Y/A
|
|
||||||
jsr MOVFM
|
|
||||||
jmp cast_FAC1_as_uw_into_ya
|
|
||||||
.pend
|
|
||||||
|
|
||||||
cast_as_w_into_ay .proc ; also used for float 2 b
|
|
||||||
; -- cast float at A/Y to word into A/Y
|
|
||||||
jsr MOVFM
|
|
||||||
jmp cast_FAC1_as_w_into_ay
|
|
||||||
.pend
|
|
||||||
|
|
||||||
cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
|
|
||||||
; -- cast fac1 to uword into Y/A
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr GETADR ; into Y/A
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
|
||||||
; -- cast fac1 to word into A/Y
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr AYINT
|
|
||||||
ldy floats.AYINT_facmo
|
|
||||||
lda floats.AYINT_facmo+1
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
stack_b2float .proc
|
|
||||||
; -- b2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda P8ESTACK_LO,x
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FREADSA
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_w2float .proc
|
|
||||||
; -- w2float operating on the stack
|
|
||||||
inx
|
|
||||||
ldy P8ESTACK_LO,x
|
|
||||||
lda P8ESTACK_HI,x
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_ub2float .proc
|
|
||||||
; -- ub2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda P8ESTACK_LO,x
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
tay
|
|
||||||
lda #0
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_uw2float .proc
|
|
||||||
; -- uw2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda P8ESTACK_LO,x
|
|
||||||
ldy P8ESTACK_HI,x
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2w .proc ; also used for float2b
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr AYINT
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
lda floats.AYINT_facmo
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
lda floats.AYINT_facmo+1
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2uw .proc ; also used for float2ub
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr GETADR
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
tya
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
push_float .proc
|
|
||||||
; ---- push mflpt5 in A/Y onto stack
|
|
||||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
ldy #0
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
sta P8ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float .proc
|
|
||||||
; ---- pops mflpt5 from stack to memory A/Y
|
|
||||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
ldy #4
|
|
||||||
inx
|
|
||||||
lda P8ESTACK_LO,x
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda P8ESTACK_HI,x
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
dey
|
|
||||||
lda P8ESTACK_LO,x
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda P8ESTACK_HI,x
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
dey
|
|
||||||
lda P8ESTACK_LO,x
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float_fac1 .proc
|
|
||||||
; -- pops float from stack into FAC1
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
copy_float .proc
|
|
||||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
|
||||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
|
||||||
sta _target+1
|
|
||||||
sty _target+2
|
|
||||||
ldy #4
|
|
||||||
_loop lda (P8ZP_SCRATCH_W1),y
|
|
||||||
_target sta $ffff,y ; modified
|
|
||||||
dey
|
|
||||||
bpl _loop
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
inc_var_f .proc
|
|
||||||
; -- add 1 to float pointed to by A/Y
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr MOVFM
|
|
||||||
lda #<FL_ONE_const
|
|
||||||
ldy #>FL_ONE_const
|
|
||||||
jsr FADD
|
|
||||||
ldx P8ZP_SCRATCH_W1
|
|
||||||
ldy P8ZP_SCRATCH_W1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
dec_var_f .proc
|
|
||||||
; -- subtract 1 from float pointed to by A/Y
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
lda #<FL_ONE_const
|
|
||||||
ldy #>FL_ONE_const
|
|
||||||
jsr MOVFM
|
|
||||||
lda P8ZP_SCRATCH_W1
|
|
||||||
ldy P8ZP_SCRATCH_W1+1
|
|
||||||
jsr FSUB
|
|
||||||
ldx P8ZP_SCRATCH_W1
|
|
||||||
ldy P8ZP_SCRATCH_W1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
pop_2_floats_f2_in_fac1 .proc
|
|
||||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
|
|
||||||
|
|
||||||
push_fac1 .proc
|
|
||||||
; -- push the float in FAC1 onto the stack
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
_internal ldx #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVMF
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
jmp push_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
div_f .proc
|
|
||||||
; -- push f1/f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FDIV
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
add_f .proc
|
|
||||||
; -- push f1+f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FADD
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
sub_f .proc
|
|
||||||
; -- push f1-f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FSUB
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
mul_f .proc
|
|
||||||
; -- push f1*f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1._internal
|
|
||||||
.pend
|
|
||||||
|
|
||||||
neg_f .proc
|
|
||||||
; -- toggle the sign bit on the stack
|
|
||||||
lda P8ESTACK_HI+3,x
|
|
||||||
eor #$80
|
|
||||||
sta P8ESTACK_HI+3,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
var_fac1_less_f .proc
|
|
||||||
; -- is the float in FAC1 < the variable AY?
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
cmp #255
|
|
||||||
beq +
|
|
||||||
lda #0
|
|
||||||
rts
|
|
||||||
+ lda #1
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
var_fac1_lesseq_f .proc
|
|
||||||
; -- is the float in FAC1 <= the variable AY?
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
cmp #255
|
|
||||||
beq +
|
|
||||||
lda #0
|
|
||||||
rts
|
|
||||||
+ lda #1
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
var_fac1_greater_f .proc
|
|
||||||
; -- is the float in FAC1 > the variable AY?
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
cmp #1
|
|
||||||
beq +
|
|
||||||
lda #0
|
|
||||||
rts
|
|
||||||
+ lda #1
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
var_fac1_greatereq_f .proc
|
|
||||||
; -- is the float in FAC1 >= the variable AY?
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
cmp #1
|
|
||||||
beq +
|
|
||||||
lda #0
|
|
||||||
rts
|
|
||||||
+ lda #1
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
var_fac1_notequal_f .proc
|
|
||||||
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
and #1
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
vars_equal_f .proc
|
|
||||||
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
ldy #0
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
cmp (P8ZP_SCRATCH_W2),y
|
|
||||||
bne _false
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
cmp (P8ZP_SCRATCH_W2),y
|
|
||||||
bne _false
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
cmp (P8ZP_SCRATCH_W2),y
|
|
||||||
bne _false
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
cmp (P8ZP_SCRATCH_W2),y
|
|
||||||
bne _false
|
|
||||||
iny
|
|
||||||
lda (P8ZP_SCRATCH_W1),y
|
|
||||||
cmp (P8ZP_SCRATCH_W2),y
|
|
||||||
bne _false
|
|
||||||
lda #1
|
|
||||||
rts
|
|
||||||
_false lda #0
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
equal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack identical?
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
lda P8ESTACK_LO-3,x
|
|
||||||
cmp P8ESTACK_LO,x
|
|
||||||
bne _equals_false
|
|
||||||
lda P8ESTACK_LO-2,x
|
|
||||||
cmp P8ESTACK_LO+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda P8ESTACK_LO-1,x
|
|
||||||
cmp P8ESTACK_LO+2,x
|
|
||||||
bne _equals_false
|
|
||||||
lda P8ESTACK_HI-2,x
|
|
||||||
cmp P8ESTACK_HI+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda P8ESTACK_HI-1,x
|
|
||||||
cmp P8ESTACK_HI+2,x
|
|
||||||
bne _equals_false
|
|
||||||
_equals_true lda #1
|
|
||||||
_equals_store inx
|
|
||||||
sta P8ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
_equals_false lda #0
|
|
||||||
beq _equals_store
|
|
||||||
.pend
|
|
||||||
|
|
||||||
notequal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack different?
|
|
||||||
jsr equal_f
|
|
||||||
eor #1 ; invert the result
|
|
||||||
sta P8ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
vars_less_f .proc
|
|
||||||
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
|
|
||||||
jsr MOVFM
|
|
||||||
lda P8ZP_SCRATCH_W2
|
|
||||||
ldy P8ZP_SCRATCH_W2+1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
cmp #255
|
|
||||||
bne +
|
|
||||||
lda #1
|
|
||||||
rts
|
|
||||||
+ lda #0
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
vars_lesseq_f .proc
|
|
||||||
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
|
|
||||||
jsr MOVFM
|
|
||||||
lda P8ZP_SCRATCH_W2
|
|
||||||
ldy P8ZP_SCRATCH_W2+1
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
cmp #255
|
|
||||||
bne +
|
|
||||||
- lda #1
|
|
||||||
rts
|
|
||||||
+ cmp #0
|
|
||||||
beq -
|
|
||||||
lda #0
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
less_f .proc
|
|
||||||
; -- is f1 < f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
lesseq_f .proc
|
|
||||||
; -- is f1 <= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greater_f .proc
|
|
||||||
; -- is f1 > f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greatereq_f .proc
|
|
||||||
; -- is f1 >= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
compare_floats .proc
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVFM ; fac1 = flt1
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
stx P8ZP_SCRATCH_REG
|
|
||||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
|
||||||
ldx P8ZP_SCRATCH_REG
|
|
||||||
rts
|
|
||||||
_return_false lda #0
|
|
||||||
_return_result sta P8ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
_return_true lda #1
|
|
||||||
bne _return_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
set_array_float_from_fac1 .proc
|
|
||||||
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
|
|
||||||
sta P8ZP_SCRATCH_B1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc P8ZP_SCRATCH_B1
|
|
||||||
ldy P8ZP_SCRATCH_W1+1
|
|
||||||
clc
|
|
||||||
adc P8ZP_SCRATCH_W1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ stx floats_store_reg
|
|
||||||
tax
|
|
||||||
jsr MOVMF
|
|
||||||
ldx floats_store_reg
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
set_0_array_float .proc
|
|
||||||
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
|
|
||||||
sta P8ZP_SCRATCH_B1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc P8ZP_SCRATCH_B1
|
|
||||||
tay
|
|
||||||
lda #0
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
iny
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
iny
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
iny
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
iny
|
|
||||||
sta (P8ZP_SCRATCH_W1),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
set_array_float .proc
|
|
||||||
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
|
|
||||||
sta P8ZP_SCRATCH_B1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc P8ZP_SCRATCH_B1
|
|
||||||
adc P8ZP_SCRATCH_W2
|
|
||||||
ldy P8ZP_SCRATCH_W2+1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jmp copy_float
|
|
||||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
|
||||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
|||||||
; Prog8 definitions for floating point handling on the Commodore 128
|
|
||||||
|
|
||||||
%option enable_floats
|
|
||||||
%import floats_functions
|
|
||||||
|
|
||||||
floats {
|
|
||||||
; ---- this block contains C-128 compatible floating point related functions ----
|
|
||||||
|
|
||||||
const float PI = 3.141592653589793
|
|
||||||
const float TWOPI = 6.283185307179586
|
|
||||||
|
|
||||||
|
|
||||||
; ---- ROM float functions ----
|
|
||||||
|
|
||||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
|
||||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
|
||||||
|
|
||||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
|
||||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
|
||||||
|
|
||||||
romsub $af00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 102-103 ($66-$67) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
|
||||||
|
|
||||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
|
||||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
|
||||||
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
|
||||||
romsub $af03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
|
||||||
|
|
||||||
romsub $af06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
|
||||||
; romsub $af09 = 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)
|
|
||||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
|
||||||
romsub $af0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
|
||||||
romsub $af0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
|
|
||||||
|
|
||||||
romsub $af12 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
|
||||||
romsub $af15 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands NOTE: use FSUBT2() instead!
|
|
||||||
romsub $af18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
|
||||||
romsub $af1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2 NOTE: use FADDT2() instead!
|
|
||||||
romsub $af1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
|
||||||
romsub $af21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2 NOTE: use FMULTT2() instead!
|
|
||||||
romsub $af24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
|
||||||
romsub $af27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands NOTE: use FDIVT2() instead!
|
|
||||||
romsub $af2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
|
||||||
romsub $af2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
|
||||||
romsub $af30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
|
||||||
romsub $af33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
|
||||||
romsub $af36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
|
|
||||||
romsub $af39 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1 NOTE: use FPWRT2() instead!
|
|
||||||
romsub $af3c = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
|
||||||
romsub $af3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
|
||||||
romsub $af42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
|
||||||
romsub $af45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
|
||||||
romsub $af48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
|
||||||
romsub $af4b = ROUND() clobbers(A,X,Y) ; round 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 $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
|
|
||||||
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 $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 $af63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
|
||||||
romsub $af66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
|
||||||
romsub $af69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
|
||||||
romsub $af6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
|
||||||
|
|
||||||
; X16 additions
|
|
||||||
romsub $af6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
|
||||||
romsub $af72 = FADDT2() clobbers(A,X,Y) ; fac1 += fac2
|
|
||||||
romsub $af75 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
|
||||||
romsub $af78 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
|
||||||
romsub $af7b = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
|
||||||
romsub $af7e = FMULTT2() clobbers(A,X,Y) ; fac1 *= fac2
|
|
||||||
romsub $af81 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
|
||||||
romsub $af84 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
|
||||||
romsub $af87 = FDIVT2() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
|
||||||
romsub $af8a = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
|
||||||
romsub $af8d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
|
||||||
romsub $af90 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
|
|
||||||
romsub $af93 = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
|
||||||
romsub $af9C = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
|
|
||||||
romsub $af9f = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
|
||||||
romsub $afa5 = FPWRT2() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
|
||||||
|
|
||||||
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
|
||||||
; ---- 8 bit signed A -> float in fac1
|
|
||||||
%asm {{
|
|
||||||
tay
|
|
||||||
bpl +
|
|
||||||
lda #$ff
|
|
||||||
jmp GIVAYF
|
|
||||||
+ lda #0
|
|
||||||
jmp GIVAYF
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|
||||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
|
||||||
%asm {{
|
|
||||||
phx
|
|
||||||
sty $64 ; facmo
|
|
||||||
sta $65 ; facmo+1
|
|
||||||
ldx #$90
|
|
||||||
sec
|
|
||||||
jsr FLOATC
|
|
||||||
plx
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|
||||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
|
||||||
%asm {{
|
|
||||||
sta P8ZP_SCRATCH_B1
|
|
||||||
tya
|
|
||||||
ldy P8ZP_SCRATCH_B1
|
|
||||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
|
||||||
; ---- fac1 to signed word in A/Y
|
|
||||||
%asm {{
|
|
||||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
|
||||||
sta P8ZP_SCRATCH_B1
|
|
||||||
tya
|
|
||||||
ldy P8ZP_SCRATCH_B1
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
|
||||||
; ---- fac1 to unsigned word in A/Y
|
|
||||||
%asm {{
|
|
||||||
jsr GETADR ; this uses the inverse order, Y/A
|
|
||||||
sta P8ZP_SCRATCH_B1
|
|
||||||
tya
|
|
||||||
ldy P8ZP_SCRATCH_B1
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub FREADUY (ubyte value @Y) {
|
|
||||||
; -- 8 bit unsigned Y -> float in fac1
|
|
||||||
%asm {{
|
|
||||||
tya
|
|
||||||
jmp FLOAT
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
&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:c64/floats_funcs.asm"
|
|
||||||
|
|
||||||
}
|
|
@ -8,10 +8,9 @@ c64 {
|
|||||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||||
&ubyte STATUS = $90 ; kernal status variable for I/O
|
&ubyte STATUS = $90 ; kernal status variable for I/O
|
||||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||||
;;&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ) // TODO c128 ??
|
&ubyte SHFLAG = $d3 ; various modifier key status (updated by IRQ)
|
||||||
|
&ubyte SFDX = $d4 ; current key pressed (matrix value) (updated by IRQ)
|
||||||
&ubyte COLOR = $00f1 ; cursor color
|
&ubyte COLOR = $f1 ; cursor color
|
||||||
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
|
|
||||||
|
|
||||||
&uword IERROR = $0300
|
&uword IERROR = $0300
|
||||||
&uword IMAIN = $0302
|
&uword IMAIN = $0302
|
||||||
@ -311,7 +310,7 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
|||||||
sta _modified+1
|
sta _modified+1
|
||||||
sty _modified+2
|
sty _modified+2
|
||||||
lda #0
|
lda #0
|
||||||
adc #0
|
rol a
|
||||||
sta _use_kernal
|
sta _use_kernal
|
||||||
sei
|
sei
|
||||||
lda #<_irq_handler
|
lda #<_irq_handler
|
||||||
@ -340,8 +339,7 @@ _modified jsr $ffff ; modified
|
|||||||
_use_kernal .byte 0
|
_use_kernal .byte 0
|
||||||
|
|
||||||
_irq_handler_init
|
_irq_handler_init
|
||||||
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||||
stx IRQ_X_REG
|
|
||||||
lda P8ZP_SCRATCH_B1
|
lda P8ZP_SCRATCH_B1
|
||||||
sta IRQ_SCRATCH_ZPB1
|
sta IRQ_SCRATCH_ZPB1
|
||||||
lda P8ZP_SCRATCH_REG
|
lda P8ZP_SCRATCH_REG
|
||||||
@ -354,18 +352,15 @@ _irq_handler_init
|
|||||||
sta IRQ_SCRATCH_ZPWORD2
|
sta IRQ_SCRATCH_ZPWORD2
|
||||||
lda P8ZP_SCRATCH_W2+1
|
lda P8ZP_SCRATCH_W2+1
|
||||||
sta IRQ_SCRATCH_ZPWORD2+1
|
sta IRQ_SCRATCH_ZPWORD2+1
|
||||||
; stack protector; make sure we don't clobber the top of the evaluation stack
|
; Set X to the bottom 32 bytes of the evaluation stack, to HOPEFULLY not clobber it.
|
||||||
dex
|
; This leaves 128-32=96 stack entries for the main program, and 32 stack entries for the IRQ handler.
|
||||||
dex
|
; We assume IRQ handlers don't contain complex expressions taking up more than that.
|
||||||
dex
|
ldx #32
|
||||||
dex
|
|
||||||
dex
|
|
||||||
dex
|
|
||||||
cld
|
cld
|
||||||
rts
|
rts
|
||||||
|
|
||||||
_irq_handler_end
|
_irq_handler_end
|
||||||
; restore all zp scratch registers and the X register
|
; restore all zp scratch registers
|
||||||
lda IRQ_SCRATCH_ZPB1
|
lda IRQ_SCRATCH_ZPB1
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
lda IRQ_SCRATCH_ZPREG
|
lda IRQ_SCRATCH_ZPREG
|
||||||
@ -378,10 +373,8 @@ _irq_handler_end
|
|||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
lda IRQ_SCRATCH_ZPWORD2+1
|
lda IRQ_SCRATCH_ZPWORD2+1
|
||||||
sta P8ZP_SCRATCH_W2+1
|
sta P8ZP_SCRATCH_W2+1
|
||||||
ldx IRQ_X_REG
|
|
||||||
rts
|
rts
|
||||||
|
|
||||||
IRQ_X_REG .byte 0
|
|
||||||
IRQ_SCRATCH_ZPB1 .byte 0
|
IRQ_SCRATCH_ZPB1 .byte 0
|
||||||
IRQ_SCRATCH_ZPREG .byte 0
|
IRQ_SCRATCH_ZPREG .byte 0
|
||||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||||
@ -411,7 +404,7 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @P
|
|||||||
sta _modified+1
|
sta _modified+1
|
||||||
sty _modified+2
|
sty _modified+2
|
||||||
lda #0
|
lda #0
|
||||||
adc #0
|
rol a
|
||||||
sta set_irq._use_kernal
|
sta set_irq._use_kernal
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
ldy cx16.r0+1
|
ldy cx16.r0+1
|
||||||
@ -486,10 +479,8 @@ asmsub init_system() {
|
|||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
cld
|
cld
|
||||||
;;lda #%00101111 ; TODO c128 ram and rom bank selection how?
|
lda #0
|
||||||
;;sta $00
|
sta $ff00 ; select default bank 15
|
||||||
;;lda #%00100111
|
|
||||||
;;sta $01
|
|
||||||
jsr c64.IOINIT
|
jsr c64.IOINIT
|
||||||
jsr c64.RESTOR
|
jsr c64.RESTOR
|
||||||
jsr c64.CINT
|
jsr c64.CINT
|
||||||
@ -555,21 +546,38 @@ sys {
|
|||||||
; Soft-reset the system back to initial power-on Basic prompt.
|
; Soft-reset the system back to initial power-on Basic prompt.
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
;lda #14
|
lda #0
|
||||||
;sta $01 ; bank the kernal in TODO c128 how to do this?
|
sta $ff00 ; default bank 15
|
||||||
jmp (c64.RESET_VEC)
|
jmp (c64.RESET_VEC)
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -720,8 +728,8 @@ _longcopy
|
|||||||
inline asmsub exit(ubyte returnvalue @A) {
|
inline asmsub exit(ubyte returnvalue @A) {
|
||||||
; -- 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 {{
|
%asm {{
|
||||||
;lda #14
|
lda #0
|
||||||
;sta $01 ; bank the kernal in TODO c128 how to do this?
|
sta $ff00 ; default bank 15
|
||||||
jsr c64.CLRCHN ; reset i/o channels
|
jsr c64.CLRCHN ; reset i/o channels
|
||||||
jsr c64.enable_runstop_and_charsetswitch
|
jsr c64.enable_runstop_and_charsetswitch
|
||||||
ldx prog8_lib.orig_stackpointer
|
ldx prog8_lib.orig_stackpointer
|
||||||
|
@ -6,7 +6,7 @@ FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
|||||||
|
|
||||||
|
|
||||||
floats_store_reg .byte 0 ; temp storage
|
floats_store_reg .byte 0 ; temp storage
|
||||||
|
floats_temp_var .byte 0,0,0,0,0 ; temporary storage for a float
|
||||||
|
|
||||||
ub2float .proc
|
ub2float .proc
|
||||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
@ -28,10 +28,6 @@ romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
|
|||||||
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||||
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||||
|
|
||||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
|
||||||
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
|
||||||
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
|
||||||
|
|
||||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
@ -64,8 +60,8 @@ romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1
|
|||||||
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||||
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||||
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||||
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||||
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||||
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||||
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||||
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||||
@ -147,17 +143,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
|
||||||
; ---- fac1 to signed word in A/Y
|
|
||||||
%asm {{
|
|
||||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
|
||||||
sta P8ZP_SCRATCH_REG
|
|
||||||
tya
|
|
||||||
ldy P8ZP_SCRATCH_REG
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||||
; ---- fac1 to unsigned word in A/Y
|
; ---- fac1 to unsigned word in A/Y
|
||||||
%asm {{
|
%asm {{
|
||||||
|
@ -8,6 +8,7 @@ c64 {
|
|||||||
&ubyte STATUS = $90 ; kernal status variable for I/O
|
&ubyte STATUS = $90 ; kernal status variable for I/O
|
||||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||||
|
&ubyte SHFLAG = $028d ; various modifier key status (updated by IRQ)
|
||||||
|
|
||||||
&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)
|
||||||
@ -355,7 +356,7 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
|||||||
sta _modified+1
|
sta _modified+1
|
||||||
sty _modified+2
|
sty _modified+2
|
||||||
lda #0
|
lda #0
|
||||||
adc #0
|
rol a
|
||||||
sta _use_kernal
|
sta _use_kernal
|
||||||
sei
|
sei
|
||||||
lda #<_irq_handler
|
lda #<_irq_handler
|
||||||
@ -384,8 +385,7 @@ _modified jsr $ffff ; modified
|
|||||||
_use_kernal .byte 0
|
_use_kernal .byte 0
|
||||||
|
|
||||||
_irq_handler_init
|
_irq_handler_init
|
||||||
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||||
stx IRQ_X_REG
|
|
||||||
lda P8ZP_SCRATCH_B1
|
lda P8ZP_SCRATCH_B1
|
||||||
sta IRQ_SCRATCH_ZPB1
|
sta IRQ_SCRATCH_ZPB1
|
||||||
lda P8ZP_SCRATCH_REG
|
lda P8ZP_SCRATCH_REG
|
||||||
@ -398,18 +398,15 @@ _irq_handler_init
|
|||||||
sta IRQ_SCRATCH_ZPWORD2
|
sta IRQ_SCRATCH_ZPWORD2
|
||||||
lda P8ZP_SCRATCH_W2+1
|
lda P8ZP_SCRATCH_W2+1
|
||||||
sta IRQ_SCRATCH_ZPWORD2+1
|
sta IRQ_SCRATCH_ZPWORD2+1
|
||||||
; stack protector; make sure we don't clobber the top of the evaluation stack
|
; Set X to the bottom 32 bytes of the evaluation stack, to HOPEFULLY not clobber it.
|
||||||
dex
|
; This leaves 128-32=96 stack entries for the main program, and 32 stack entries for the IRQ handler.
|
||||||
dex
|
; We assume IRQ handlers don't contain complex expressions taking up more than that.
|
||||||
dex
|
ldx #32
|
||||||
dex
|
|
||||||
dex
|
|
||||||
dex
|
|
||||||
cld
|
cld
|
||||||
rts
|
rts
|
||||||
|
|
||||||
_irq_handler_end
|
_irq_handler_end
|
||||||
; restore all zp scratch registers and the X register
|
; restore all zp scratch registers
|
||||||
lda IRQ_SCRATCH_ZPB1
|
lda IRQ_SCRATCH_ZPB1
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
lda IRQ_SCRATCH_ZPREG
|
lda IRQ_SCRATCH_ZPREG
|
||||||
@ -422,10 +419,8 @@ _irq_handler_end
|
|||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
lda IRQ_SCRATCH_ZPWORD2+1
|
lda IRQ_SCRATCH_ZPWORD2+1
|
||||||
sta P8ZP_SCRATCH_W2+1
|
sta P8ZP_SCRATCH_W2+1
|
||||||
ldx IRQ_X_REG
|
|
||||||
rts
|
rts
|
||||||
|
|
||||||
IRQ_X_REG .byte 0
|
|
||||||
IRQ_SCRATCH_ZPB1 .byte 0
|
IRQ_SCRATCH_ZPB1 .byte 0
|
||||||
IRQ_SCRATCH_ZPREG .byte 0
|
IRQ_SCRATCH_ZPREG .byte 0
|
||||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||||
@ -455,7 +450,7 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @P
|
|||||||
sta _modified+1
|
sta _modified+1
|
||||||
sty _modified+2
|
sty _modified+2
|
||||||
lda #0
|
lda #0
|
||||||
adc #0
|
rol a
|
||||||
sta set_irq._use_kernal
|
sta set_irq._use_kernal
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
ldy cx16.r0+1
|
ldy cx16.r0+1
|
||||||
@ -527,15 +522,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) {
|
||||||
|
@ -521,7 +521,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
|||||||
|
|
||||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||||
;By Omegamatrix Further optimizations by tepples
|
;By Omegamatrix Further optimizations by tepples
|
||||||
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
|
; routine from https://forums.nesdev.org/viewtopic.php?p=130363&sid=1944ba8bac4d6afa9c02e3cc42304e6b#p130363
|
||||||
|
|
||||||
;HexToDec99
|
;HexToDec99
|
||||||
; start in A
|
; start in A
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
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.
|
||||||
@ -14,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.
|
||||||
@ -24,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):
|
||||||
@ -87,8 +89,9 @@ 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)
|
||||||
@ -147,7 +150,7 @@ m_in_buffer sta $ffff
|
|||||||
}
|
}
|
||||||
|
|
||||||
; 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
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
floats {
|
floats {
|
||||||
; ---- this block contains C-64 compatible floating point related functions ----
|
; ---- this block contains C-64 compatible floating point related functions ----
|
||||||
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
|
|
||||||
|
|
||||||
const float PI = 3.141592653589793
|
const float PI = 3.141592653589793
|
||||||
const float TWOPI = 6.283185307179586
|
const float TWOPI = 6.283185307179586
|
||||||
@ -40,8 +39,8 @@ romsub $fe18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt valu
|
|||||||
romsub $fe1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
romsub $fe1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||||
romsub $fe1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
romsub $fe1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||||
romsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
romsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||||
romsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
romsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||||
romsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
romsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||||
romsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
romsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||||
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||||
romsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
romsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||||
@ -76,7 +75,7 @@ romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
|||||||
romsub $fe7e = 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 $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||||
romsub $fe84 = 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 $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
|
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (s8).A
|
||||||
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
|
||||||
romsub $fe8d = 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 $fe90 = 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
|
||||||
@ -119,17 +118,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
|
||||||
; ---- fac1 to signed word in A/Y
|
|
||||||
%asm {{
|
|
||||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
|
||||||
sta P8ZP_SCRATCH_B1
|
|
||||||
tya
|
|
||||||
ldy P8ZP_SCRATCH_B1
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||||
; ---- fac1 to unsigned word in A/Y
|
; ---- fac1 to unsigned word in A/Y
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -144,8 +132,8 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
|||||||
asmsub FREADUY (ubyte value @Y) {
|
asmsub FREADUY (ubyte value @Y) {
|
||||||
; -- 8 bit unsigned Y -> float in fac1
|
; -- 8 bit unsigned Y -> float in fac1
|
||||||
%asm {{
|
%asm {{
|
||||||
tya
|
lda #0
|
||||||
jmp FLOAT
|
jmp GIVAYF
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ graphics {
|
|||||||
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
|
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
|
||||||
%asm {{
|
%asm {{
|
||||||
jsr cx16.FB_cursor_position
|
jsr cx16.FB_cursor_position
|
||||||
lda #1
|
lda graphics.stroke_color
|
||||||
jsr cx16.FB_set_pixel
|
jsr cx16.FB_set_pixel
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,10 @@ psg {
|
|||||||
; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE.
|
; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE.
|
||||||
; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE.
|
; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE.
|
||||||
envelope_states[voice_num] = 255
|
envelope_states[voice_num] = 255
|
||||||
|
%asm {{
|
||||||
|
php
|
||||||
|
sei
|
||||||
|
}}
|
||||||
cx16.r0 = $f9c2 + voice_num * 4
|
cx16.r0 = $f9c2 + voice_num * 4
|
||||||
cx16.VERA_CTRL = 0
|
cx16.VERA_CTRL = 0
|
||||||
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
||||||
@ -32,6 +36,9 @@ psg {
|
|||||||
cx16.VERA_DATA0 = waveform | pulsewidth
|
cx16.VERA_DATA0 = waveform | pulsewidth
|
||||||
envelope_volumes[voice_num] = mkword(volume, 0)
|
envelope_volumes[voice_num] = mkword(volume, 0)
|
||||||
envelope_maxvolumes[voice_num] = volume
|
envelope_maxvolumes[voice_num] = volume
|
||||||
|
%asm {{
|
||||||
|
plp
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
; sub freq_hz(ubyte voice_num, float hertz) {
|
; sub freq_hz(ubyte voice_num, float hertz) {
|
||||||
@ -44,48 +51,54 @@ psg {
|
|||||||
sub freq(ubyte voice_num, uword vera_freq) {
|
sub freq(ubyte voice_num, uword vera_freq) {
|
||||||
; -- Changes the frequency of the voice's sound.
|
; -- Changes the frequency of the voice's sound.
|
||||||
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
|
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
|
||||||
; (https://github.com/commanderx16/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
|
; (https://github.com/x16community/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
|
||||||
cx16.r0 = $f9c0 + voice_num * 4
|
; Write freq MSB first and then LSB to reduce the chance on clicks
|
||||||
|
%asm {{
|
||||||
|
php
|
||||||
|
sei
|
||||||
|
}}
|
||||||
|
cx16.r0 = $f9c1 + voice_num * 4
|
||||||
cx16.VERA_CTRL = 0
|
cx16.VERA_CTRL = 0
|
||||||
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
||||||
cx16.VERA_ADDR_M = msb(cx16.r0)
|
cx16.VERA_ADDR_M = msb(cx16.r0)
|
||||||
cx16.VERA_ADDR_H = 1
|
cx16.VERA_ADDR_H = 1
|
||||||
cx16.VERA_DATA0 = lsb(vera_freq)
|
|
||||||
cx16.VERA_ADDR_L++
|
|
||||||
cx16.VERA_DATA0 = msb(vera_freq)
|
cx16.VERA_DATA0 = msb(vera_freq)
|
||||||
|
cx16.VERA_ADDR_L--
|
||||||
|
cx16.VERA_DATA0 = lsb(vera_freq)
|
||||||
|
%asm {{
|
||||||
|
plp
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume(ubyte voice_num, ubyte vol) {
|
sub volume(ubyte voice_num, ubyte vol) {
|
||||||
; -- Modifies the volume of this voice.
|
; -- Modifies the volume of this voice.
|
||||||
; voice_num = 0-15, vol = 0-63 where 0=silent, 63=loudest.
|
; voice_num = 0-15, vol = 0-63 where 0=silent, 63=loudest.
|
||||||
cx16.r0 = $f9c2 + voice_num * 4
|
|
||||||
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | vol)
|
|
||||||
envelope_volumes[voice_num] = mkword(vol, 0)
|
envelope_volumes[voice_num] = mkword(vol, 0)
|
||||||
|
cx16.vpoke_mask(1, $f9c2 + voice_num * 4, %11000000, vol)
|
||||||
envelope_maxvolumes[voice_num] = vol
|
envelope_maxvolumes[voice_num] = vol
|
||||||
}
|
}
|
||||||
|
|
||||||
sub pulse_width(ubyte voice_num, ubyte pw) {
|
sub pulse_width(ubyte voice_num, ubyte pw) {
|
||||||
; -- Modifies the pulse width of this voice (when waveform=PULSE)
|
; -- Modifies the pulse width of this voice (when waveform=PULSE)
|
||||||
; voice_num = 0-15, pw = 0-63 where 0=narrow, 63=50%cycle so square wave.
|
; voice_num = 0-15, pw = 0-63 where 0=narrow, 63=50%cycle so square wave.
|
||||||
cx16.r0 = $f9c3 + voice_num * 4
|
cx16.vpoke_mask(1, $f9c3 + voice_num * 4, %11000000, pw)
|
||||||
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | pw)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub envelope(ubyte voice_num, ubyte maxvolume, ubyte attack, ubyte sustain, ubyte release) {
|
sub envelope(ubyte voice_num, ubyte maxvolume, ubyte attack, ubyte sustain, ubyte release) {
|
||||||
; -- Enables AttackSustainRelease volume envelope for a voice.
|
; -- Enables AttackSustainRelease volume envelope for a voice.
|
||||||
; Note: this requires setting up envelopes_irq() as well, read its description.
|
; Note: this requires setting up envelopes_irq() as well, read its description.
|
||||||
; voice_num = 0-15 maxvolume = 0-63
|
; voice_num = 0-15 maxvolume = 0-63
|
||||||
; attack, sustain, release = 0-255 that determine the speed of the A/D/R.
|
; attack, sustain, release = 0-255 that determine the speed of the A/D/R:
|
||||||
; TODO describe how the speeds are calculated. For now, experiment. Higher values means *slower* enveloping.
|
; attack time: MAXVOL/15/attack seconds. higher value = faster attack.
|
||||||
|
; sustain time: sustain/60 seconds higher sustain value = longer sustain (!).
|
||||||
|
; release time: MAXVOL/15/release seconds. higher vaule = faster release.
|
||||||
|
|
||||||
envelope_states[voice_num] = 255
|
envelope_states[voice_num] = 255
|
||||||
envelope_attacks[voice_num] = attack
|
envelope_attacks[voice_num] = attack
|
||||||
envelope_sustains[voice_num] = sustain
|
envelope_sustains[voice_num] = sustain
|
||||||
envelope_releases[voice_num] = release
|
envelope_releases[voice_num] = release
|
||||||
if attack
|
if maxvolume<envelope_volumes[voice_num]
|
||||||
attack = 0
|
envelope_volumes[voice_num] = maxvolume
|
||||||
else
|
|
||||||
attack = maxvolume ; max volume when no attack is set
|
|
||||||
envelope_volumes[voice_num] = mkword(attack, 0)
|
|
||||||
envelope_maxvolumes[voice_num] = maxvolume
|
envelope_maxvolumes[voice_num] = maxvolume
|
||||||
envelope_states[voice_num] = 0
|
envelope_states[voice_num] = 0
|
||||||
}
|
}
|
||||||
@ -94,7 +107,6 @@ psg {
|
|||||||
; -- Shut down all PSG voices.
|
; -- Shut down all PSG voices.
|
||||||
for cx16.r1L in 0 to 15 {
|
for cx16.r1L in 0 to 15 {
|
||||||
envelope_states[cx16.r1L] = 255
|
envelope_states[cx16.r1L] = 255
|
||||||
envelope_volumes[cx16.r1L] = 0
|
|
||||||
volume(cx16.r1L, 0)
|
volume(cx16.r1L, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,11 +116,11 @@ psg {
|
|||||||
; you have to call this routine every 1/60th second, for example from your vsync irq handler,
|
; you have to call this routine every 1/60th second, for example from your vsync irq handler,
|
||||||
; or just install this routine as the only irq handler if you don't have to do other things there.
|
; or just install this routine as the only irq handler if you don't have to do other things there.
|
||||||
; Example: cx16.set_irq(&psg.envelopes_irq, true)
|
; Example: cx16.set_irq(&psg.envelopes_irq, true)
|
||||||
|
; NOTE: this routine calls save/restore_vera_context() for you, don't nest this or call it yourself!
|
||||||
|
|
||||||
; cx16.r0 = the volume word (volume scaled by 256)
|
; cx16.r0 = the volume word (volume scaled by 256)
|
||||||
; cx16.r1L = the voice number
|
; cx16.r1L = the voice number
|
||||||
; cx16.r2L = attack value
|
; cx16.r2L = attack value
|
||||||
|
|
||||||
pushw(cx16.r0)
|
pushw(cx16.r0)
|
||||||
push(cx16.r1L)
|
push(cx16.r1L)
|
||||||
push(cx16.r2L)
|
push(cx16.r2L)
|
||||||
@ -148,7 +160,7 @@ psg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
; set new volumes of all 16 voices, using vera stride of 4
|
; set new volumes of all 16 voices, using vera stride of 4
|
||||||
cx16.push_vera_context()
|
cx16.save_vera_context()
|
||||||
cx16.VERA_CTRL = 0
|
cx16.VERA_CTRL = 0
|
||||||
cx16.VERA_ADDR_L = $c2
|
cx16.VERA_ADDR_L = $c2
|
||||||
cx16.VERA_ADDR_M = $f9
|
cx16.VERA_ADDR_M = $f9
|
||||||
@ -160,7 +172,7 @@ psg {
|
|||||||
for cx16.r1L in 0 to 15 {
|
for cx16.r1L in 0 to 15 {
|
||||||
cx16.VERA_DATA0 = cx16.VERA_DATA1 & %11000000 | msb(envelope_volumes[cx16.r1L])
|
cx16.VERA_DATA0 = cx16.VERA_DATA1 & %11000000 | msb(envelope_volumes[cx16.r1L])
|
||||||
}
|
}
|
||||||
cx16.pop_vera_context()
|
cx16.restore_vera_context()
|
||||||
popw(cx16.r9)
|
popw(cx16.r9)
|
||||||
pop(cx16.r2L)
|
pop(cx16.r2L)
|
||||||
pop(cx16.r1L)
|
pop(cx16.r1L)
|
||||||
|
@ -233,14 +233,18 @@ cx16 {
|
|||||||
&ubyte VERA_IEN = VERA_BASE + $0006
|
&ubyte VERA_IEN = VERA_BASE + $0006
|
||||||
&ubyte VERA_ISR = VERA_BASE + $0007
|
&ubyte VERA_ISR = VERA_BASE + $0007
|
||||||
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
|
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
|
||||||
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
|
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009 ; DCSEL= 0
|
||||||
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
|
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A ; DCSEL= 0
|
||||||
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
|
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B ; DCSEL= 0
|
||||||
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
|
&ubyte VERA_DC_BORDER = VERA_BASE + $000C ; DCSEL= 0
|
||||||
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
|
&ubyte VERA_DC_HSTART = VERA_BASE + $0009 ; DCSEL= 1
|
||||||
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
|
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A ; DCSEL= 1
|
||||||
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
|
&ubyte VERA_DC_VSTART = VERA_BASE + $000B ; DCSEL= 1
|
||||||
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
|
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C ; DCSEL= 1
|
||||||
|
&ubyte VERA_DC_VER0 = VERA_BASE + $0009 ; DCSEL=63
|
||||||
|
&ubyte VERA_DC_VER1 = VERA_BASE + $000A ; DCSEL=63
|
||||||
|
&ubyte VERA_DC_VER2 = VERA_BASE + $000B ; DCSEL=63
|
||||||
|
&ubyte VERA_DC_VER3 = VERA_BASE + $000C ; DCSEL=63
|
||||||
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
|
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
|
||||||
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
|
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
|
||||||
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
|
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
|
||||||
@ -266,41 +270,41 @@ cx16 {
|
|||||||
|
|
||||||
; I/O
|
; I/O
|
||||||
|
|
||||||
const uword via1 = $9f00 ;VIA 6522 #1
|
const uword VIA1_BASE = $9f00 ;VIA 6522 #1
|
||||||
&ubyte d1prb = via1+0
|
&ubyte via1prb = VIA1_BASE + 0
|
||||||
&ubyte d1pra = via1+1
|
&ubyte via1pra = VIA1_BASE + 1
|
||||||
&ubyte d1ddrb = via1+2
|
&ubyte via1ddrb = VIA1_BASE + 2
|
||||||
&ubyte d1ddra = via1+3
|
&ubyte via1ddra = VIA1_BASE + 3
|
||||||
&ubyte d1t1l = via1+4
|
&ubyte via1t1l = VIA1_BASE + 4
|
||||||
&ubyte d1t1h = via1+5
|
&ubyte via1t1h = VIA1_BASE + 5
|
||||||
&ubyte d1t1ll = via1+6
|
&ubyte via1t1ll = VIA1_BASE + 6
|
||||||
&ubyte d1t1lh = via1+7
|
&ubyte via1t1lh = VIA1_BASE + 7
|
||||||
&ubyte d1t2l = via1+8
|
&ubyte via1t2l = VIA1_BASE + 8
|
||||||
&ubyte d1t2h = via1+9
|
&ubyte via1t2h = VIA1_BASE + 9
|
||||||
&ubyte d1sr = via1+10
|
&ubyte via1sr = VIA1_BASE + 10
|
||||||
&ubyte d1acr = via1+11
|
&ubyte via1acr = VIA1_BASE + 11
|
||||||
&ubyte d1pcr = via1+12
|
&ubyte via1pcr = VIA1_BASE + 12
|
||||||
&ubyte d1ifr = via1+13
|
&ubyte via1ifr = VIA1_BASE + 13
|
||||||
&ubyte d1ier = via1+14
|
&ubyte via1ier = VIA1_BASE + 14
|
||||||
&ubyte d1ora = via1+15
|
&ubyte via1ora = VIA1_BASE + 15
|
||||||
|
|
||||||
const uword via2 = $9f10 ;VIA 6522 #2
|
const uword VIA2_BASE = $9f10 ;VIA 6522 #2
|
||||||
&ubyte d2prb = via2+0
|
&ubyte via2prb = VIA2_BASE + 0
|
||||||
&ubyte d2pra = via2+1
|
&ubyte via2pra = VIA2_BASE + 1
|
||||||
&ubyte d2ddrb = via2+2
|
&ubyte via2ddrb = VIA2_BASE + 2
|
||||||
&ubyte d2ddra = via2+3
|
&ubyte via2ddra = VIA2_BASE + 3
|
||||||
&ubyte d2t1l = via2+4
|
&ubyte via2t1l = VIA2_BASE + 4
|
||||||
&ubyte d2t1h = via2+5
|
&ubyte via2t1h = VIA2_BASE + 5
|
||||||
&ubyte d2t1ll = via2+6
|
&ubyte via2t1ll = VIA2_BASE + 6
|
||||||
&ubyte d2t1lh = via2+7
|
&ubyte via2t1lh = VIA2_BASE + 7
|
||||||
&ubyte d2t2l = via2+8
|
&ubyte via2t2l = VIA2_BASE + 8
|
||||||
&ubyte d2t2h = via2+9
|
&ubyte via2t2h = VIA2_BASE + 9
|
||||||
&ubyte d2sr = via2+10
|
&ubyte via2sr = VIA2_BASE + 10
|
||||||
&ubyte d2acr = via2+11
|
&ubyte via2acr = VIA2_BASE + 11
|
||||||
&ubyte d2pcr = via2+12
|
&ubyte via2pcr = VIA2_BASE + 12
|
||||||
&ubyte d2ifr = via2+13
|
&ubyte via2ifr = VIA2_BASE + 13
|
||||||
&ubyte d2ier = via2+14
|
&ubyte via2ier = VIA2_BASE + 14
|
||||||
&ubyte d2ora = via2+15
|
&ubyte via2ora = VIA2_BASE + 15
|
||||||
|
|
||||||
; YM-2151 sound chip
|
; YM-2151 sound chip
|
||||||
&ubyte YM_ADDRESS = $9f40
|
&ubyte YM_ADDRESS = $9f40
|
||||||
@ -345,7 +349,7 @@ romsub $ff41 = GRAPH_put_next_char(ubyte char @A) clobbers(A,X,Y) ; alias f
|
|||||||
; framebuffer
|
; framebuffer
|
||||||
romsub $fef6 = FB_init() clobbers(A,X,Y)
|
romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||||
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
||||||
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y)
|
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte colorcount @X) clobbers(A,X,Y)
|
||||||
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
||||||
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
|
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
|
||||||
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
||||||
@ -394,6 +398,14 @@ romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
|||||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
||||||
|
|
||||||
|
; Audio (bank 10)
|
||||||
|
romsub $C04B = psg_init() clobbers(A,X,Y)
|
||||||
|
romsub $C063 = ym_init() clobbers(A,X,Y) -> ubyte @Pc ; (re)init YM chip
|
||||||
|
romsub $C066 = ym_loaddefpatches() clobbers(A,X,Y) -> ubyte @Pc ; load default YM patches
|
||||||
|
romsub $C09F = audio_init() clobbers(A,X,Y) -> ubyte @Pc ; (re)initialize PSG and YM audio chips
|
||||||
|
; TODO: add more of the audio routines?
|
||||||
|
|
||||||
|
|
||||||
asmsub kbdbuf_clear() {
|
asmsub kbdbuf_clear() {
|
||||||
; -- convenience helper routine to clear the keyboard buffer
|
; -- convenience helper routine to clear the keyboard buffer
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -454,19 +466,28 @@ inline asmsub getrombank() -> ubyte @A {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub getrambank() -> ubyte @A {
|
inline asmsub getrambank() -> ubyte @A {
|
||||||
; -- get the current ram bank
|
; -- get the current RAM bank
|
||||||
%asm {{
|
%asm {{
|
||||||
lda $00
|
lda $00
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
; -- Returns the number of available RAM banks according to the kernal (each bank is 8 Kb).
|
||||||
|
; Note that the number of such banks can be 256 so a word is returned.
|
||||||
|
; But just looking at the A register (the LSB of the result word) could suffice if you know that A=0 means 256 banks:
|
||||||
|
; The maximum number of RAM banks in the X16 is currently 256 (2 Megabytes of banked RAM).
|
||||||
|
; Kernal's MEMTOP routine reports 0 in this case but that doesn't mean 'zero banks', instead it means 256 banks,
|
||||||
|
; as there is no X16 without at least 1 page of banked RAM. So this routine returns 256 instead of 0.
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
phx
|
||||||
sec
|
sec
|
||||||
jsr c64.MEMTOP
|
jsr c64.MEMTOP
|
||||||
plx
|
ldy #0
|
||||||
|
cmp #0
|
||||||
|
bne +
|
||||||
|
iny
|
||||||
|
+ plx
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -583,39 +604,24 @@ asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A)
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub vpoke_mask(ubyte bank @A, uword address @R0, ubyte mask @X, ubyte value @Y) clobbers (A) {
|
||||||
sub FB_set_pixels_from_buf(uword buffer, uword count) {
|
; -- bitwise or a single byte to the value already in the VERA's video memory at that location
|
||||||
|
; after applying the and-mask. Note: inefficient when writing multiple sequential bytes!
|
||||||
%asm {{
|
%asm {{
|
||||||
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
|
sty P8ZP_SCRATCH_B1
|
||||||
; However that routine contains a bug in the current v38 ROM that makes it crash when count > 255.
|
stz cx16.VERA_CTRL
|
||||||
; So the code below replaces that. Once the ROM is patched this routine is no longer necessary.
|
and #1
|
||||||
; See https://github.com/commanderx16/x16-rom/issues/179
|
sta cx16.VERA_ADDR_H
|
||||||
phx
|
lda cx16.r0
|
||||||
lda buffer
|
sta cx16.VERA_ADDR_L
|
||||||
ldy buffer+1
|
lda cx16.r0+1
|
||||||
sta P8ZP_SCRATCH_W1
|
sta cx16.VERA_ADDR_M
|
||||||
sty P8ZP_SCRATCH_W1+1
|
txa
|
||||||
jsr _pixels
|
and cx16.VERA_DATA0
|
||||||
plx
|
ora P8ZP_SCRATCH_B1
|
||||||
rts
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
_pixels lda count+1
|
}}
|
||||||
beq +
|
|
||||||
ldx #0
|
|
||||||
- jsr _loop
|
|
||||||
inc P8ZP_SCRATCH_W1+1
|
|
||||||
dec count+1
|
|
||||||
bne -
|
|
||||||
|
|
||||||
+ ldx count
|
|
||||||
_loop ldy #0
|
|
||||||
- lda (P8ZP_SCRATCH_W1),y
|
|
||||||
sta cx16.VERA_DATA0
|
|
||||||
iny
|
|
||||||
dex
|
|
||||||
bne -
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
; ---- system stuff -----
|
; ---- system stuff -----
|
||||||
@ -624,27 +630,30 @@ asmsub init_system() {
|
|||||||
; Called automatically by the loader program logic.
|
; Called automatically by the loader program logic.
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
|
lda #0
|
||||||
|
tax
|
||||||
|
tay
|
||||||
|
jsr cx16.mouse_config ; disable mouse
|
||||||
cld
|
cld
|
||||||
lda VERA_DC_VIDEO
|
lda VERA_DC_VIDEO
|
||||||
and #%00000111 ; retain chroma + output mode
|
and #%00000111 ; retain chroma + output mode
|
||||||
sta P8ZP_SCRATCH_REG
|
sta P8ZP_SCRATCH_REG
|
||||||
lda #$80
|
lda #$0a
|
||||||
sta VERA_CTRL ; reset vera
|
sta $01 ; rom bank 10 (audio)
|
||||||
stz $01 ; select rom bank 0 (enable kernal)
|
jsr audio_init ; silence
|
||||||
|
stz $01 ; rom bank 0 (kernal)
|
||||||
jsr c64.IOINIT
|
jsr c64.IOINIT
|
||||||
jsr c64.RESTOR
|
jsr c64.RESTOR
|
||||||
jsr c64.CINT
|
jsr c64.CINT
|
||||||
lda VERA_DC_VIDEO
|
lda VERA_DC_VIDEO
|
||||||
and #%11111000
|
and #%11111000
|
||||||
ora P8ZP_SCRATCH_REG
|
ora P8ZP_SCRATCH_REG
|
||||||
sta VERA_DC_VIDEO ; keep old output mode
|
sta VERA_DC_VIDEO ; restore old output mode
|
||||||
ldy #0
|
|
||||||
clc
|
|
||||||
jsr c64.PLOT ; force a call to PLOT to avoid autostart black square issue, also see textio.fix_autostart_square()
|
|
||||||
lda #$90 ; black
|
lda #$90 ; black
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
lda #1 ; swap fg/bg
|
lda #1
|
||||||
jsr c64.CHROUT
|
sta $00 ; select ram bank 1
|
||||||
|
jsr c64.CHROUT ; swap fg/bg
|
||||||
lda #$9e ; yellow
|
lda #$9e ; yellow
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
lda #147 ; clear screen
|
lda #147 ; clear screen
|
||||||
@ -677,7 +686,7 @@ asmsub cleanup_at_exit() {
|
|||||||
lda #1
|
lda #1
|
||||||
sta $00 ; ram bank 1
|
sta $00 ; ram bank 1
|
||||||
lda #4
|
lda #4
|
||||||
sta $01 ; rom bank 4 (kernal)
|
sta $01 ; rom bank 4 (basic)
|
||||||
stz $2d ; hack to reset machine code monitor bank to 0
|
stz $2d ; hack to reset machine code monitor bank to 0
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
@ -688,7 +697,7 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
|||||||
sta _modified+1
|
sta _modified+1
|
||||||
sty _modified+2
|
sty _modified+2
|
||||||
lda #0
|
lda #0
|
||||||
adc #0
|
rol a
|
||||||
sta _use_kernal
|
sta _use_kernal
|
||||||
sei
|
sei
|
||||||
lda #<_irq_handler
|
lda #<_irq_handler
|
||||||
@ -707,8 +716,7 @@ _modified jsr $ffff ; modified
|
|||||||
lda _use_kernal
|
lda _use_kernal
|
||||||
bne +
|
bne +
|
||||||
; end irq processing - don't use kernal's irq handling
|
; end irq processing - don't use kernal's irq handling
|
||||||
lda cx16.VERA_ISR
|
lda #1
|
||||||
ora #1
|
|
||||||
sta cx16.VERA_ISR ; clear Vera Vsync irq status
|
sta cx16.VERA_ISR ; clear Vera Vsync irq status
|
||||||
ply
|
ply
|
||||||
plx
|
plx
|
||||||
@ -719,8 +727,7 @@ _modified jsr $ffff ; modified
|
|||||||
_use_kernal .byte 0
|
_use_kernal .byte 0
|
||||||
|
|
||||||
_irq_handler_init
|
_irq_handler_init
|
||||||
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||||
stx IRQ_X_REG
|
|
||||||
lda P8ZP_SCRATCH_B1
|
lda P8ZP_SCRATCH_B1
|
||||||
sta IRQ_SCRATCH_ZPB1
|
sta IRQ_SCRATCH_ZPB1
|
||||||
lda P8ZP_SCRATCH_REG
|
lda P8ZP_SCRATCH_REG
|
||||||
@ -733,18 +740,15 @@ _irq_handler_init
|
|||||||
sta IRQ_SCRATCH_ZPWORD2
|
sta IRQ_SCRATCH_ZPWORD2
|
||||||
lda P8ZP_SCRATCH_W2+1
|
lda P8ZP_SCRATCH_W2+1
|
||||||
sta IRQ_SCRATCH_ZPWORD2+1
|
sta IRQ_SCRATCH_ZPWORD2+1
|
||||||
; stack protector; make sure we don't clobber the top of the evaluation stack
|
; Set X to the bottom 32 bytes of the evaluation stack, to HOPEFULLY not clobber it.
|
||||||
dex
|
; This leaves 128-32=96 stack entries for the main program, and 32 stack entries for the IRQ handler.
|
||||||
dex
|
; We assume IRQ handlers don't contain complex expressions taking up more than that.
|
||||||
dex
|
ldx #32
|
||||||
dex
|
|
||||||
dex
|
|
||||||
dex
|
|
||||||
cld
|
cld
|
||||||
rts
|
rts
|
||||||
|
|
||||||
_irq_handler_end
|
_irq_handler_end
|
||||||
; restore all zp scratch registers and the X register
|
; restore all zp scratch registers
|
||||||
lda IRQ_SCRATCH_ZPB1
|
lda IRQ_SCRATCH_ZPB1
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
lda IRQ_SCRATCH_ZPREG
|
lda IRQ_SCRATCH_ZPREG
|
||||||
@ -757,10 +761,8 @@ _irq_handler_end
|
|||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
lda IRQ_SCRATCH_ZPWORD2+1
|
lda IRQ_SCRATCH_ZPWORD2+1
|
||||||
sta P8ZP_SCRATCH_W2+1
|
sta P8ZP_SCRATCH_W2+1
|
||||||
ldx IRQ_X_REG
|
|
||||||
rts
|
rts
|
||||||
|
|
||||||
IRQ_X_REG .byte 0
|
|
||||||
IRQ_SCRATCH_ZPB1 .byte 0
|
IRQ_SCRATCH_ZPB1 .byte 0
|
||||||
IRQ_SCRATCH_ZPREG .byte 0
|
IRQ_SCRATCH_ZPREG .byte 0
|
||||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||||
@ -769,7 +771,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub push_vera_context() clobbers(A) {
|
asmsub save_vera_context() clobbers(A) {
|
||||||
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
|
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
|
||||||
%asm {{
|
%asm {{
|
||||||
; note cannot store this on cpu hardware stack because this gets called as a subroutine
|
; note cannot store this on cpu hardware stack because this gets called as a subroutine
|
||||||
@ -782,6 +784,7 @@ asmsub push_vera_context() clobbers(A) {
|
|||||||
lda cx16.VERA_CTRL
|
lda cx16.VERA_CTRL
|
||||||
sta _vera_storage+3
|
sta _vera_storage+3
|
||||||
eor #1
|
eor #1
|
||||||
|
sta _vera_storage+7
|
||||||
sta cx16.VERA_CTRL
|
sta cx16.VERA_CTRL
|
||||||
lda cx16.VERA_ADDR_L
|
lda cx16.VERA_ADDR_L
|
||||||
sta _vera_storage+4
|
sta _vera_storage+4
|
||||||
@ -789,31 +792,29 @@ asmsub push_vera_context() clobbers(A) {
|
|||||||
sta _vera_storage+5
|
sta _vera_storage+5
|
||||||
lda cx16.VERA_ADDR_H
|
lda cx16.VERA_ADDR_H
|
||||||
sta _vera_storage+6
|
sta _vera_storage+6
|
||||||
lda cx16.VERA_CTRL
|
|
||||||
sta _vera_storage+7
|
|
||||||
rts
|
rts
|
||||||
_vera_storage: .byte 0,0,0,0,0,0,0,0
|
_vera_storage: .byte 0,0,0,0,0,0,0,0
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub pop_vera_context() clobbers(A) {
|
asmsub restore_vera_context() clobbers(A) {
|
||||||
; -- use this at the end of your IRQ handler if it uses Vera registers, to restore the state
|
; -- use this at the end of your IRQ handler if it uses Vera registers, to restore the state
|
||||||
%asm {{
|
%asm {{
|
||||||
lda cx16.push_vera_context._vera_storage+7
|
lda cx16.save_vera_context._vera_storage+7
|
||||||
sta cx16.VERA_CTRL
|
sta cx16.VERA_CTRL
|
||||||
lda cx16.push_vera_context._vera_storage+6
|
lda cx16.save_vera_context._vera_storage+6
|
||||||
sta cx16.VERA_ADDR_H
|
sta cx16.VERA_ADDR_H
|
||||||
lda cx16.push_vera_context._vera_storage+5
|
lda cx16.save_vera_context._vera_storage+5
|
||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
lda cx16.push_vera_context._vera_storage+4
|
lda cx16.save_vera_context._vera_storage+4
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
lda cx16.push_vera_context._vera_storage+3
|
lda cx16.save_vera_context._vera_storage+3
|
||||||
sta cx16.VERA_CTRL
|
sta cx16.VERA_CTRL
|
||||||
lda cx16.push_vera_context._vera_storage+2
|
lda cx16.save_vera_context._vera_storage+2
|
||||||
sta cx16.VERA_ADDR_H
|
sta cx16.VERA_ADDR_H
|
||||||
lda cx16.push_vera_context._vera_storage+1
|
lda cx16.save_vera_context._vera_storage+1
|
||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
lda cx16.push_vera_context._vera_storage+0
|
lda cx16.save_vera_context._vera_storage+0
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
@ -900,28 +901,52 @@ sys {
|
|||||||
|
|
||||||
asmsub reset_system() {
|
asmsub reset_system() {
|
||||||
; Soft-reset the system back to initial power-on Basic prompt.
|
; Soft-reset the system back to initial power-on Basic prompt.
|
||||||
|
; We do this via the SMC so that a true reset is performed that also resets the Vera fully.
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
stz $01 ; bank the kernal in
|
ldx #$42
|
||||||
lda #$80
|
ldy #1
|
||||||
sta cx16.VERA_CTRL ; reset Vera (kernal doesn't do this?)
|
tya
|
||||||
jmp (cx16.RESET_VEC)
|
jsr cx16.i2c_write_byte
|
||||||
|
bra *
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub poweroff_system() {
|
||||||
|
; use the SMC to shutdown the computer
|
||||||
|
void cx16.i2c_write_byte($42, $01, $00)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_leds_brightness(ubyte activity, ubyte power) {
|
||||||
|
void cx16.i2c_write_byte($42, $04, power)
|
||||||
|
void cx16.i2c_write_byte($42, $05, activity)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,26 +21,6 @@ sub home() {
|
|||||||
txt.chrout(19)
|
txt.chrout(19)
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub fix_autostart_square() {
|
|
||||||
; Here's a possible work around for weird issue that prints a black character after first call to c64.PLOT()
|
|
||||||
; if you're also using c64.CINT() yourself. The default prog8 program initialization (which calls CINT) already performs this workaround.
|
|
||||||
; The problem occurs when a program is autostarded in the emulator with -run -prg test.prg,
|
|
||||||
; or when the program is saved as AUTOBOOT.X16 and loaded on boot like that.
|
|
||||||
%asm {{
|
|
||||||
phx
|
|
||||||
sec
|
|
||||||
jsr c64.PLOT
|
|
||||||
clc
|
|
||||||
jsr c64.PLOT
|
|
||||||
lda #' '
|
|
||||||
jsr c64.CHROUT ; overwrite the black square
|
|
||||||
clc
|
|
||||||
jsr c64.PLOT ; cursor back to original position
|
|
||||||
plx
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub nl() {
|
sub nl() {
|
||||||
txt.chrout('\n')
|
txt.chrout('\n')
|
||||||
}
|
}
|
||||||
|
@ -282,6 +282,7 @@ 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
|
||||||
|
|
||||||
@ -316,6 +317,7 @@ m_in_buffer sta $ffff
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -475,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.
|
||||||
@ -488,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
|
||||||
@ -506,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
|
||||||
|
@ -997,6 +997,20 @@ _arg_s1 .word 0
|
|||||||
_arg_s2 .word 0
|
_arg_s2 .word 0
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
strcmp_stack .proc
|
||||||
|
; -- compare strings, both on stack.
|
||||||
|
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
jmp strcmp_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
strcmp_mem .proc
|
strcmp_mem .proc
|
||||||
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
||||||
|
@ -292,7 +292,7 @@ str = P8ZP_SCRATCH_W1
|
|||||||
sta modify_pattern2+2
|
sta modify_pattern2+2
|
||||||
jsr _match
|
jsr _match
|
||||||
lda #0
|
lda #0
|
||||||
adc #0
|
rol a
|
||||||
ldx P8ZP_SCRATCH_REG
|
ldx P8ZP_SCRATCH_REG
|
||||||
rts
|
rts
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user