mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13ab4166c0 | ||
|
|
3dc5a0e7f8 | ||
|
|
e15c5cde53 | ||
|
|
d88c09b098 | ||
|
|
893b383bdf | ||
|
|
dd7c9d62e6 | ||
|
|
97c5c90eff | ||
|
|
1fb94e7a7b | ||
|
|
daca87c6d0 | ||
|
|
203ec5fa46 | ||
|
|
9ea69c07b8 | ||
|
|
68539d6cc9 | ||
|
|
f75fd0811e | ||
|
|
836bc9d456 | ||
|
|
a37769aafe | ||
|
|
68e62e4bd2 | ||
|
|
a5cd3728c9 | ||
|
|
a48ce35f0b | ||
|
|
e1835b5775 | ||
|
|
433832b329 | ||
|
|
ee81da14d6 | ||
|
|
6395d1908e | ||
|
|
989a5a2f8a | ||
|
|
b7a622c68e | ||
|
|
a8507b437d | ||
|
|
e505bf9ccf | ||
|
|
a289b32053 | ||
|
|
c3f1f09ad1 | ||
|
|
70ee2026ff | ||
|
|
690782bf60 | ||
|
|
755cc4835e | ||
|
|
a684ea46e4 | ||
|
|
8fbe13f99d | ||
|
|
452e9e275f | ||
|
|
cd40088636 | ||
|
|
9b9e6f4af5 | ||
|
|
ae6eeadf54 | ||
|
|
5268b05060 | ||
|
|
390263a34e | ||
|
|
55646edc3e | ||
|
|
8d177beb78 | ||
|
|
1da0c59182 | ||
|
|
36e8f10d2b | ||
|
|
cdf5a8f20f | ||
|
|
eb64d92333 | ||
|
|
eb55da63ef | ||
|
|
918302f79b | ||
|
|
9d7131d9f6 | ||
|
|
229c1114dd | ||
|
|
885df9156f | ||
|
|
c319233ddc | ||
|
|
958b5c0780 | ||
|
|
880c0a5da8 | ||
|
|
237c6dc856 | ||
|
|
ccf6e32bf9 | ||
|
|
a1874f6f00 | ||
|
|
95e4490a8a | ||
|
|
31c132c2eb | ||
|
|
00b0ec58b4 | ||
|
|
a1d0e5bb65 | ||
|
|
03e0d4b2e8 | ||
|
|
6afdd4e6fd | ||
|
|
b500a0d477 | ||
|
|
dd2463a440 | ||
|
|
23a8bebd9e | ||
|
|
3caf9108ad | ||
|
|
bde4be8231 | ||
|
|
0bbbb12ed2 | ||
|
|
b570bdaed7 | ||
|
|
8c0843cc87 | ||
|
|
31458ffd81 | ||
|
|
c15c10a94e | ||
|
|
9fca978725 | ||
|
|
b125901717 | ||
|
|
eb018ae660 | ||
|
|
7e5a9474fe | ||
|
|
525a9b5036 | ||
|
|
c3fbdf34ca | ||
|
|
48bd51e1a5 | ||
|
|
10d0b03a90 | ||
|
|
e1b3582f08 | ||
|
|
95be1c9e22 | ||
|
|
1ce8fe06d5 | ||
|
|
15c649024e | ||
|
|
e97303c226 | ||
|
|
3b786c819d | ||
|
|
04959dbd8b | ||
|
|
5cd4b874ea | ||
|
|
f14ea1b3de | ||
|
|
9cc0cda0fb | ||
|
|
09a7a4bbe5 | ||
|
|
cfea8b3745 | ||
|
|
28bf0b61ce | ||
|
|
2dc2429735 | ||
|
|
83d4592526 | ||
|
|
2d528c26ae | ||
|
|
66b3dce794 | ||
|
|
93f77a1045 | ||
|
|
aa4d23a3d5 | ||
|
|
2d7ebff8e9 | ||
|
|
bad9dd3b3b | ||
|
|
2f4e517857 | ||
|
|
ff35ba3696 | ||
|
|
72768e7fad | ||
|
|
77f3852cdc | ||
|
|
66857ca477 | ||
|
|
75514fc7af | ||
|
|
be06d871b6 | ||
|
|
f98ee326b4 | ||
|
|
bc8126eb16 | ||
|
|
4c8beefdcb | ||
|
|
bbb6c53457 | ||
|
|
d8991894e3 | ||
|
|
c7b7dcfd03 | ||
|
|
2c9e50873c | ||
|
|
923367296d | ||
|
|
151a206617 | ||
|
|
e403c4cf99 | ||
|
|
e3fbe37f9f | ||
|
|
dc870cd5ea | ||
|
|
584be44743 | ||
|
|
5fffd35ec1 | ||
|
|
b92e22e4a6 | ||
|
|
3e6d16a7a8 | ||
|
|
ecbcc277b8 | ||
|
|
dff1d9e4dd | ||
|
|
7c0bde7310 | ||
|
|
a82d21ac05 | ||
|
|
0bf8378fcb | ||
|
|
017ef8a837 | ||
|
|
0d63cdcb96 | ||
|
|
68a6f99c9f | ||
|
|
60781bcfc4 | ||
|
|
77fa2e2722 | ||
|
|
c36afd872e | ||
|
|
7e58a4c130 | ||
|
|
19a4bf1088 | ||
|
|
9678bbae4b | ||
|
|
a4d093afa1 | ||
|
|
ba788bcf0f | ||
|
|
f2c62bee7e | ||
|
|
548721e306 | ||
|
|
1ae950a638 | ||
|
|
c9385e93fe | ||
|
|
9bb16e293c | ||
|
|
c223702ea0 | ||
|
|
9167ba499d | ||
|
|
2d7e95e1b6 | ||
|
|
0cba736446 | ||
|
|
0816a57032 | ||
|
|
a0ab0bd3e2 | ||
|
|
b89ad4b328 | ||
|
|
6cda76a116 | ||
|
|
c112b327ab | ||
|
|
46c12a8899 | ||
|
|
c5219dfb3f | ||
|
|
4a8ee6815a | ||
|
|
e1b6bb154a | ||
|
|
b19c282269 | ||
|
|
e520921746 | ||
|
|
970642244b | ||
|
|
3b90be2d9e | ||
|
|
2f756f1e3a | ||
|
|
78e84182f0 | ||
|
|
65a7a8caf8 | ||
|
|
4c6a2f5df9 | ||
|
|
fea297e409 | ||
|
|
7cf6aba625 | ||
|
|
3bbc00cc8c | ||
|
|
70ed2b4203 | ||
|
|
0adce9b9c6 | ||
|
|
0e781d18fa | ||
|
|
4575a8fffe | ||
|
|
10d0ff252b | ||
|
|
c7d54570cc | ||
|
|
7136b33f2e | ||
|
|
70a78e74f6 | ||
|
|
d5707b7bf3 | ||
|
|
9f247901d4 | ||
|
|
5659742d97 | ||
|
|
450eaf7c4a | ||
|
|
47485e4b49 | ||
|
|
64254e758d | ||
|
|
c1aa5d4e47 | ||
|
|
ab8173637a | ||
|
|
3841cef497 | ||
|
|
b717f1c7eb | ||
|
|
da57f76de3 | ||
|
|
4784f1c65a | ||
|
|
41af63b333 | ||
|
|
e2bb0de24d | ||
|
|
b791fae9ce | ||
|
|
6033a9e20c | ||
|
|
9e8c8973d8 | ||
|
|
3933bf5c1a | ||
|
|
708e296774 | ||
|
|
84925ab69c | ||
|
|
b3cb9b7fe2 | ||
|
|
9cb61fa34d | ||
|
|
7c219d235c | ||
|
|
6938c79f88 | ||
|
|
b8284a147d | ||
|
|
15ee90e99c | ||
|
|
795f80b4ec | ||
|
|
6b6427492d | ||
|
|
6055b8c3dc | ||
|
|
a98cb50d55 | ||
|
|
e98bbc1c52 | ||
|
|
7245aece4f | ||
|
|
60cbb02822 | ||
|
|
4e863ecdac | ||
|
|
5037033fcf | ||
|
|
e6b158bc97 | ||
|
|
4cc0dfa10b | ||
|
|
4ced8889d3 | ||
|
|
d26967a87d | ||
|
|
fc8955941b | ||
|
|
071a80360f | ||
|
|
d2154f5f2e | ||
|
|
334d382bfa | ||
|
|
90c4b00f74 | ||
|
|
71261525e8 | ||
|
|
3126959576 | ||
|
|
02e51d8282 | ||
|
|
ffb2027a19 | ||
|
|
70c9ab9074 | ||
|
|
6d1fdf1ba6 | ||
|
|
1f7180d9a8 | ||
|
|
b4e94ae4dd | ||
|
|
07c606bfc9 | ||
|
|
e705a8bd89 | ||
|
|
b3bdfb7f1f | ||
|
|
5af1aeb092 | ||
|
|
be64fa674a | ||
|
|
204f5591a9 | ||
|
|
ee3e3a3a40 | ||
|
|
f9200a2b75 | ||
|
|
f570b70827 | ||
|
|
0db141eeac | ||
|
|
acb2ee53bb | ||
|
|
c544b7f5ba | ||
|
|
c0024e97e5 | ||
|
|
bdf8aa9168 | ||
|
|
de5ce0f515 | ||
|
|
bb95484c8a | ||
|
|
cad18b8a3a | ||
|
|
0f6a98751a | ||
|
|
aac5a4c27f | ||
|
|
d3f6415387 | ||
|
|
04da44eb98 | ||
|
|
7649be97b1 | ||
|
|
c9ef777e0f | ||
|
|
c0cb2438d5 | ||
|
|
30c531b39e | ||
|
|
bf703a8a66 | ||
|
|
e7b631b087 | ||
|
|
a9f5dc036c | ||
|
|
0a83b51e00 | ||
|
|
eab63ecc6c | ||
|
|
b0794cf35e | ||
|
|
5b9e71a27d | ||
|
|
eae41de27d | ||
|
|
e9163aa3a7 | ||
|
|
8c617515ba | ||
|
|
04e4e71f2e | ||
|
|
a587482edf | ||
|
|
0aac9350d5 | ||
|
|
f56c12ee4e | ||
|
|
4bb9ae61f2 | ||
|
|
ff7f3484e4 | ||
|
|
5da3abe6b4 | ||
|
|
c0b398e0ce | ||
|
|
3de10adac2 | ||
|
|
1b573d6552 | ||
|
|
2a96f93919 | ||
|
|
c6b2639ca4 | ||
|
|
b9abf37a7e | ||
|
|
373cbb4144 | ||
|
|
a521982576 | ||
|
|
a77fde577c | ||
|
|
ea6926e57d | ||
|
|
ba25b7fee6 | ||
|
|
7ee162d98b | ||
|
|
380f557c45 | ||
|
|
1bdae53f4e | ||
|
|
9314c346da | ||
|
|
bfaad1388c | ||
|
|
0b580ad05d | ||
|
|
bb35a80177 | ||
|
|
24fc95ac81 | ||
|
|
8f864417c4 | ||
|
|
bb9d29b061 | ||
|
|
b9d8ec1463 | ||
|
|
1842a7660d | ||
|
|
5caa2f5536 | ||
|
|
d6078be8b7 | ||
|
|
cf60723f14 | ||
|
|
f7ff0a2b1d | ||
|
|
cc49664b2f | ||
|
|
99fe74f026 | ||
|
|
b021869eeb | ||
|
|
b8806d163b | ||
|
|
1116aae1de | ||
|
|
5e5f60253b | ||
|
|
bbc02752c9 | ||
|
|
9896bc110e | ||
|
|
ca60f8ecdd | ||
|
|
544acd1e35 | ||
|
|
82898f7bba | ||
|
|
d61283a8bc | ||
|
|
1ee3f826cc | ||
|
|
4a00a5ba9e | ||
|
|
39eda67867 | ||
|
|
a99d38fdaa | ||
|
|
3ac9036c79 | ||
|
|
c94e292176 | ||
|
|
91d87c2d9b | ||
|
|
ff472f69c0 | ||
|
|
e18119e24c | ||
|
|
4a592dc64c | ||
|
|
5c75b19c5d | ||
|
|
52a77db60f | ||
|
|
0513c250fb | ||
|
|
cdbccad21e | ||
|
|
e15bc68c9b | ||
|
|
8bffd7672d | ||
|
|
61df5b3060 | ||
|
|
0c94e377fc | ||
|
|
b24f2f1756 | ||
|
|
061617122a | ||
|
|
06d1570142 | ||
|
|
093c370faa | ||
|
|
aec9574737 | ||
|
|
300e2fe9f8 | ||
|
|
91e1643627 | ||
|
|
91421b0c62 | ||
|
|
40f611664f | ||
|
|
dcba4f4098 | ||
|
|
c098ad2b3b | ||
|
|
b43223cb7a | ||
|
|
e243531dab | ||
|
|
1af38e62bc | ||
|
|
f37f062cdc | ||
|
|
05d152746f | ||
|
|
b4963b725b | ||
|
|
6a664a7e15 | ||
|
|
85cf0e311c | ||
|
|
1e469b3b0f | ||
|
|
bd2bcb6994 | ||
|
|
fd1e9971e4 | ||
|
|
21bc505d85 | ||
|
|
3d69a95c49 | ||
|
|
d81fdf6d6b | ||
|
|
87d3109ffb | ||
|
|
180dbbb521 | ||
|
|
24aac7cee5 | ||
|
|
53e18a5387 | ||
|
|
92062d056d | ||
|
|
06368ab0a1 | ||
|
|
38efe25c68 | ||
|
|
319079de7a | ||
|
|
025bf900a5 | ||
|
|
2885f4f7b1 | ||
|
|
c07eda15b1 | ||
|
|
4274296cf3 | ||
|
|
76a203d4df |
No files matched your search
@@ -10,13 +10,18 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install 64tass
|
||||
run: sudo apt-get update -y && sudo apt-get install -y 64tass
|
||||
- name: build and install recent 64tass
|
||||
run: |
|
||||
sudo apt-get install -y make build-essential
|
||||
git clone --depth=1 https://github.com/irmen/64tass
|
||||
cd 64tass
|
||||
make -j4
|
||||
sudo make install
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
|
||||
@@ -15,6 +15,7 @@ out/
|
||||
parser/**/*.interp
|
||||
parser/**/*.tokens
|
||||
parser/**/*.java
|
||||
compiler/src/prog8/buildversion/*
|
||||
*.py[cod]
|
||||
*.egg
|
||||
*.egg-info
|
||||
@@ -29,6 +30,8 @@ parsetab.py
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
**/BuildVersion.kt
|
||||
/prog8compiler.jar
|
||||
sd*.img
|
||||
*.d64
|
||||
|
||||
Generated
+1
-1
@@ -4,6 +4,6 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.8.21-release-380" />
|
||||
<option name="version" value="1.9.0-release-358" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.12.0">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.1">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.12.0/antlr4-4.12.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.12.0/antlr4-runtime-4.12.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
|
||||
+14
-14
@@ -1,21 +1,21 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.5.5" />
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.7.2" />
|
||||
<CLASSES>
|
||||
<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/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/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.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.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/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$/io/kotest/kotest-common-jvm/5.5.5/kotest-common-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-core-jvm/1.6.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.property.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-property-jvm:5.5.5" />
|
||||
<CLASSES>
|
||||
<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$/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/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/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-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.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+28
-41
@@ -1,55 +1,42 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.5.5" />
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.7.2" />
|
||||
<CLASSES>
|
||||
<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.5.5/kotest-framework-api-jvm-5.5.5.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/kotest/kotest-assertions-shared-jvm/5.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.7.2/kotest-runner-junit5-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.7.2/kotest-framework-api-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.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/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.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-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$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.7.2/kotest-framework-engine-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.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$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.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$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.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/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.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$/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-core-jvm/5.5.5/kotest-assertions-core-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$/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-extensions-jvm/5.5.5/kotest-extensions-jvm-5.5.5.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-jvm/1.13.1/mockk-dsl-jvm-1.13.1.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$/org/objenesis/objenesis/3.2/objenesis-3.2.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-core-jvm/1.13.1/mockk-core-jvm-1.13.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$/junit/junit/4.13.2/junit-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.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/junit/jupiter/junit-jupiter-params/5.8.2/junit-jupiter-params-5.8.2.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/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-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$/io/kotest/kotest-framework-discovery-jvm/5.7.2/kotest-framework-discovery-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.7.2/kotest-extensions-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.7.2/kotest-framework-concurrency-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.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/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/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-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/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.5" />
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16" />
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.16/kotlin-result-jvm-1.1.16.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.20/kotlin-stdlib-common-1.6.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.20/kotlin-stdlib-jdk8-1.6.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.20/kotlin-stdlib-1.6.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.18/kotlin-result-jvm-1.1.18.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.20/kotlin-stdlib-jdk7-1.6.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
Generated
+3
-3
@@ -1,9 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="slf4j.simple" type="repository">
|
||||
<properties maven-id="org.slf4j:slf4j-simple:2.0.7" />
|
||||
<properties maven-id="org.slf4j:slf4j-simple:2.0.9" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.7/slf4j-simple-2.0.7.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.9/slf4j-simple-2.0.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
Generated
+3
@@ -16,6 +16,9 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
|
||||
@@ -14,6 +14,21 @@ Documentation
|
||||
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||
https://prog8.readthedocs.io/
|
||||
|
||||
How to get it/build it
|
||||
----------------------
|
||||
|
||||
- Download the latest [official release](https://github.com/irmen/prog8/releases) from github.
|
||||
- Or, if you want/need a bleeding edge development version, you can:
|
||||
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
|
||||
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
|
||||
|
||||
Community
|
||||
---------
|
||||
Most of the development on Prog8 and the use of it is currently centered around
|
||||
the [Commander X16](https://www.commanderx16.com/) retro computer. Their [discord server](https://discord.gg/nS2PqEC) contains a small channel
|
||||
dedicated to Prog8. Other than that, use the issue tracker on github.
|
||||
|
||||
|
||||
Software license
|
||||
----------------
|
||||
GNU GPL 3.0 (see file LICENSE), with exception for generated code:
|
||||
@@ -59,6 +74,8 @@ What does Prog8 provide?
|
||||
- "c64": Commodore-64 (6502 like CPU)
|
||||
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
||||
- "pet32": Commodore PET (experimental)
|
||||
- "atari": Atari 8 bit such as 800XL (experimental)
|
||||
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
|
||||
|
||||
|
||||
|
||||
@@ -26,16 +26,16 @@ compileTestKotlin {
|
||||
dependencies {
|
||||
// should have no dependencies to other modules
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,16 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
|
||||
}
|
||||
|
||||
override fun lookup(scopedName: String) = flat[scopedName]
|
||||
|
||||
fun getLength(name: String): Int? {
|
||||
val node = flat[name]
|
||||
return when(node) {
|
||||
is StMemVar -> node.length
|
||||
is StMemorySlab -> node.size.toInt()
|
||||
is StStaticVariable -> node.length
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,13 +196,10 @@ class StStaticVariable(name: String,
|
||||
}
|
||||
if(onetimeInitializationNumericValue!=null) {
|
||||
require(dt in NumericDatatypes)
|
||||
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
|
||||
}
|
||||
if(onetimeInitializationArrayValue!=null) {
|
||||
require(dt in ArrayDatatypes)
|
||||
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
|
||||
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
|
||||
}
|
||||
require(length==onetimeInitializationArrayValue.size)
|
||||
}
|
||||
if(onetimeInitializationStringValue!=null) {
|
||||
require(dt == DataType.STR)
|
||||
@@ -203,8 +210,7 @@ class StStaticVariable(name: String,
|
||||
|
||||
|
||||
class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) :
|
||||
StNode(name, StNodeType.CONSTANT, astNode) {
|
||||
}
|
||||
StNode(name, StNodeType.CONSTANT, astNode)
|
||||
|
||||
|
||||
class StMemVar(name: String,
|
||||
@@ -226,12 +232,11 @@ class StMemorySlab(
|
||||
val align: UInt,
|
||||
astNode: PtNode
|
||||
):
|
||||
StNode(name, StNodeType.MEMORYSLAB, astNode) {
|
||||
}
|
||||
StNode(name, StNodeType.MEMORYSLAB, astNode)
|
||||
|
||||
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
|
||||
StNode(name, StNodeType.SUBROUTINE, astNode) {
|
||||
}
|
||||
StNode(name, StNodeType.SUBROUTINE, astNode)
|
||||
|
||||
|
||||
class StRomSub(name: String,
|
||||
|
||||
@@ -26,8 +26,6 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
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))
|
||||
@@ -76,10 +74,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
numElements = value.value.length + 1 // include the terminating 0-byte
|
||||
}
|
||||
is PtArray -> {
|
||||
val array = makeInitialArray(value)
|
||||
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
|
||||
initialArray = makeInitialArray(value)
|
||||
initialString = null
|
||||
numElements = array.size
|
||||
numElements = initialArray.size
|
||||
require(node.arraySize?.toInt()==numElements)
|
||||
}
|
||||
else -> {
|
||||
@@ -94,6 +91,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
initialString = null
|
||||
numElements = node.arraySize?.toInt()
|
||||
}
|
||||
// if(node.type in SplitWordArrayTypes) {
|
||||
// ... split array also add _lsb and _msb to symboltable?
|
||||
// }
|
||||
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
@@ -125,7 +125,11 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||
return value.children.map {
|
||||
when(it) {
|
||||
is PtAddressOf -> StArrayElement(null, it.identifier.name)
|
||||
is PtAddressOf -> {
|
||||
if(it.isFromArrayElement)
|
||||
TODO("address-of array element $it in initial array value")
|
||||
StArrayElement(null, it.identifier.name)
|
||||
}
|
||||
is PtIdentifier -> StArrayElement(null, it.name)
|
||||
is PtNumber -> StArrayElement(it.number, null)
|
||||
else -> throw AssemblyError("invalid array element $it")
|
||||
|
||||
@@ -37,16 +37,17 @@ class PtNodeGroup : PtNode(Position.DUMMY)
|
||||
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
|
||||
// Note that as an exception, the 'name' is not read-only
|
||||
// but a var. This is to allow for cheap node renames.
|
||||
val scopedName: String by lazy {
|
||||
var namedParent: PtNode = this.parent
|
||||
if(namedParent is PtProgram)
|
||||
name
|
||||
else {
|
||||
while (namedParent !is PtNamedNode)
|
||||
namedParent = namedParent.parent
|
||||
namedParent.scopedName + "." + name
|
||||
val scopedName: String
|
||||
get() {
|
||||
var namedParent: PtNode = this.parent
|
||||
return if(namedParent is PtProgram)
|
||||
name
|
||||
else {
|
||||
while (namedParent !is PtNamedNode)
|
||||
namedParent = namedParent.parent
|
||||
namedParent.scopedName + "." + name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +64,9 @@ class PtProgram(
|
||||
children.asSequence().filterIsInstance<PtBlock>()
|
||||
|
||||
fun entrypoint(): PtSub? =
|
||||
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start") } as PtSub?
|
||||
allBlocks().firstOrNull { it.name == "main" || it.name=="p8_main" }
|
||||
?.children
|
||||
?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start" || it.name=="p8_start" || it.name=="p8_main.p8_start") } as PtSub?
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +74,8 @@ class PtBlock(name: String,
|
||||
val address: UInt?,
|
||||
val library: Boolean,
|
||||
val forceOutput: Boolean,
|
||||
val noSymbolPrefixing: Boolean,
|
||||
val veraFxMuls: Boolean,
|
||||
val alignment: BlockAlignment,
|
||||
val source: SourceCode, // taken from the module the block is defined in.
|
||||
position: Position
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.NumericDatatypes
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.*
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.round
|
||||
@@ -27,8 +24,18 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
|
||||
infix fun isSameAs(other: PtExpression): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
|
||||
is PtAddressOf -> {
|
||||
if(other !is PtAddressOf)
|
||||
return false
|
||||
if (other.type!==type || !(other.identifier isSameAs identifier))
|
||||
return false
|
||||
if(other.children.size!=children.size)
|
||||
return false
|
||||
if(children.size==1)
|
||||
return true
|
||||
return arrayIndexExpr!! isSameAs other.arrayIndexExpr!!
|
||||
}
|
||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index && other.splitWords==splitWords
|
||||
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
|
||||
@@ -51,7 +58,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
this.name == target.identifier!!.name
|
||||
}
|
||||
target.array != null && this is PtArrayIndexer -> {
|
||||
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index
|
||||
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index && this.splitWords==target.array!!.splitWords
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
@@ -108,7 +115,12 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
|
||||
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||
val identifier: PtIdentifier
|
||||
get() = children.single() as PtIdentifier
|
||||
get() = children[0] as PtIdentifier
|
||||
val arrayIndexExpr: PtExpression?
|
||||
get() = if(children.size==2) children[1] as PtExpression else null
|
||||
|
||||
val isFromArrayElement: Boolean
|
||||
get() = children.size==2
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +130,12 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el
|
||||
val index: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
|
||||
val splitWords: Boolean
|
||||
get() = variable.type in SplitWordArrayTypes
|
||||
|
||||
val usesPointerVariable: Boolean
|
||||
get() = variable.type==DataType.UWORD
|
||||
|
||||
init {
|
||||
require(elementType in NumericDatatypes)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
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 PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
|
||||
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
||||
is PtBuiltinFunctionCall -> {
|
||||
val str = if(node.void) "void " else ""
|
||||
@@ -96,13 +96,14 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
str
|
||||
}
|
||||
is PtVariable -> {
|
||||
val split = if(node.type in SplitWordArrayTypes) "@split" else ""
|
||||
val str = if(node.arraySize!=null) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[${node.arraySize}] ${node.name}"
|
||||
"${eltType.name.lowercase()}[${node.arraySize}] $split ${node.name}"
|
||||
}
|
||||
else if(node.type in ArrayDatatypes) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[] ${node.name}"
|
||||
"${eltType.name.lowercase()}[] $split ${node.name}"
|
||||
}
|
||||
else
|
||||
"${node.type.name.lowercase()} ${node.name}"
|
||||
@@ -125,7 +126,6 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
else
|
||||
"->"
|
||||
}
|
||||
else -> throw InternalCompilerException("unrecognised ast node $node")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,9 +68,10 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
"setlsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
|
||||
"setmsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
|
||||
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
@@ -79,20 +80,44 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"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),
|
||||
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
|
||||
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
|
||||
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
|
||||
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
|
||||
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
|
||||
"abs__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
|
||||
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
|
||||
// normal functions follow:
|
||||
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
|
||||
"sizeof" to FSignature(true, listOf(FParam("object", DataType.entries.toTypedArray())), DataType.UBYTE),
|
||||
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
|
||||
"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),
|
||||
"sqrt" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
|
||||
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||
"sqrt__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"sqrt__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
|
||||
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divident", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("division", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"divmod__ubyte" 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),
|
||||
"divmod__uword" 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),
|
||||
"clamp" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE)), FParam("minimum", arrayOf(DataType.BYTE)), FParam("maximum", arrayOf(DataType.BYTE))), null),
|
||||
"clamp__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE)), FParam("minimum", arrayOf(DataType.BYTE)), FParam("maximum", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||
"clamp__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE)), FParam("minimum", arrayOf(DataType.UBYTE)), FParam("maximum", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||
"clamp__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD)), FParam("minimum", arrayOf(DataType.WORD)), FParam("maximum", arrayOf(DataType.WORD))), DataType.WORD),
|
||||
"clamp__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD)), FParam("minimum", arrayOf(DataType.UWORD)), FParam("maximum", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"min" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), null),
|
||||
"min__byte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||
"min__ubyte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UBYTE)), FParam("val2", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||
"min__word" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.WORD)), FParam("val2", arrayOf(DataType.WORD))), DataType.WORD),
|
||||
"min__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"max" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), null),
|
||||
"max__byte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||
"max__ubyte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UBYTE)), FParam("val2", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||
"max__word" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.WORD)), FParam("val2", arrayOf(DataType.WORD))), DataType.WORD),
|
||||
"max__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), 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),
|
||||
@@ -103,11 +128,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"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")
|
||||
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
|
||||
@@ -13,15 +13,15 @@ class CompilationOptions(val output: OutputType,
|
||||
val compTarget: ICompilationTarget,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
var slowCodegenWarnings: Boolean = false,
|
||||
var warnSymbolShadowing: Boolean = false,
|
||||
var optimize: Boolean = false,
|
||||
var optimizeFloatExpressions: Boolean = false,
|
||||
var asmQuiet: Boolean = false,
|
||||
var asmListfile: Boolean = false,
|
||||
var includeSourcelines: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var varsHigh: Boolean = false,
|
||||
var useNewExprCode: Boolean = false,
|
||||
var evalStackBaseAddress: UInt? = null,
|
||||
var varsHighBank: Int? = null,
|
||||
var splitWordArrays: Boolean = false,
|
||||
var breakpointCpuInstruction: Boolean = false,
|
||||
var outputDir: Path = Path(""),
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
) {
|
||||
|
||||
@@ -11,7 +11,9 @@ enum class DataType {
|
||||
ARRAY_UB, // pass by reference
|
||||
ARRAY_B, // pass by reference
|
||||
ARRAY_UW, // pass by reference
|
||||
ARRAY_UW_SPLIT, // pass by reference, lo/hi byte split
|
||||
ARRAY_W, // pass by reference
|
||||
ARRAY_W_SPLIT, // pass by reference, lo/hi byte split
|
||||
ARRAY_F, // pass by reference
|
||||
ARRAY_BOOL, // pass by reference
|
||||
UNDEFINED;
|
||||
@@ -119,12 +121,14 @@ val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWO
|
||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL)
|
||||
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)
|
||||
val IterableDatatypes = arrayOf(
|
||||
DataType.STR,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||
DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT,
|
||||
DataType.ARRAY_F, DataType.ARRAY_BOOL
|
||||
)
|
||||
val PassByValueDatatypes = NumericDatatypes
|
||||
@@ -135,6 +139,8 @@ val ArrayToElementTypes = mapOf(
|
||||
DataType.ARRAY_UB to DataType.UBYTE,
|
||||
DataType.ARRAY_W to DataType.WORD,
|
||||
DataType.ARRAY_UW to DataType.UWORD,
|
||||
DataType.ARRAY_W_SPLIT to DataType.WORD,
|
||||
DataType.ARRAY_UW_SPLIT to DataType.UWORD,
|
||||
DataType.ARRAY_F to DataType.FLOAT,
|
||||
DataType.ARRAY_BOOL to DataType.BOOL
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package prog8.code.core
|
||||
interface IErrorReporter {
|
||||
fun err(msg: String, position: Position)
|
||||
fun warn(msg: String, position: Position)
|
||||
fun undefined(symbol: List<String>, position: Position)
|
||||
fun noErrors(): Boolean
|
||||
fun report()
|
||||
fun finalizeNumErrors(numErrors: Int, numWarnings: Int) {
|
||||
|
||||
@@ -13,8 +13,6 @@ interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
var ESTACK_LO: UInt
|
||||
var ESTACK_HI: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
val BSSHIGHRAM_END: UInt
|
||||
@@ -29,11 +27,4 @@ interface IMachineDefinition {
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
fun overrideEvalStack(evalStackBaseAddress: UInt) {
|
||||
require(evalStackBaseAddress and 255u == 0u)
|
||||
ESTACK_LO = evalStackBaseAddress
|
||||
ESTACK_HI = evalStackBaseAddress + 256u
|
||||
require(ESTACK_LO !in golden.region && ESTACK_HI !in golden.region) { "user-set ESTACK can't be in GOLDEN ram" }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.code.core
|
||||
|
||||
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8.code.core.SourceCode.Companion.LIBRARYFILEPREFIX
|
||||
import java.nio.file.InvalidPathException
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
@@ -10,7 +10,7 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
|
||||
fun toClickableStr(): String {
|
||||
if(this===DUMMY)
|
||||
return ""
|
||||
if(file.startsWith(libraryFilePrefix))
|
||||
if(file.startsWith(LIBRARYFILEPREFIX))
|
||||
return "$file:$line:$startCol:"
|
||||
return try {
|
||||
val path = Path(file).absolute().normalize().toString()
|
||||
|
||||
@@ -54,12 +54,14 @@ sealed class SourceCode {
|
||||
/**
|
||||
* filename prefix to designate library files that will be retreived from internal resources rather than disk
|
||||
*/
|
||||
const val libraryFilePrefix = "library:"
|
||||
const val stringSourcePrefix = "string:"
|
||||
const val LIBRARYFILEPREFIX = "library:"
|
||||
const val STRINGSOURCEPREFIX = "string:"
|
||||
val curdir: Path = Path(".").toAbsolutePath()
|
||||
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
|
||||
fun isRegularFilesystemPath(pathString: String) =
|
||||
!(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix))
|
||||
!(pathString.startsWith(LIBRARYFILEPREFIX) || pathString.startsWith(STRINGSOURCEPREFIX))
|
||||
|
||||
fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +71,7 @@ sealed class SourceCode {
|
||||
class Text(override val text: String): SourceCode() {
|
||||
override val isFromResources = false
|
||||
override val isFromFilesystem = false
|
||||
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}"
|
||||
override val origin = "$STRINGSOURCEPREFIX${System.identityHashCode(text).toString(16)}"
|
||||
override val name = "<unnamed-text>"
|
||||
}
|
||||
|
||||
@@ -110,7 +112,7 @@ sealed class SourceCode {
|
||||
|
||||
override val isFromResources = true
|
||||
override val isFromFilesystem = false
|
||||
override val origin = "$libraryFilePrefix$normalized"
|
||||
override val origin = "$LIBRARYFILEPREFIX$normalized"
|
||||
override val text: String
|
||||
override val name: String
|
||||
|
||||
@@ -139,3 +141,33 @@ sealed class SourceCode {
|
||||
override val text: String = "<generated code node, no text representation>"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object SourceLineCache {
|
||||
private val cache = mutableMapOf<String, List<String>>()
|
||||
|
||||
private fun getCachedFile(file: String): List<String> {
|
||||
val existing = cache[file]
|
||||
if(existing!=null)
|
||||
return existing
|
||||
if (SourceCode.isRegularFilesystemPath(file)) {
|
||||
val source = SourceCode.File(Path(file))
|
||||
cache[file] = source.text.split('\n', '\r').map { it.trim() }
|
||||
return cache.getValue(file)
|
||||
} else if(file.startsWith(SourceCode.LIBRARYFILEPREFIX)) {
|
||||
val source = SourceCode.Resource(file.drop(SourceCode.LIBRARYFILEPREFIX.length))
|
||||
cache[file] = source.text.split('\n', '\r').map { it.trim()}
|
||||
return cache.getValue(file)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun retrieveLine(position: Position): String? {
|
||||
if (position.line>0) {
|
||||
val lines = getCachedFile(position.file)
|
||||
if(lines.isNotEmpty())
|
||||
return lines[position.line-1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IMemSizer
|
||||
import prog8.code.core.IStringEncoding
|
||||
import prog8.code.target.cbm.CbmMemorySizer
|
||||
import prog8.code.target.pet.PETMachineDefinition
|
||||
|
||||
|
||||
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = PETMachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
|
||||
companion object {
|
||||
const val NAME = "pet32"
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,6 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = 6
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
||||
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive // TODO
|
||||
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive // TODO
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO
|
||||
override val BSSHIGHRAM_END = 0u // TODO
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive
|
||||
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO
|
||||
override val BSSHIGHRAM_END = 0u // TODO
|
||||
|
||||
|
||||
@@ -16,12 +16,8 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0xcf00u // $cf00-$cf7f inclusive
|
||||
override var ESTACK_HI = 0xcf80u // $cf80-$cfff inclusive
|
||||
|
||||
override val BSSHIGHRAM_START = 0xc000u
|
||||
override val BSSHIGHRAM_END = ESTACK_LO
|
||||
override val BSSHIGHRAM_END = 0xd000u
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
@@ -62,7 +58,7 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, 0xc000u until ESTACK_LO)
|
||||
golden = GoldenRam(compilerOptions, 0xc000u until 0xd000u)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,11 +53,11 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
if(options.zeropage != ZeropageType.DONTUSE) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
|
||||
@@ -24,7 +24,7 @@ object PetsciiEncoding {
|
||||
'\ufffe', // 0x0A -> UNDEFINED
|
||||
'\ufffe', // 0x0B -> UNDEFINED
|
||||
'\ufffe', // 0x0C -> UNDEFINED
|
||||
'\r' , // 0x0D -> CARRIAGE RETURN
|
||||
'\n' , // 0x0D -> LINE FEED (RETURN)
|
||||
'\u000e', // 0x0E -> SHIFT OUT
|
||||
'\ufffe', // 0x0F -> UNDEFINED
|
||||
'\ufffe', // 0x10 -> UNDEFINED
|
||||
@@ -152,7 +152,7 @@ object PetsciiEncoding {
|
||||
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
||||
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
||||
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
||||
'\n' , // 0x8D -> LINE FEED
|
||||
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
|
||||
'\u000f', // 0x8E -> SHIFT IN
|
||||
'\ufffe', // 0x8F -> UNDEFINED
|
||||
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
||||
@@ -283,7 +283,7 @@ object PetsciiEncoding {
|
||||
'\ufffe', // 0x0A -> UNDEFINED
|
||||
'\ufffe', // 0x0B -> UNDEFINED
|
||||
'\ufffe', // 0x0C -> UNDEFINED
|
||||
'\r' , // 0x0D -> CARRIAGE RETURN
|
||||
'\n' , // 0x0D -> LINE FEED (RETURN)
|
||||
'\u000e', // 0x0E -> SHIFT OUT
|
||||
'\ufffe', // 0x0F -> UNDEFINED
|
||||
'\ufffe', // 0x10 -> UNDEFINED
|
||||
@@ -411,7 +411,7 @@ object PetsciiEncoding {
|
||||
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
||||
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
||||
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
||||
'\n' , // 0x8D -> LINE FEED
|
||||
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
|
||||
'\u000f', // 0x8E -> SHIFT IN
|
||||
'\ufffe', // 0x8F -> UNDEFINED
|
||||
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
||||
@@ -1061,6 +1061,7 @@ object PetsciiEncoding {
|
||||
'}' -> '├'
|
||||
'|' -> '│'
|
||||
'\\' -> '╲'
|
||||
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
|
||||
else -> chr
|
||||
}
|
||||
|
||||
@@ -1076,7 +1077,10 @@ object PetsciiEncoding {
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
|
||||
if(chr.isISOControl())
|
||||
throw CharConversionException("no ${case}Petscii character for char #${chr.code}")
|
||||
else
|
||||
throw CharConversionException("no ${case}Petscii character for char #${chr.code} '${chr}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1119,7 +1123,10 @@ object PetsciiEncoding {
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
|
||||
if(chr.isISOControl())
|
||||
throw CharConversionException("no ${case}Screencode character for char #${chr.code}")
|
||||
else
|
||||
throw CharConversionException("no ${case}Screencode character for char #${chr.code} '${chr}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,6 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x0700u // $0700-$077f 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.
|
||||
|
||||
@@ -64,7 +60,7 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, 0x0400u until ESTACK_LO)
|
||||
golden = GoldenRam(compilerOptions, 0x0400u until 0x0800u)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,7 +40,6 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
val distinctFree = free.distinct()
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package prog8.code.target.pet
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.cbm.Mflpt5
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class PETMachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||
|
||||
override val BSSHIGHRAM_START = 0xffffu
|
||||
override val BSSHIGHRAM_END = 0xffffu
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The pet target only supports the main emulator (Vice).")
|
||||
return
|
||||
}
|
||||
|
||||
println("\nStarting PET emulator...")
|
||||
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
|
||||
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist,
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process=processb.start()
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = PETZeropage(compilerOptions)
|
||||
// there's no golden ram.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package prog8.code.target.pet
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.core.Zeropage
|
||||
import prog8.code.core.ZeropageType
|
||||
|
||||
|
||||
// reference: http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
|
||||
|
||||
class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0xb3u // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0xb4u // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xb6u // temp storage 1 for a word
|
||||
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
|
||||
|
||||
init {
|
||||
if (options.floats) {
|
||||
throw InternalCompilerException("PET target doesn't yet support floating point routines")
|
||||
}
|
||||
|
||||
if (options.floats && options.zeropage !in arrayOf(
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE,
|
||||
ZeropageType.DONTUSE
|
||||
))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x00u..0xffu)
|
||||
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x00u..0xffu)
|
||||
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
|
||||
}
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0xb3u..0xbau) // TODO more?
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
}
|
||||
|
||||
val distinctFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
TODO("Not known if PET can put the virtual regs in ZP")
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
|
||||
override var ESTACK_LO = 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
|
||||
|
||||
@@ -28,18 +28,18 @@ dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
test {
|
||||
|
||||
@@ -3,6 +3,7 @@ package prog8.codegen.cpu6502
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.code.StNodeType
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.*
|
||||
@@ -14,18 +15,178 @@ import kotlin.io.path.writeLines
|
||||
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
|
||||
|
||||
class AsmGen6502: ICodeGeneratorBackend {
|
||||
class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram? {
|
||||
val asmgen = AsmGen6502Internal(program, symbolTable, options, errors)
|
||||
val st = if(prefixSymbols) prefixSymbols(program, options, symbolTable) else symbolTable
|
||||
val asmgen = AsmGen6502Internal(program, st, options, errors)
|
||||
return asmgen.compileToAssembly()
|
||||
}
|
||||
|
||||
private fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
|
||||
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
|
||||
fun prefixNamedNode(node: PtNamedNode) {
|
||||
node.name = "p8_${node.name}"
|
||||
}
|
||||
|
||||
fun prefixSymbols(node: PtNode) {
|
||||
when(node) {
|
||||
is PtAsmSub -> {
|
||||
prefixNamedNode(node)
|
||||
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
|
||||
}
|
||||
is PtSub -> {
|
||||
prefixNamedNode(node)
|
||||
node.parameters.forEach { prefixNamedNode(it) }
|
||||
}
|
||||
is PtFunctionCall -> {
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
var lookupName = node.name
|
||||
if(node.type in SplitWordArrayTypes && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName)!!
|
||||
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
is PtJump -> {
|
||||
if(node.identifier!=null) {
|
||||
val stNode = st.lookup(node.identifier!!.name)!!
|
||||
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
else if(node.generatedLabel!=null) {
|
||||
val stNode = st.lookup(node.generatedLabel!!)!!
|
||||
if(stNode.astNode.definingBlock()?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtBlock -> prefixNamedNode(node)
|
||||
is PtConstant -> prefixNamedNode(node)
|
||||
is PtLabel -> prefixNamedNode(node)
|
||||
is PtMemMapped -> prefixNamedNode(node)
|
||||
is PtSubroutineParameter -> prefixNamedNode(node)
|
||||
is PtVariable -> {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
node.children.forEach { prefixSymbols(it) }
|
||||
}
|
||||
|
||||
program.allBlocks().forEach { block ->
|
||||
if (!block.noSymbolPrefixing) {
|
||||
prefixSymbols(block)
|
||||
}
|
||||
}
|
||||
|
||||
nodesToPrefix.forEach { (parent, index) ->
|
||||
val node = parent.children[index]
|
||||
when(node) {
|
||||
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
|
||||
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
|
||||
is PtJump -> parent.children[index] = node.prefix(parent, st)
|
||||
is PtVariable -> parent.children[index] = node.prefix(st)
|
||||
else -> throw AssemblyError("weird node to prefix $node")
|
||||
}
|
||||
}
|
||||
|
||||
// reversed so inner calls (such as arguments to a function call) get processed before the actual function call itself
|
||||
functionCallsToPrefix.reversed().forEach { (parent, index) ->
|
||||
val node = parent.children[index]
|
||||
if(node is PtFunctionCall) {
|
||||
parent.children[index] = node.prefix(parent)
|
||||
} else {
|
||||
throw AssemblyError("expected PtFunctionCall")
|
||||
}
|
||||
}
|
||||
|
||||
return SymbolTableMaker(program, options).make()
|
||||
}
|
||||
}
|
||||
|
||||
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
|
||||
name = name.split('.').map {"p8_$it" }.joinToString(".")
|
||||
if(value==null)
|
||||
return this
|
||||
|
||||
val arrayValue = value as? PtArray
|
||||
return if(arrayValue!=null && arrayValue.children.any { it !is PtNumber} ) {
|
||||
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
||||
arrayValue.children.forEach { elt ->
|
||||
when(elt) {
|
||||
is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st))
|
||||
is PtNumber -> newValue.add(elt)
|
||||
is PtAddressOf -> {
|
||||
if(elt.definingBlock()?.noSymbolPrefixing==true)
|
||||
newValue.add(elt)
|
||||
else {
|
||||
val newAddr = PtAddressOf(elt.position)
|
||||
newAddr.children.add(elt.identifier.prefix(newAddr, st))
|
||||
newAddr.parent = arrayValue
|
||||
newValue.add(newAddr)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
PtVariable(name, type, zeropage, newValue, arraySize, position)
|
||||
}
|
||||
else this
|
||||
}
|
||||
|
||||
private fun PtJump.prefix(parent: PtNode, st: SymbolTable): PtJump {
|
||||
val jump = if(identifier!=null) {
|
||||
val prefixedIdent = identifier!!.prefix(this, st)
|
||||
PtJump(prefixedIdent, address, generatedLabel, position)
|
||||
} else {
|
||||
val prefixedLabel = generatedLabel!!.split('.').map {"p8_$it" }.joinToString(".")
|
||||
PtJump(null, address, prefixedLabel, position)
|
||||
}
|
||||
jump.parent = parent
|
||||
return jump
|
||||
}
|
||||
|
||||
private fun PtFunctionCall.prefix(parent: PtNode): PtFunctionCall {
|
||||
val newName = name.split('.').map {"p8_$it" }.joinToString(".")
|
||||
val call = PtFunctionCall(newName, void, type, position)
|
||||
call.children.addAll(children)
|
||||
call.children.forEach { it.parent = call }
|
||||
call.parent = parent
|
||||
return call
|
||||
}
|
||||
|
||||
private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
|
||||
val target = st.lookup(name)
|
||||
if(target?.astNode?.definingBlock()?.noSymbolPrefixing==true)
|
||||
return this
|
||||
|
||||
val newName = name.split('.').map { "p8_$it" }.joinToString(".")
|
||||
val node = PtIdentifier(newName, type, position)
|
||||
node.parent = parent
|
||||
return node
|
||||
}
|
||||
|
||||
|
||||
class AsmGen6502Internal (
|
||||
val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
@@ -40,12 +201,12 @@ class AsmGen6502Internal (
|
||||
private val allocator = VariableAllocator(symbolTable, options, errors)
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
private val breakpointLabels = mutableListOf<String>()
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this, zeropage)
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(this, zeropage)
|
||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, symbolTable, this, allocator)
|
||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this, allocator)
|
||||
private val anyExprGen = AnyExprAsmGen(this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
|
||||
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
|
||||
|
||||
fun compileToAssembly(): IAssemblyProgram? {
|
||||
@@ -77,8 +238,19 @@ class AsmGen6502Internal (
|
||||
|
||||
internal fun isTargetCpu(cpu: CpuType) = options.compTarget.machine.cpu == cpu
|
||||
|
||||
private var lastSourceLineNumber: Int = -1
|
||||
|
||||
internal fun outputSourceLine(node: PtNode) {
|
||||
out(" ;\tsrc line: ${node.position.file}:${node.position.line}")
|
||||
if(!options.includeSourcelines || node.position===Position.DUMMY || node.position.line==lastSourceLineNumber)
|
||||
return
|
||||
|
||||
lastSourceLineNumber = node.position.line
|
||||
val srcComment = "\t; source: ${node.position.file}:${node.position.line}"
|
||||
val line = SourceLineCache.retrieveLine(node.position)
|
||||
if(line==null)
|
||||
out(srcComment, false)
|
||||
else
|
||||
out("$srcComment $line", false)
|
||||
}
|
||||
|
||||
internal fun out(str: String, splitlines: Boolean = true) {
|
||||
@@ -231,32 +403,6 @@ class AsmGen6502Internal (
|
||||
return name2.replace("prog8_lib.P8ZP_SCRATCH_", "P8ZP_SCRATCH_") // take care of the 'hooks' to the temp vars -> reference zp symbols directly
|
||||
}
|
||||
|
||||
internal fun saveRegisterLocal(register: CpuRegister, scope: IPtSubroutine) {
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
// just use the cpu's stack for all registers, shorter code
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> out(" phx")
|
||||
CpuRegister.Y -> out(" phy")
|
||||
}
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
// just use the stack, only for A
|
||||
out(" pha")
|
||||
}
|
||||
CpuRegister.X -> {
|
||||
out(" stx prog8_regsaveX")
|
||||
subroutineExtra(scope).usedRegsaveX = true
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
out(" sty prog8_regsaveY")
|
||||
subroutineExtra(scope).usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun saveRegisterStack(register: CpuRegister, keepA: Boolean) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
@@ -281,24 +427,6 @@ class AsmGen6502Internal (
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegisterLocal(register: CpuRegister) {
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (register) {
|
||||
// this just used the stack, for all registers. Shorter code.
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> out(" plx")
|
||||
CpuRegister.Y -> out(" ply")
|
||||
}
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pla") // this just used the stack but only for A
|
||||
CpuRegister.X -> out(" ldx prog8_regsaveX")
|
||||
CpuRegister.Y -> out(" ldy prog8_regsaveY")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegisterStack(register: CpuRegister, keepA: Boolean) {
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
@@ -362,13 +490,15 @@ class AsmGen6502Internal (
|
||||
internal fun loadScaledArrayIndexIntoRegister(
|
||||
expr: PtArrayIndexer,
|
||||
elementDt: DataType,
|
||||
register: CpuRegister,
|
||||
addOneExtra: Boolean = false
|
||||
register: CpuRegister
|
||||
) {
|
||||
val reg = register.toString().lowercase()
|
||||
val indexnum = expr.index.asConstInteger()
|
||||
if (indexnum != null) {
|
||||
val indexValue = indexnum * options.compTarget.memorySize(elementDt) + if (addOneExtra) 1 else 0
|
||||
val indexValue = if(expr.splitWords)
|
||||
indexnum
|
||||
else
|
||||
indexnum * options.compTarget.memorySize(elementDt)
|
||||
out(" ld$reg #$indexValue")
|
||||
return
|
||||
}
|
||||
@@ -377,96 +507,51 @@ class AsmGen6502Internal (
|
||||
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.index.position}")
|
||||
|
||||
val indexName = asmVariableName(indexVar)
|
||||
if (addOneExtra) {
|
||||
// add 1 to the result
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" ldy $indexName | iny")
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" tya")
|
||||
CpuRegister.X -> out(" tyx")
|
||||
CpuRegister.Y -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | sec | rol a")
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||
out(
|
||||
"""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
adc $indexName"""
|
||||
)
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
|
||||
if(expr.splitWords) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" lda $indexName")
|
||||
CpuRegister.X -> out(" ldx $indexName")
|
||||
CpuRegister.Y -> out(" ldy $indexName")
|
||||
}
|
||||
} else {
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||
out(
|
||||
"""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName"""
|
||||
)
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName"""
|
||||
)
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||
internal fun translateExpression(expression: PtExpression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateBuiltinFunctionCallExpression(bfc: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(bfc, resultToStack, resultRegister)
|
||||
internal fun translateBuiltinFunctionCallExpression(bfc: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?): DataType? =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(bfc, resultRegister)
|
||||
|
||||
internal fun translateFunctionCall(functionCallExpr: PtFunctionCall) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCallExpr)
|
||||
|
||||
internal fun saveXbeforeCall(functionCall: PtFunctionCall) =
|
||||
functioncallAsmGen.saveXbeforeCall(functionCall)
|
||||
|
||||
internal fun restoreXafterCall(functionCall: PtFunctionCall) =
|
||||
functioncallAsmGen.restoreXafterCall(functionCall)
|
||||
|
||||
internal fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) =
|
||||
assignmentAsmGen.translateNormalAssignment(assign, scope)
|
||||
|
||||
@@ -495,7 +580,6 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
|
||||
// don't use translateExpression() to avoid evalstack
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
@@ -546,7 +630,6 @@ class AsmGen6502Internal (
|
||||
private fun translate(stmt: PtIfElse) {
|
||||
val condition = stmt.condition as? PtBinaryExpression
|
||||
if(condition!=null) {
|
||||
require(!options.useNewExprCode)
|
||||
requireComparisonExpression(condition) // IfStatement: condition must be of form 'x <comparison> <value>'
|
||||
if (stmt.elseScope.children.isEmpty()) {
|
||||
val jump = stmt.ifScope.children.singleOrNull()
|
||||
@@ -674,65 +757,48 @@ class AsmGen6502Internal (
|
||||
private fun repeatWordCount(count: Int, stmt: PtRepeatLoop) {
|
||||
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
|
||||
out("""
|
||||
lda #<$count
|
||||
ldy #>$count
|
||||
sta $counterVar
|
||||
sty $counterVar+1
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, isTargetCpu(CpuType.CPU65c02), stmt)
|
||||
// the iny + double dec is microoptimization of the 16 bit loop
|
||||
out("""
|
||||
ldy #>$count
|
||||
lda #<$count
|
||||
beq +
|
||||
iny
|
||||
+ sta $counterVar
|
||||
sty $counterVar+1
|
||||
$repeatLabel""")
|
||||
translate(stmt.statements)
|
||||
out("""
|
||||
lda $counterVar
|
||||
bne +
|
||||
dec $counterVar+1
|
||||
+ dec $counterVar
|
||||
lda $counterVar
|
||||
ora $counterVar+1
|
||||
bne $repeatLabel""")
|
||||
} else {
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)
|
||||
out("""
|
||||
lda #<$count
|
||||
ldy #>$count
|
||||
sta $counterVar
|
||||
sty $counterVar+1
|
||||
$repeatLabel""")
|
||||
translate(stmt.statements)
|
||||
out("""
|
||||
lda $counterVar
|
||||
bne +
|
||||
dec $counterVar+1
|
||||
+ dec $counterVar
|
||||
lda $counterVar
|
||||
ora $counterVar+1
|
||||
bne $repeatLabel""")
|
||||
}
|
||||
translate(stmt.statements)
|
||||
out("""
|
||||
dec $counterVar
|
||||
bne $repeatLabel
|
||||
dec $counterVar+1
|
||||
bne $repeatLabel""")
|
||||
}
|
||||
|
||||
private fun repeatWordCountInAY(endLabel: String, stmt: PtRepeatLoop) {
|
||||
// note: A/Y must have been loaded with the number of iterations!
|
||||
// no need to explicitly test for 0 iterations as this is done in the countdown logic below
|
||||
// the iny + double dec is microoptimization of the 16 bit loop
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)
|
||||
out("""
|
||||
sta $counterVar
|
||||
sty $counterVar+1
|
||||
$repeatLabel lda $counterVar
|
||||
bne +
|
||||
lda $counterVar+1
|
||||
beq $endLabel
|
||||
lda $counterVar
|
||||
bne +
|
||||
dec $counterVar+1
|
||||
+ dec $counterVar
|
||||
""")
|
||||
cmp #0
|
||||
beq +
|
||||
iny
|
||||
+ sta $counterVar
|
||||
sty $counterVar+1
|
||||
ora $counterVar+1
|
||||
beq $endLabel
|
||||
$repeatLabel""")
|
||||
translate(stmt.statements)
|
||||
jmp(repeatLabel)
|
||||
out("""
|
||||
dec $counterVar
|
||||
bne $repeatLabel
|
||||
dec $counterVar+1
|
||||
bne $repeatLabel""")
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
|
||||
private fun repeatByteCount(count: Int, stmt: PtRepeatLoop) {
|
||||
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
@@ -795,7 +861,7 @@ $repeatLabel lda $counterVar
|
||||
DataType.UBYTE, DataType.UWORD -> {
|
||||
val result = zeropage.allocate(counterVar, dt, null, stmt.position, errors)
|
||||
result.fold(
|
||||
success = { (address, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
|
||||
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
|
||||
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
|
||||
)
|
||||
return counterVar
|
||||
@@ -935,7 +1001,10 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(asm: PtInlineAssembly) {
|
||||
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
|
||||
if(asm.isIR)
|
||||
throw AssemblyError("%asm containing IR code cannot be translated to 6502 assembly")
|
||||
else
|
||||
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
|
||||
}
|
||||
|
||||
private fun translate(incbin: PtIncludeBinary) {
|
||||
@@ -957,6 +1026,15 @@ $repeatLabel lda $counterVar
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
breakpointLabels.add(label)
|
||||
out(label)
|
||||
if(options.breakpointCpuInstruction) {
|
||||
val instruction =
|
||||
when(options.compTarget.machine.cpu) {
|
||||
CpuType.CPU6502 -> "brk"
|
||||
CpuType.CPU65c02 -> "stp"
|
||||
else -> throw AssemblyError("invalid cpu type")
|
||||
}
|
||||
out(" $instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendAYlsb(valueDt: DataType) {
|
||||
@@ -974,20 +1052,6 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendStackLsb(valueDt: DataType) {
|
||||
// sign extend signed byte on stack to signed word on stack
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(isTargetCpu(CpuType.CPU65c02))
|
||||
out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
DataType.BYTE -> out(" jsr prog8_lib.sign_extend_stack_byte")
|
||||
else -> throw AssemblyError("need byte type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendVariableLsb(asmvar: String, valueDt: DataType) {
|
||||
// sign extend signed byte in a var to a full word in that variable
|
||||
when(valueDt) {
|
||||
@@ -1023,18 +1087,10 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: PtExpression): Pair<PtExpression, PtExpression>? {
|
||||
val left: PtExpression
|
||||
val right: PtExpression
|
||||
val operator: String
|
||||
|
||||
if (pointerOffsetExpr is PtBinaryExpression) {
|
||||
require(!options.useNewExprCode)
|
||||
operator = pointerOffsetExpr.operator
|
||||
left = pointerOffsetExpr.left
|
||||
right = pointerOffsetExpr.right
|
||||
}
|
||||
else return null
|
||||
|
||||
if (pointerOffsetExpr !is PtBinaryExpression) return null
|
||||
val operator = pointerOffsetExpr.operator
|
||||
val left = pointerOffsetExpr.left
|
||||
val right = pointerOffsetExpr.right
|
||||
if (operator != "+") return null
|
||||
val leftDt = left.type
|
||||
val rightDt = right.type
|
||||
@@ -1146,10 +1202,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
internal fun findSubroutineParameter(name: String, asmgen: AsmGen6502Internal): PtSubroutineParameter? {
|
||||
val stScope = asmgen.symbolTable.lookup(name)
|
||||
require(stScope!=null) {
|
||||
"invalid name lookup $name"
|
||||
}
|
||||
val stScope = asmgen.symbolTable.lookup(name) ?: return null
|
||||
val node = stScope.astNode
|
||||
if(node is PtSubroutineParameter)
|
||||
return node
|
||||
@@ -1627,7 +1680,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
else if (left is PtMemoryByte) {
|
||||
return if(rightConstVal.number.toInt()!=0) {
|
||||
translateDirectMemReadExpressionToRegAorStack(left, false)
|
||||
translateDirectMemReadExpressionToRegA(left)
|
||||
code("#${rightConstVal.number.toInt()}")
|
||||
}
|
||||
else
|
||||
@@ -1774,7 +1827,7 @@ $repeatLabel lda $counterVar
|
||||
out(" beq $jumpIfFalseLabel")
|
||||
}
|
||||
else if (left is PtMemoryByte) {
|
||||
translateDirectMemReadExpressionToRegAorStack(left, false)
|
||||
translateDirectMemReadExpressionToRegA(left)
|
||||
return if(rightConstVal.number.toInt()!=0)
|
||||
code("#${rightConstVal.number.toInt()}")
|
||||
else
|
||||
@@ -1935,7 +1988,7 @@ $repeatLabel lda $counterVar
|
||||
out(" bne $jumpIfFalseLabel")
|
||||
}
|
||||
else if (left is PtMemoryByte) {
|
||||
translateDirectMemReadExpressionToRegAorStack(left, false)
|
||||
translateDirectMemReadExpressionToRegA(left)
|
||||
return if(rightConstVal.number.toInt()!=0)
|
||||
code("#${rightConstVal.number.toInt()}")
|
||||
else
|
||||
@@ -2099,7 +2152,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
else if (left is PtMemoryByte) {
|
||||
if(rightConstVal.number.toInt()!=0) {
|
||||
translateDirectMemReadExpressionToRegAorStack(left, false)
|
||||
translateDirectMemReadExpressionToRegA(left)
|
||||
code("#${rightConstVal.number.toInt()}")
|
||||
}
|
||||
return
|
||||
@@ -2262,7 +2315,7 @@ $repeatLabel lda $counterVar
|
||||
out(" bne $jumpIfFalseLabel")
|
||||
}
|
||||
else if (left is PtMemoryByte) {
|
||||
translateDirectMemReadExpressionToRegAorStack(left, false)
|
||||
translateDirectMemReadExpressionToRegA(left)
|
||||
return if(rightConstVal.number.toInt()!=0)
|
||||
code("#${rightConstVal.number.toInt()}")
|
||||
else
|
||||
@@ -2308,7 +2361,7 @@ $repeatLabel lda $counterVar
|
||||
out(" beq $jumpIfFalseLabel")
|
||||
}
|
||||
else if (left is PtMemoryByte) {
|
||||
translateDirectMemReadExpressionToRegAorStack(left, false)
|
||||
translateDirectMemReadExpressionToRegA(left)
|
||||
return if(rightConstVal.number.toInt()!=0)
|
||||
code("#${rightConstVal.number.toInt()}")
|
||||
else
|
||||
@@ -2373,8 +2426,7 @@ $repeatLabel lda $counterVar
|
||||
cmp #<$number
|
||||
bne $jumpIfFalseLabel
|
||||
cpy #>$number
|
||||
bne $jumpIfFalseLabel
|
||||
""")
|
||||
bne $jumpIfFalseLabel""")
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
@@ -2382,18 +2434,21 @@ $repeatLabel lda $counterVar
|
||||
cmp ${asmVariableName(right)}
|
||||
bne $jumpIfFalseLabel
|
||||
cpy ${asmVariableName(right)}+1
|
||||
bne $jumpIfFalseLabel
|
||||
""")
|
||||
bne $jumpIfFalseLabel""")
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(right.identifier)
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne $jumpIfFalseLabel
|
||||
cpy #>$name
|
||||
bne $jumpIfFalseLabel
|
||||
""")
|
||||
if(right.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${right.position}")
|
||||
// assignmentAsmGen.assignAddressOf(target, name, right.arrayIndexExpr)
|
||||
} else {
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne $jumpIfFalseLabel
|
||||
cpy #>$name
|
||||
bne $jumpIfFalseLabel""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if(left.isSimple()) {
|
||||
@@ -2474,12 +2529,16 @@ $repeatLabel lda $counterVar
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(right.identifier)
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne +
|
||||
cpy #>$name
|
||||
beq $jumpIfFalseLabel
|
||||
+""")
|
||||
if(right.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${right.position}")
|
||||
} else {
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne +
|
||||
cpy #>$name
|
||||
beq $jumpIfFalseLabel
|
||||
+""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if(left.isSimple()) {
|
||||
@@ -2764,22 +2823,14 @@ $repeatLabel lda $counterVar
|
||||
+""")
|
||||
}
|
||||
|
||||
internal fun translateDirectMemReadExpressionToRegAorStack(expr: PtMemoryByte, pushResultOnEstack: Boolean) {
|
||||
internal fun translateDirectMemReadExpressionToRegA(expr: PtMemoryByte) {
|
||||
|
||||
fun assignViaExprEval() {
|
||||
assignExpressionToVariable(expr.address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (pushResultOnEstack) {
|
||||
out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
|
||||
} else {
|
||||
out(" lda (P8ZP_SCRATCH_W2)")
|
||||
}
|
||||
out(" lda (P8ZP_SCRATCH_W2)")
|
||||
} else {
|
||||
if (pushResultOnEstack) {
|
||||
out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y | dex | sta P8ESTACK_LO+1,x")
|
||||
} else {
|
||||
out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2787,22 +2838,14 @@ $repeatLabel lda $counterVar
|
||||
is PtNumber -> {
|
||||
val address = (expr.address as PtNumber).number.toInt()
|
||||
out(" lda ${address.toHex()}")
|
||||
if(pushResultOnEstack)
|
||||
out(" sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
// the identifier is a pointer variable, so read the value from the address in it
|
||||
loadByteFromPointerIntoA(expr.address as PtIdentifier)
|
||||
if(pushResultOnEstack)
|
||||
out(" sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
require(!options.useNewExprCode)
|
||||
val addrExpr = expr.address as PtBinaryExpression
|
||||
if(tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
|
||||
if(pushResultOnEstack)
|
||||
out(" sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
if(!tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
|
||||
assignViaExprEval()
|
||||
}
|
||||
}
|
||||
@@ -2821,8 +2864,12 @@ $repeatLabel lda $counterVar
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(left.identifier)
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
if(left.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${left.position}")
|
||||
} else {
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
@@ -2870,8 +2917,12 @@ $repeatLabel lda $counterVar
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(right.identifier)
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
if(right.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${right.position}")
|
||||
} else {
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
@@ -2998,6 +3049,18 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
internal fun pushFAC1() {
|
||||
out(" jsr floats.pushFAC1")
|
||||
}
|
||||
|
||||
internal fun popFAC1() {
|
||||
out(" clc | jsr floats.popFAC")
|
||||
}
|
||||
|
||||
internal fun popFAC2() {
|
||||
out(" sec | jsr floats.popFAC")
|
||||
}
|
||||
|
||||
internal fun needAsaveForExpr(arg: PtExpression): Boolean =
|
||||
arg !is PtNumber && arg !is PtIdentifier && (arg !is PtMemoryByte || !arg.isSimple())
|
||||
|
||||
@@ -3030,9 +3093,6 @@ $repeatLabel lda $counterVar
|
||||
* it's more consistent to only define these attributes on a Subroutine node.
|
||||
*/
|
||||
internal class SubroutineExtraAsmInfo {
|
||||
var usedRegsaveA = false
|
||||
var usedRegsaveX = false
|
||||
var usedRegsaveY = false
|
||||
var usedFloatEvalResultVar1 = false
|
||||
var usedFloatEvalResultVar2 = false
|
||||
|
||||
|
||||
@@ -15,21 +15,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
|
||||
var linesByFour = getLinesBy(lines, 4)
|
||||
|
||||
var mods = optimizeUselessStackByteWrites(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeIncDec(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeCmpSequence(linesByFour)
|
||||
var mods = optimizeIncDec(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
@@ -43,7 +29,21 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
|
||||
mods = optimizeJsrRtsAndOtherCombinations(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeUselessPushPopStack(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeUnneededTempvarInAdd(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
@@ -90,44 +90,6 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||
// all lines (that aren't empty or comments) in sliding windows of certain size
|
||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||
|
||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// when statement (on bytes) generates a sequence of:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// beq check_prog8_s72choice_32
|
||||
// lda $ce01,x
|
||||
// cmp #$21
|
||||
// beq check_prog8_s73choice_33
|
||||
// the repeated lda can be removed
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||
lines[1].value.trim().startsWith("cmp ") &&
|
||||
lines[2].value.trim().startsWith("beq ") &&
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
||||
// this is a lot harder for word values because the instruction sequence varies.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||
lines[1].value.trim()=="dex" &&
|
||||
lines[2].value.trim()=="inx" &&
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeSameAssignments(
|
||||
linesByFourteen: List<List<IndexedValue<String>>>,
|
||||
machine: IMachineDefinition,
|
||||
@@ -517,3 +479,63 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedVal
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
val mods = mutableListOf<Modification>()
|
||||
|
||||
fun optimize(register: Char, lines: List<IndexedValue<String>>) {
|
||||
if(lines[0].value.trimStart().startsWith("ph$register")) {
|
||||
if(lines[2].value.trimStart().startsWith("pl$register")) {
|
||||
val second = lines[1].value.trimStart().take(6).lowercase()
|
||||
if(register!in second
|
||||
&& !second.startsWith("jsr")
|
||||
&& !second.startsWith("pl")
|
||||
&& !second.startsWith("ph")) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
else if (lines[3].value.trimStart().startsWith("pl$register")) {
|
||||
val second = lines[1].value.trimStart().take(6).lowercase()
|
||||
val third = lines[2].value.trimStart().take(6).lowercase()
|
||||
if(register !in second && register !in third
|
||||
&& !second.startsWith("jsr") && !third.startsWith("jsr")
|
||||
&& !second.startsWith("pl") && !third.startsWith("pl")
|
||||
&& !second.startsWith("ph") && !third.startsWith("ph")) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (lines in linesByFour) {
|
||||
optimize('a', lines)
|
||||
optimize('x', lines)
|
||||
optimize('y', lines)
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeUnneededTempvarInAdd(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sequence: sta P8ZP_SCRATCH_XX / lda something / clc / adc P8ZP_SCRATCH_XX
|
||||
// this can be performed without the scratch variable: clc / adc something
|
||||
val mods = mutableListOf<Modification>()
|
||||
|
||||
for(lines in linesByFour) {
|
||||
val first = lines[0].value.trimStart()
|
||||
val second = lines[1].value.trimStart()
|
||||
val third = lines[2].value.trimStart()
|
||||
val fourth = lines[3].value.trimStart()
|
||||
if(first.startsWith("sta P8ZP_SCRATCH_") && second.startsWith("lda") && third.startsWith("clc") && fourth.startsWith("adc P8ZP_SCRATCH_") ) {
|
||||
if(fourth.substring(4)==first.substring(4)) {
|
||||
mods.add(Modification(lines[0].index, false, " clc"))
|
||||
mods.add(Modification(lines[1].index, false, " adc ${second.substring(3).trimStart()}"))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
||||
internal class AssemblyProgram(
|
||||
@@ -28,15 +22,20 @@ internal class AssemblyProgram(
|
||||
val assemblerCommand: List<String>
|
||||
|
||||
when (compTarget.name) {
|
||||
in setOf("c64", "c128", "cx16") -> {
|
||||
in setOf("c64", "c128", "cx16", "pet32") -> {
|
||||
// CBM machines .prg generation.
|
||||
|
||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
"-Wall", // "-Wno-strict-bool", "-Werror",
|
||||
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
|
||||
)
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
else
|
||||
command.add("-Wno-shadow")
|
||||
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
@@ -65,10 +64,15 @@ internal class AssemblyProgram(
|
||||
|
||||
// TODO are these options okay for atari?
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
"-Wall", // "-Werror", "-Wno-strict-bool"
|
||||
"--no-monitor"
|
||||
)
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
else
|
||||
command.add("-Wno-shadow")
|
||||
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
@@ -130,18 +134,3 @@ internal class AssemblyProgram(
|
||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
||||
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
|
||||
return com.github.michaelbull.result.runCatching {
|
||||
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").text
|
||||
}.mapError { NoSuchFileException(File(filename)) }
|
||||
} else {
|
||||
val sib = Path(source.origin).resolveSibling(filename)
|
||||
if (sib.isRegularFile())
|
||||
Ok(SourceCode.File(sib).text)
|
||||
else
|
||||
Ok(SourceCode.File(Path(filename)).text)
|
||||
}
|
||||
}
|
||||
@@ -10,41 +10,43 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val assignAsmGen: AssignmentAsmGen) {
|
||||
|
||||
internal fun translateFunctioncallExpression(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
|
||||
return translateFunctioncall(fcall, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
|
||||
internal fun translateFunctioncallExpression(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?): DataType? {
|
||||
return translateFunctioncall(fcall, discardResult = false, resultRegister = resultRegister)
|
||||
}
|
||||
|
||||
internal fun translateFunctioncallStatement(fcall: PtBuiltinFunctionCall) {
|
||||
translateFunctioncall(fcall, discardResult = true, resultToStack = false, resultRegister = null)
|
||||
translateFunctioncall(fcall, discardResult = true, resultRegister = null)
|
||||
}
|
||||
|
||||
private fun translateFunctioncall(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
|
||||
private fun translateFunctioncall(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultRegister: RegisterOrPair?): DataType? {
|
||||
if (discardResult && fcall.hasNoSideEffects)
|
||||
return null // can just ignore the whole function call altogether
|
||||
|
||||
if(discardResult && resultToStack)
|
||||
throw AssemblyError("cannot both discard the result AND put it onto stack")
|
||||
|
||||
val sscope = fcall.definingISub()
|
||||
|
||||
when (fcall.name) {
|
||||
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
|
||||
"lsb" -> funcLsb(fcall, resultToStack, resultRegister)
|
||||
"mkword" -> funcMkword(fcall, resultToStack, resultRegister)
|
||||
"abs" -> funcAbs(fcall, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, resultToStack, resultRegister, sscope)
|
||||
"sqrt16" -> funcSqrt16(fcall, resultToStack, resultRegister, sscope)
|
||||
"divmod" -> funcDivmod(fcall)
|
||||
"divmodw" -> funcDivmodW(fcall)
|
||||
"msb" -> funcMsb(fcall, resultRegister)
|
||||
"lsb" -> funcLsb(fcall, resultRegister)
|
||||
"mkword" -> funcMkword(fcall, resultRegister)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(fcall, resultRegister)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(fcall, resultRegister)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(fcall, resultRegister)
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, resultRegister, sscope)
|
||||
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
|
||||
"divmod__ubyte" -> funcDivmod(fcall)
|
||||
"divmod__uword" -> funcDivmodW(fcall)
|
||||
"rol" -> funcRol(fcall)
|
||||
"rol2" -> funcRol2(fcall)
|
||||
"ror" -> funcRor(fcall)
|
||||
"ror2" -> funcRor2(fcall)
|
||||
"setlsb" -> funcSetLsbMsb(fcall, false)
|
||||
"setmsb" -> funcSetLsbMsb(fcall, true)
|
||||
"sort" -> funcSort(fcall)
|
||||
"reverse" -> funcReverse(fcall)
|
||||
"memory" -> funcMemory(fcall, discardResult, resultToStack, resultRegister)
|
||||
"peekw" -> funcPeekW(fcall, resultToStack, resultRegister)
|
||||
"memory" -> funcMemory(fcall, discardResult, resultRegister)
|
||||
"peekw" -> funcPeekW(fcall, resultRegister)
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"pokemon" -> { /* meme function */ }
|
||||
@@ -68,18 +70,35 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.popCpuStack(DataType.UWORD, target, fcall.definingISub())
|
||||
}
|
||||
"rsave" -> funcRsave()
|
||||
"rsavex" -> funcRsaveX()
|
||||
"rrestore" -> funcRrestore()
|
||||
"rrestorex" -> funcRrestoreX()
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall)
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultToStack)
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall)
|
||||
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD)
|
||||
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
|
||||
}
|
||||
|
||||
return BuiltinFunctions.getValue(fcall.name).returnType
|
||||
}
|
||||
|
||||
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType) {
|
||||
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
|
||||
when (resultType) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A)
|
||||
asmgen.out(" tay | jsr math.multiply_bytes")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out(" jsr math.square")
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("optimized square only for integer types")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcDivmod(fcall: PtBuiltinFunctionCall) {
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
@@ -112,11 +131,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
sty $remainderVar+1""")
|
||||
}
|
||||
|
||||
private fun funcStringCompare(fcall: PtBuiltinFunctionCall, resultToStack: Boolean) {
|
||||
private fun funcStringCompare(fcall: PtBuiltinFunctionCall) {
|
||||
asmgen.assignWordOperandsToAYAndVar(fcall.args[0], fcall.args[1], "P8ZP_SCRATCH_W2")
|
||||
asmgen.out(" jsr prog8_lib.strcmp_mem")
|
||||
if(resultToStack)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
|
||||
private fun funcRsave() {
|
||||
@@ -139,13 +156,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
}
|
||||
|
||||
private fun funcRsaveX() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" phx")
|
||||
else
|
||||
asmgen.out(" txa | pha")
|
||||
}
|
||||
|
||||
private fun funcRrestore() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
@@ -163,13 +173,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
plp""")
|
||||
}
|
||||
|
||||
private fun funcRrestoreX() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" plx")
|
||||
else
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: PtBuiltinFunctionCall) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
@@ -180,7 +183,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.jsrfar
|
||||
jsr cx16.JSRFAR
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
@@ -250,7 +253,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMemory(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcMemory(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(discardResult)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val name = (fcall.args[0] as PtString).value
|
||||
@@ -260,22 +263,27 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
addressOf.add(slabname)
|
||||
addressOf.parent = fcall
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
|
||||
val target =
|
||||
if(resultToStack)
|
||||
AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, null, fcall.position)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
|
||||
val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
|
||||
val assign = AsmAssignment(src, target, program.memsizer, fcall.position)
|
||||
asmgen.translateNormalAssignment(assign, fcall.definingISub())
|
||||
}
|
||||
|
||||
private fun funcSqrt16(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
private fun funcSqrt(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
||||
else {
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
|
||||
when(fcall.args[0].type) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.out(" ldy #0 | jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +336,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
lda #$numElements
|
||||
jsr floats.func_reverse_f""")
|
||||
}
|
||||
in SplitWordArrayTypes -> TODO("split word reverse")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@@ -374,6 +383,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
jsr prog8_lib.func_sort_ub""")
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
|
||||
in SplitWordArrayTypes -> TODO("split word sort")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
} else
|
||||
@@ -408,6 +418,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.splitWords)
|
||||
TODO("ror2 split words ${what.position}")
|
||||
translateRolRorArrayArgs(what.variable, what, "ror2", 'w')
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||
}
|
||||
@@ -438,16 +450,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
} else {
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.A)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, true)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, false)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ ror ${'$'}ffff,x ; modified""")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
@@ -467,6 +476,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.splitWords)
|
||||
TODO("ror split words ${what.position}")
|
||||
translateRolRorArrayArgs(what.variable, what, "ror", 'w')
|
||||
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||
}
|
||||
@@ -509,6 +520,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.splitWords)
|
||||
TODO("rol2 split words ${what.position}")
|
||||
translateRolRorArrayArgs(what.variable, what, "rol2", 'w')
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||
}
|
||||
@@ -539,16 +552,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
} else {
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.A)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, true)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, false)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ rol ${'$'}ffff,x ; modified""")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
@@ -568,6 +578,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.splitWords)
|
||||
TODO("rol split words ${what.position}")
|
||||
translateRolRorArrayArgs(what.variable, what, "rol", 'w')
|
||||
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||
}
|
||||
@@ -583,6 +595,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun translateRolRorArrayArgs(arrayvar: PtIdentifier, indexer: PtArrayIndexer, operation: String, dt: Char) {
|
||||
if(indexer.splitWords)
|
||||
TODO("rol/ror split words access ${indexer.position}")
|
||||
if(arrayvar.type==DataType.UWORD) {
|
||||
if(dt!='b')
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
@@ -597,72 +611,135 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE)
|
||||
}
|
||||
|
||||
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
if(resultToStack) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_stack")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
private fun funcSetLsbMsb(fcall: PtBuiltinFunctionCall, msb: Boolean) {
|
||||
val target: AsmAssignTarget
|
||||
when(fcall.args[0]) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(fcall.args[0] as PtIdentifier) + if(msb) "+1" else ""
|
||||
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, variableAsmName = varname)
|
||||
}
|
||||
is PtNumber -> {
|
||||
val num = (fcall.args[0] as PtNumber).number + if(msb) 1 else 0
|
||||
val mem = PtMemoryByte(fcall.position)
|
||||
mem.add(PtNumber(DataType.UBYTE, num, fcall.position))
|
||||
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
val mem = PtMemoryByte(fcall.position)
|
||||
if(msb) {
|
||||
val address = PtBinaryExpression("+", DataType.UWORD, fcall.args[0].position)
|
||||
address.add(fcall.args[0])
|
||||
address.add(PtNumber(address.type, 1.0, fcall.args[0].position))
|
||||
mem.add(address)
|
||||
} else {
|
||||
mem.add(fcall.args[0])
|
||||
}
|
||||
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
val indexer = fcall.args[0] as PtArrayIndexer
|
||||
require(!indexer.usesPointerVariable)
|
||||
val elementSize: Int
|
||||
val msbAdd: Int
|
||||
if(indexer.splitWords) {
|
||||
val arrayVariable = indexer.variable
|
||||
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.ARRAY_UB, arrayVariable.position)
|
||||
indexer.children[0].parent = indexer
|
||||
elementSize = 1
|
||||
msbAdd = 0
|
||||
} else {
|
||||
elementSize = 2
|
||||
msbAdd = if(msb) 1 else 0
|
||||
}
|
||||
|
||||
// double the index because of word array (if not split), add one if msb (if not split)
|
||||
val constIndexNum = (indexer.index as? PtNumber)?.number
|
||||
if(constIndexNum!=null) {
|
||||
indexer.children[1] = PtNumber(indexer.index.type, constIndexNum*elementSize + msbAdd, indexer.position)
|
||||
indexer.children[1].parent = indexer
|
||||
} else {
|
||||
val multipliedIndex: PtExpression
|
||||
if(elementSize==1) {
|
||||
multipliedIndex = indexer.index
|
||||
} else {
|
||||
multipliedIndex = PtBinaryExpression("<<", indexer.index.type, indexer.position)
|
||||
multipliedIndex.add(indexer.index)
|
||||
multipliedIndex.add(PtNumber(DataType.UBYTE, 1.0, indexer.position))
|
||||
}
|
||||
if(msbAdd>0) {
|
||||
val msbIndex = PtBinaryExpression("+", indexer.index.type, indexer.position)
|
||||
msbIndex.add(multipliedIndex)
|
||||
msbIndex.add(PtNumber(DataType.UBYTE, msbAdd.toDouble(), indexer.position))
|
||||
indexer.children[1] = msbIndex
|
||||
msbIndex.parent = indexer
|
||||
} else {
|
||||
indexer.children[1] = multipliedIndex
|
||||
multipliedIndex.parent=indexer
|
||||
}
|
||||
}
|
||||
target = AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, array = indexer)
|
||||
}
|
||||
else -> throw AssemblyError("setlsb/setmsb on weird target ${fcall.args[0]}")
|
||||
}
|
||||
|
||||
if(fcall.args[1].asConstInteger() == 0) {
|
||||
assignAsmGen.assignConstantByte(target, 0)
|
||||
} else {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_into_A")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
|
||||
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.func_sign_w_into_A")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true)
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().type
|
||||
if(resultToStack) {
|
||||
when (dt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_stack")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A | ldy #0")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A | ldy #0")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_into_A | ldy #0")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes)
|
||||
when (dt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A | ldy #0")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A | ldy #0")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_into_A | ldy #0")
|
||||
in SplitWordArrayTypes -> TODO("split word any/all")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes)
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
if(resultToStack) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" ldy #0")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.abs_b_stack")
|
||||
DataType.UWORD -> {}
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_stack")
|
||||
else -> throw AssemblyError("weird type")
|
||||
when (dt) {
|
||||
DataType.BYTE -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false)
|
||||
}
|
||||
} else {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" ldy #0")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.abs_b_into_AY")
|
||||
DataType.UWORD -> {}
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
else -> throw AssemblyError("weird type")
|
||||
DataType.WORD -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, scope, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, scope, asmgen), RegisterOrPair.AY)
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.func_abs_f_into_FAC1")
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, fcall.position, scope, asmgen))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
asmgen.assignRegister(RegisterOrPair.A, AsmAssignTarget.fromRegisters(resultRegister?:RegisterOrPair.A, false, fcall.position, scope, asmgen))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.assignRegister(RegisterOrPair.AY, AsmAssignTarget.fromRegisters(resultRegister?:RegisterOrPair.AY, false, fcall.position, scope, asmgen))
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,7 +755,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val varname = asmgen.asmVariableName(addrExpr)
|
||||
if(asmgen.isZpVar(addrExpr)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out("""
|
||||
@@ -694,29 +770,24 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
iny
|
||||
sta ($varname),y""")
|
||||
}
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
return
|
||||
}
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
require(!asmgen.options.useNewExprCode)
|
||||
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
|
||||
val pointer = result?.first as? PtIdentifier
|
||||
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
|
||||
// can do ZP,Y indexing
|
||||
val varname = asmgen.asmVariableName(pointer)
|
||||
val scope = fcall.definingISub()!!
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
|
||||
asmgen.saveRegisterLocal(CpuRegister.Y, scope)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.Y)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
asmgen.out("""
|
||||
sta ($varname),y
|
||||
txa
|
||||
iny
|
||||
sta ($varname),y""")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -728,7 +799,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr prog8_lib.func_pokew")
|
||||
}
|
||||
|
||||
private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
fun fallback() {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out(" jsr prog8_lib.func_peekw")
|
||||
@@ -752,16 +823,15 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($varname),y
|
||||
pha
|
||||
tax
|
||||
iny
|
||||
lda ($varname),y
|
||||
tay
|
||||
pla""")
|
||||
txa""")
|
||||
}
|
||||
} else fallback()
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
require(!asmgen.options.useNewExprCode)
|
||||
val result = asmgen.pointerViaIndexRegisterPossible(addrExpr)
|
||||
val pointer = result?.first as? PtIdentifier
|
||||
if(result!=null && pointer!=null && asmgen.isZpVar(pointer)) {
|
||||
@@ -770,87 +840,207 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
|
||||
asmgen.out("""
|
||||
lda ($varname),y
|
||||
pha
|
||||
tax
|
||||
iny
|
||||
lda ($varname),y
|
||||
tay
|
||||
pla""")
|
||||
txa""")
|
||||
} else fallback()
|
||||
}
|
||||
else -> fallback()
|
||||
}
|
||||
|
||||
if(resultToStack){
|
||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
} else {
|
||||
when(resultRegister ?: RegisterOrPair.AY) {
|
||||
RegisterOrPair.AY -> {}
|
||||
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
|
||||
RegisterOrPair.XY -> asmgen.out(" tax")
|
||||
in Cx16VirtualRegisters -> asmgen.out(
|
||||
" sta cx16.${
|
||||
resultRegister.toString().lowercase()
|
||||
} | sty cx16.${resultRegister.toString().lowercase()}+1")
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
when(resultRegister ?: RegisterOrPair.AY) {
|
||||
RegisterOrPair.AY -> {}
|
||||
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
|
||||
RegisterOrPair.XY -> asmgen.out(" tax")
|
||||
in Cx16VirtualRegisters -> asmgen.out(
|
||||
" sta cx16.${
|
||||
resultRegister.toString().lowercase()
|
||||
} | sty cx16.${resultRegister.toString().lowercase()}+1")
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMkword(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(resultToStack) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
asmgen.out(" sta P8ESTACK_LO,x | pla | sta P8ESTACK_HI,x | dex")
|
||||
} else {
|
||||
val reg = resultRegister ?: RegisterOrPair.AY
|
||||
var needAsave = asmgen.needAsaveForExpr(fcall.args[0])
|
||||
if(!needAsave) {
|
||||
val mr0 = fcall.args[0] as? PtMemoryByte
|
||||
val mr1 = fcall.args[1] as? PtMemoryByte
|
||||
if (mr0 != null)
|
||||
needAsave = mr0.address !is PtNumber
|
||||
if (mr1 != null)
|
||||
needAsave = needAsave or (mr1.address !is PtNumber)
|
||||
private fun funcClamp(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val signed = fcall.type in SignedDatatypes
|
||||
when(fcall.type) {
|
||||
in ByteDatatypes -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed)
|
||||
}
|
||||
when(reg) {
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
if(needAsave)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
|
||||
if(needAsave)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
if(needAsave)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
|
||||
if(needAsave)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
if(needAsave)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
|
||||
if(needAsave)
|
||||
asmgen.out(" pla")
|
||||
asmgen.out(" tax")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
asmgen.out(" sta cx16.${reg.toString().lowercase()}")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
|
||||
asmgen.out(" sta cx16.${reg.toString().lowercase()}+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid mkword target reg")
|
||||
in WordDatatypes -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
assignAsmGen.assignRegisterpairWord(targetReg, RegisterOrPair.AY)
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcMin(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val signed = fcall.type in SignedDatatypes
|
||||
if(fcall.type in ByteDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
} else if(fcall.type in WordDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if(signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
+""")
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
} else {
|
||||
throw AssemblyError("min float not supported")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMax(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val signed = fcall.type in SignedDatatypes
|
||||
if(fcall.type in ByteDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(signed) asmgen.out(" bpl +") else asmgen.out(" bcs +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
} else if(fcall.type in WordDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if(signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bmi +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
} else {
|
||||
throw AssemblyError("max float not supported")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMkword(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val reg = resultRegister ?: RegisterOrPair.AY
|
||||
var needAsave = asmgen.needAsaveForExpr(fcall.args[0])
|
||||
if(!needAsave) {
|
||||
val mr0 = fcall.args[0] as? PtMemoryByte
|
||||
val mr1 = fcall.args[1] as? PtMemoryByte
|
||||
if (mr0 != null)
|
||||
needAsave = mr0.address !is PtNumber
|
||||
if (mr1 != null)
|
||||
needAsave = needAsave or (mr1.address !is PtNumber)
|
||||
}
|
||||
when(reg) {
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
if(needAsave)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
|
||||
if(needAsave)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
if(needAsave)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
|
||||
if(needAsave)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
if(needAsave)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
|
||||
if(needAsave)
|
||||
asmgen.out(" pla")
|
||||
asmgen.out(" tax")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
asmgen.out(" sta cx16.${reg.toString().lowercase()}")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
|
||||
asmgen.out(" sta cx16.${reg.toString().lowercase()}+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid mkword target reg")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (arg.type !in WordDatatypes)
|
||||
throw AssemblyError("msb required word argument")
|
||||
@@ -858,53 +1048,44 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
if(resultToStack) {
|
||||
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | stz cx16.$regname+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | stz cx16.$regname+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
if(resultToStack) {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.out(" tya")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tya")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcLsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcLsb(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (arg.type !in WordDatatypes)
|
||||
throw AssemblyError("lsb required word argument")
|
||||
@@ -913,54 +1094,60 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
if(resultToStack) {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = resultRegister.name.lowercase()
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
if(resultToStack) {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
|
||||
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.out(" tay | pla | cpy #0")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
|
||||
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tay | cpy #0")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" ldy #0 | cmp #0")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
|
||||
asmgen.out(" ldx #0 | cmp #0")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
|
||||
asmgen.out(" ldy #0 | cpx #0")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.assignExpressionToRegister(arg, resultRegister)
|
||||
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
zero.parent=fcall
|
||||
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
|
||||
asmgen.out(" lda cx16.r0L")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,929 +0,0 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ExpressionsAsmGen(private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val allocator: VariableAllocator) {
|
||||
|
||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||
internal fun translateExpression(expression: PtExpression) {
|
||||
if (this.asmgen.options.slowCodegenWarnings) {
|
||||
asmgen.errors.warn("slow stack evaluation used for expression", expression.position)
|
||||
}
|
||||
translateExpressionInternal(expression)
|
||||
}
|
||||
|
||||
|
||||
// the rest of the methods are all PRIVATE
|
||||
|
||||
|
||||
private fun translateExpressionInternal(expression: PtExpression) {
|
||||
|
||||
when(expression) {
|
||||
is PtPrefix -> translateExpression(expression)
|
||||
is PtBinaryExpression -> translateExpression(expression)
|
||||
is PtArrayIndexer -> translateExpression(expression)
|
||||
is PtTypeCast -> translateExpression(expression)
|
||||
is PtAddressOf -> translateExpression(expression)
|
||||
is PtMemoryByte -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||
is PtNumber -> translateExpression(expression)
|
||||
is PtIdentifier -> translateExpression(expression)
|
||||
is PtFunctionCall -> translateFunctionCallResultOntoStack(expression)
|
||||
is PtBuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
|
||||
is PtContainmentCheck -> translateContainmentCheck(expression)
|
||||
is PtArray, is PtString -> throw AssemblyError("string/array literal value assignment should have been replaced by a variable")
|
||||
is PtRange -> throw AssemblyError("range expression should have been changed into array values")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
asmgen.saveXbeforeCall(call)
|
||||
asmgen.translateFunctionCall(call)
|
||||
if(sub.regXasResult()) {
|
||||
// store the return value in X somewhere that we can access again below
|
||||
asmgen.out(" stx P8ZP_SCRATCH_REG")
|
||||
}
|
||||
asmgen.restoreXafterCall(call)
|
||||
|
||||
val returns: List<Pair<RegisterOrStatusflag, DataType>> = sub.returnsWhatWhere()
|
||||
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)
|
||||
if (reg.registerOrPair != null) {
|
||||
when (reg.registerOrPair!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
|
||||
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
|
||||
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
|
||||
RegisterOrPair.R0,
|
||||
RegisterOrPair.R1,
|
||||
RegisterOrPair.R2,
|
||||
RegisterOrPair.R3,
|
||||
RegisterOrPair.R4,
|
||||
RegisterOrPair.R5,
|
||||
RegisterOrPair.R6,
|
||||
RegisterOrPair.R7,
|
||||
RegisterOrPair.R8,
|
||||
RegisterOrPair.R9,
|
||||
RegisterOrPair.R10,
|
||||
RegisterOrPair.R11,
|
||||
RegisterOrPair.R12,
|
||||
RegisterOrPair.R13,
|
||||
RegisterOrPair.R14,
|
||||
RegisterOrPair.R15 -> {
|
||||
asmgen.out(
|
||||
"""
|
||||
lda cx16.${reg.registerOrPair.toString().lowercase()}
|
||||
sta P8ESTACK_LO,x
|
||||
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
""")
|
||||
}
|
||||
}
|
||||
} else when(reg.statusflag) {
|
||||
Statusflag.Pc -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
rol a
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
Statusflag.Pz -> {
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
Statusflag.Pv -> {
|
||||
asmgen.out("""
|
||||
bvs +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
Statusflag.Pn -> {
|
||||
asmgen.out("""
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(typecast: PtTypeCast) {
|
||||
translateExpressionInternal(typecast.value)
|
||||
when(typecast.value.type) {
|
||||
DataType.UBYTE, DataType.BOOL -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_ub2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when(typecast.type) {
|
||||
DataType.BYTE, DataType.UBYTE -> {}
|
||||
DataType.WORD, DataType.UWORD -> {}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when(typecast.type) {
|
||||
DataType.BYTE, DataType.UBYTE -> {}
|
||||
DataType.WORD, DataType.UWORD -> {}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
|
||||
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
|
||||
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
|
||||
DataType.WORD -> asmgen.out(" jsr floats.stack_float2w")
|
||||
DataType.FLOAT -> {}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
|
||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PtAddressOf) {
|
||||
val name = asmgen.asmVariableName(expr.identifier)
|
||||
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PtNumber) {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||
lda #<${expr.number.toHex()}
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>${expr.number.toHex()}
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
""")
|
||||
DataType.FLOAT -> {
|
||||
val floatConst = allocator.getFloatAsmConst(expr.number)
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$varname | ldy #>$varname| jsr floats.push_float")
|
||||
}
|
||||
in IterableDatatypes -> {
|
||||
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
else -> throw AssemblyError("stack push weird variable type $expr")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PtBinaryExpression) {
|
||||
require(!asmgen.options.useNewExprCode)
|
||||
|
||||
// Uses evalstack to evaluate the given expression. THIS IS SLOW AND SHOULD BE AVOIDED!
|
||||
if(translateSomewhatOptimized(expr.left, expr.operator, expr.right))
|
||||
return
|
||||
|
||||
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) {
|
||||
val leftVal = left.asConstInteger()
|
||||
val rightVal = right.asConstInteger()
|
||||
if (leftVal!=null && leftVal in -4..4) {
|
||||
translateExpressionInternal(right)
|
||||
if(rightDt in ByteDatatypes) {
|
||||
val incdec = if(leftVal<0) "dec" else "inc"
|
||||
repeat(leftVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(leftVal<0) {
|
||||
repeat(leftVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(leftVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
else if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpressionInternal(left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "dec" else "inc"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(rightVal<0) {
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(rightVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = right.asConstInteger()
|
||||
if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpressionInternal(left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "inc" else "dec"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(rightVal>0) {
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(rightVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
val amount = right.asConstInteger()
|
||||
if(amount!=null) {
|
||||
translateExpressionInternal(left)
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||
repeat(amount) { asmgen.out(" lsr a") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
|
||||
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(amount>=16) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
|
||||
return true
|
||||
}
|
||||
var amountLeft = amount
|
||||
while (amountLeft >= 7) {
|
||||
asmgen.out(" jsr math.shift_right_uw_7")
|
||||
amountLeft -= 7
|
||||
}
|
||||
if (amountLeft in 0..2)
|
||||
repeat(amountLeft) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_uw_$amountLeft")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount>=16) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi +
|
||||
lda #0
|
||||
sta P8ESTACK_LO+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
beq ++
|
||||
+ lda #255
|
||||
sta P8ESTACK_LO+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
+""")
|
||||
return true
|
||||
}
|
||||
var amountLeft = amount
|
||||
while (amountLeft >= 7) {
|
||||
asmgen.out(" jsr math.shift_right_w_7")
|
||||
amountLeft -= 7
|
||||
}
|
||||
if (amountLeft in 0..2)
|
||||
repeat(amountLeft) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w_$amountLeft")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
"<<" -> {
|
||||
val amount = right.asConstInteger()
|
||||
if(amount!=null) {
|
||||
translateExpressionInternal(left)
|
||||
if (leftDt in ByteDatatypes) {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||
repeat(amount) { asmgen.out(" asl a") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
var amountLeft = amount
|
||||
while (amountLeft >= 7) {
|
||||
asmgen.out(" jsr math.shift_left_w_7")
|
||||
amountLeft -= 7
|
||||
}
|
||||
if (amountLeft in 0..2)
|
||||
repeat(amountLeft) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_left_w_$amountLeft")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val leftVar = left as? PtIdentifier
|
||||
val rightVar = right as? PtIdentifier
|
||||
if(leftVar!=null && rightVar!=null && leftVar==rightVar) {
|
||||
translateSquared(leftVar, leftDt)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val value = right as? PtNumber
|
||||
if(value!=null) {
|
||||
if(rightDt in IntegerDatatypes) {
|
||||
val amount = value.number.toInt()
|
||||
if(amount==2) {
|
||||
// optimize x*2 common case
|
||||
translateExpressionInternal(left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
asmgen.out(" asl P8ESTACK_LO+1,x")
|
||||
} else {
|
||||
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
|
||||
}
|
||||
return true
|
||||
}
|
||||
when(rightDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(amount in asmgen.optimizedByteMultiplications) {
|
||||
translateExpressionInternal(left)
|
||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||
return true
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(amount in asmgen.optimizedByteMultiplications) {
|
||||
translateExpressionInternal(left)
|
||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||
return true
|
||||
}
|
||||
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
|
||||
translateExpressionInternal(left)
|
||||
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
|
||||
return true
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(amount in asmgen.optimizedWordMultiplications) {
|
||||
translateExpressionInternal(left)
|
||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||
return true
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount in asmgen.optimizedWordMultiplications) {
|
||||
translateExpressionInternal(left)
|
||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||
return true
|
||||
}
|
||||
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
|
||||
translateExpressionInternal(left)
|
||||
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
|
||||
return true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = right.asConstInteger()
|
||||
if(rightVal!=null && rightVal==2) {
|
||||
translateExpressionInternal(left)
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bpl +
|
||||
inc P8ESTACK_LO+1,x
|
||||
lda P8ESTACK_LO+1,x
|
||||
+ asl a
|
||||
ror P8ESTACK_LO+1,x""")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signed divide using shift needs adjusting of negative value to get correct rounding towards zero
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_HI+1,x
|
||||
bpl ++
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+ lda P8ESTACK_HI+1,x
|
||||
+ asl a
|
||||
ror P8ESTACK_HI+1,x
|
||||
ror P8ESTACK_LO+1,x""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun translateComparisonWithZero(expr: PtExpression, dt: DataType, operator: String) {
|
||||
if(expr.isSimple()) {
|
||||
if(operator=="!=") {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.A, dt == DataType.BYTE)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
return
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, dt == DataType.WORD)
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
return
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.FAC1, true)
|
||||
asmgen.out("""
|
||||
jsr floats.SIGN
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
/* operator == is not worth it to special case, the code is mostly larger */
|
||||
}
|
||||
translateExpressionInternal(expr)
|
||||
when(operator) {
|
||||
"==" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.equal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.notequalzero_b")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.notequalzero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.notequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"<" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(PtNumber.fromBoolean(false, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.less_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
">" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.greaterzero_ub")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterzero_sb")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.greaterzero_uw")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.greater_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"<=" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzero_sb")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
">=" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(PtNumber.fromBoolean(true, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.greaterequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateSquared(variable: PtIdentifier, dt: DataType) {
|
||||
val asmVar = asmgen.asmVariableName(variable)
|
||||
when(dt) {
|
||||
DataType.BYTE, DataType.UBYTE -> {
|
||||
asmgen.out(" lda $asmVar")
|
||||
asmgen.signExtendAYlsb(dt)
|
||||
asmgen.out(" jsr math.square")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out(" lda $asmVar | ldy $asmVar+1 | jsr math.square")
|
||||
}
|
||||
else -> throw AssemblyError("require integer dt for square")
|
||||
}
|
||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PtPrefix) {
|
||||
translateExpressionInternal(expr.value)
|
||||
when(expr.operator) {
|
||||
"+" -> {}
|
||||
"-" -> {
|
||||
when(expr.type) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"~" -> {
|
||||
when(expr.type) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
eor #255
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(arrayExpr: PtArrayIndexer) {
|
||||
val elementDt = arrayExpr.type
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.variable)
|
||||
|
||||
if(arrayExpr.variable.type==DataType.UWORD) {
|
||||
// indexing a pointer var instead of a real array or string
|
||||
if(elementDt !in ByteDatatypes)
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
if(arrayExpr.index.type != DataType.UBYTE)
|
||||
throw AssemblyError("non-array var indexing requires bytes index")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
if(asmgen.isZpVar(arrayExpr.variable)) {
|
||||
asmgen.out(" lda ($arrayVarName),y")
|
||||
} else {
|
||||
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
return
|
||||
}
|
||||
|
||||
val constIndexNum = arrayExpr.index.asConstInteger()
|
||||
if(constIndexNum!=null) {
|
||||
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue) | jsr floats.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird element type")
|
||||
}
|
||||
} else {
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>$arrayVarName
|
||||
clc
|
||||
adc #<$arrayVarName
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.push_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
||||
when(operator) {
|
||||
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
|
||||
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||
"%" -> {
|
||||
if(types==DataType.BYTE)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||
}
|
||||
"+" -> asmgen.out("""
|
||||
lda P8ESTACK_LO+2,x
|
||||
clc
|
||||
adc P8ESTACK_LO+1,x
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
"-" -> asmgen.out("""
|
||||
lda P8ESTACK_LO+2,x
|
||||
sec
|
||||
sbc P8ESTACK_LO+1,x
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
|
||||
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
|
||||
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
||||
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
||||
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
||||
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
|
||||
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
|
||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
|
||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
|
||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateBinaryOperatorWords(operator: String, dt: DataType) {
|
||||
when(operator) {
|
||||
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
|
||||
"/" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
|
||||
"%" -> {
|
||||
if(dt==DataType.WORD)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out(" jsr prog8_lib.remainder_uw")
|
||||
}
|
||||
"+" -> asmgen.out(" jsr prog8_lib.add_w")
|
||||
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
|
||||
"<<" -> asmgen.out(" jsr math.shift_left_w")
|
||||
">>" -> {
|
||||
if(dt==DataType.UWORD)
|
||||
asmgen.out(" jsr math.shift_right_uw")
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w")
|
||||
}
|
||||
"<" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
|
||||
">" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
|
||||
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
||||
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
|
||||
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
|
||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
|
||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
|
||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateBinaryOperatorFloats(operator: String) {
|
||||
when(operator) {
|
||||
"*" -> asmgen.out(" jsr floats.mul_f")
|
||||
"/" -> asmgen.out(" jsr floats.div_f")
|
||||
"+" -> asmgen.out(" jsr floats.add_f")
|
||||
"-" -> asmgen.out(" jsr floats.sub_f")
|
||||
"<" -> asmgen.out(" jsr floats.less_f")
|
||||
">" -> asmgen.out(" jsr floats.greater_f")
|
||||
"<=" -> asmgen.out(" jsr floats.lesseq_f")
|
||||
">=" -> asmgen.out(" jsr floats.greatereq_f")
|
||||
"==" -> asmgen.out(" jsr floats.equal_f")
|
||||
"!=" -> asmgen.out(" jsr floats.notequal_f")
|
||||
"%", "<<", ">>", "&", "^", "|" -> throw AssemblyError("requires integer datatype")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateCompareStrings(s1: PtExpression, operator: String, s2: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD)
|
||||
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD)
|
||||
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
|
||||
compareStringsProcessResultInA(operator)
|
||||
}
|
||||
|
||||
private fun compareStringsProcessResultInA(operator: String) {
|
||||
when(operator) {
|
||||
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
|
||||
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
|
||||
"<=" -> asmgen.out("""
|
||||
bpl +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
">=" -> asmgen.out("""
|
||||
bmi +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
"<" -> asmgen.out("""
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
">" -> asmgen.out("""
|
||||
bpl +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
}
|
||||
asmgen.out(" dex")
|
||||
}
|
||||
}
|
||||
@@ -6,26 +6,6 @@ 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 -> {
|
||||
|
||||
@@ -5,9 +5,10 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val zeropage: Zeropage) {
|
||||
internal class ForLoopsAsmGen(
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val zeropage: Zeropage
|
||||
) {
|
||||
|
||||
internal fun translate(stmt: PtForLoop) {
|
||||
val iterableDt = stmt.iterable.type
|
||||
@@ -51,7 +52,35 @@ internal class ForLoopsAsmGen(private val program: PtProgram,
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt))
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
|
||||
// pre-check for end already reached
|
||||
if(iterableDt==DataType.ARRAY_B) {
|
||||
asmgen.out(" sta $modifiedLabel+1")
|
||||
if(stepsize<0)
|
||||
asmgen.out("""
|
||||
clc
|
||||
sbc $varname
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bpl $endLabel""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc $varname
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bmi $endLabel""")
|
||||
} else {
|
||||
if(stepsize<0)
|
||||
asmgen.out("""
|
||||
cmp $varname
|
||||
beq +
|
||||
bcs $endLabel
|
||||
+""")
|
||||
else
|
||||
asmgen.out(" cmp $varname | bcc $endLabel")
|
||||
asmgen.out(" sta $modifiedLabel+1")
|
||||
}
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
@@ -69,7 +98,35 @@ $modifiedLabel cmp #0 ; modified
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt))
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
|
||||
// pre-check for end already reached
|
||||
if(iterableDt==DataType.ARRAY_B) {
|
||||
asmgen.out(" sta $modifiedLabel+1")
|
||||
if(stepsize<0)
|
||||
asmgen.out("""
|
||||
clc
|
||||
sbc $varname
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bpl $endLabel""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc $varname
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bmi $endLabel""")
|
||||
} else {
|
||||
if(stepsize<0)
|
||||
asmgen.out("""
|
||||
cmp $varname
|
||||
beq +
|
||||
bcs $endLabel
|
||||
+""")
|
||||
else
|
||||
asmgen.out(" cmp $varname | bcc $endLabel")
|
||||
asmgen.out(" sta $modifiedLabel+1")
|
||||
}
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
if(stepsize>0) {
|
||||
@@ -100,8 +157,9 @@ $modifiedLabel cmp #0 ; modified
|
||||
|
||||
stepsize == 1 || stepsize == -1 -> {
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvar(stmt, range)
|
||||
assignLoopvarWord(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
@@ -134,8 +192,9 @@ $modifiedLabel2 cmp #0 ; modified
|
||||
|
||||
// (u)words, step >= 2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvar(stmt, range)
|
||||
assignLoopvarWord(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
@@ -182,41 +241,25 @@ $endLabel""")
|
||||
|
||||
// (u)words, step <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvar(stmt, range)
|
||||
assignLoopvarWord(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
|
||||
if(iterableDt==DataType.ARRAY_UW) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
sec
|
||||
sbc #<${stepsize.absoluteValue}
|
||||
sta $varname
|
||||
tax
|
||||
lda $varname+1
|
||||
sbc #>${stepsize.absoluteValue}
|
||||
sta $varname+1
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bcc $endLabel
|
||||
bne $loopLabel
|
||||
lda $varname
|
||||
$modifiedLabel2 cmp #0 ; modified
|
||||
bcs $loopLabel
|
||||
$endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
sec
|
||||
sbc #<${stepsize.absoluteValue}
|
||||
sta $varname
|
||||
pha
|
||||
lda $varname+1
|
||||
sbc #>${stepsize.absoluteValue}
|
||||
sta $varname+1
|
||||
pla
|
||||
txa
|
||||
$modifiedLabel2 cmp #0 ; modified
|
||||
lda $varname+1
|
||||
$modifiedLabel sbc #0 ; modified
|
||||
@@ -224,7 +267,6 @@ $modifiedLabel sbc #0 ; modified
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,6 +276,56 @@ $endLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
|
||||
// pre-check for end already reached.
|
||||
// 'to' is in AY, do NOT clobber this!
|
||||
if(iterableDt==DataType.ARRAY_W) {
|
||||
if(stepsize<0)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W2 ; to
|
||||
sty P8ZP_SCRATCH_W2+1 ; to
|
||||
lda $fromVar
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
lda $fromVar+1
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bmi $endLabel
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
cmp $fromVar
|
||||
tya
|
||||
sbc $fromVar+1
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bmi $endLabel
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
} else {
|
||||
if(stepsize<0)
|
||||
asmgen.out("""
|
||||
cpy $fromVar+1
|
||||
beq +
|
||||
bcc ++
|
||||
bcs $endLabel
|
||||
+ cmp $fromVar
|
||||
bcc +
|
||||
beq +
|
||||
bne $endLabel
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
cpy $fromVar+1
|
||||
bcc $endLabel
|
||||
bne +
|
||||
cmp $fromVar
|
||||
bcc $endLabel
|
||||
+""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
@@ -291,7 +383,7 @@ $loopLabel sty $indexVar
|
||||
// allocate index var on ZP if possible
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
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") }
|
||||
)
|
||||
} else {
|
||||
@@ -332,7 +424,46 @@ $loopLabel sty $indexVar
|
||||
// allocate index var on ZP if possible
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
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") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT -> {
|
||||
numElements!!
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
lda ${iterableName}_lsb,y
|
||||
sta $loopvarName
|
||||
lda ${iterableName}_msb,y
|
||||
sta $loopvarName+1""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements<=255u) {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
cpy #$numElements
|
||||
beq $endLabel
|
||||
bne $loopLabel""")
|
||||
} else {
|
||||
// length is 256
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address,_,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
@@ -590,7 +721,7 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
|
||||
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
|
||||
asmgen.assignExpressionToVariable(
|
||||
range.from,
|
||||
asmgen.asmVariableName(stmt.variable),
|
||||
|
||||
@@ -11,42 +11,10 @@ import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||
saveXbeforeCall(stmt)
|
||||
translateFunctionCall(stmt)
|
||||
restoreXafterCall(stmt)
|
||||
// just ignore any result values from the function call.
|
||||
}
|
||||
|
||||
internal fun saveXbeforeCall(stmt: PtFunctionCall) {
|
||||
val symbol = asmgen.symbolTable.lookup(stmt.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
if(sub.shouldSaveX()) {
|
||||
if(sub is PtAsmSub) {
|
||||
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
|
||||
if (regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
|
||||
} else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreXafterCall(stmt: PtFunctionCall) {
|
||||
val symbol = asmgen.symbolTable.lookup(stmt.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
if(sub.shouldSaveX()) {
|
||||
if(sub is PtAsmSub) {
|
||||
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
|
||||
if (regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
} else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
|
||||
(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)
|
||||
@@ -63,8 +31,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
|
||||
if(sub is PtAsmSub) {
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline && asmgen.options.optimize) {
|
||||
// inline the subroutine.
|
||||
if (sub.inline) {
|
||||
// inline the subroutine. (regardless of optimization settings!)
|
||||
// we do this by copying the subroutine's statements at the call site.
|
||||
// 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)
|
||||
@@ -164,7 +132,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(requiredDt!=value.type)
|
||||
throw AssemblyError("for statusflag, byte value is required")
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// this boolean param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
when(value) {
|
||||
is PtNumber -> {
|
||||
@@ -173,6 +141,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
// note: cannot use X register here to store A because it might be used for other arguments
|
||||
asmgen.out("""
|
||||
pha
|
||||
clc
|
||||
@@ -183,12 +152,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+""")
|
||||
asmgen.out(" ror a")
|
||||
}
|
||||
}
|
||||
} else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
|
||||
@@ -13,7 +13,6 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memory
|
||||
val targetArrayIdx = stmt.target.array
|
||||
val scope = stmt.definingISub()
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
@@ -65,20 +64,59 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.variable)
|
||||
val elementDt = targetArrayIdx.type
|
||||
val constIndex = targetArrayIdx.index.asConstInteger()
|
||||
if(targetArrayIdx.splitWords) {
|
||||
if(constIndex!=null) {
|
||||
if(incr)
|
||||
asmgen.out(" inc ${asmArrayvarname}_lsb+$constIndex | bne + | inc ${asmArrayvarname}_msb+$constIndex |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda ${asmArrayvarname}_lsb+$constIndex
|
||||
bne +
|
||||
dec ${asmArrayvarname}_msb+$constIndex
|
||||
+ dec ${asmArrayvarname}_lsb+$constIndex""")
|
||||
} else {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.X)
|
||||
if(incr)
|
||||
asmgen.out(" inc ${asmArrayvarname}_lsb,x | bne + | inc ${asmArrayvarname}_msb,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda ${asmArrayvarname}_lsb,x
|
||||
bne +
|
||||
dec ${asmArrayvarname}_msb,x
|
||||
+ dec ${asmArrayvarname}_lsb,x""")
|
||||
}
|
||||
return
|
||||
}
|
||||
if(constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in ByteDatatypes -> {
|
||||
if(targetArrayIdx.usesPointerVariable) {
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname
|
||||
clc
|
||||
adc #$indexValue
|
||||
sta (+) +1
|
||||
lda $asmArrayvarname+1
|
||||
adc #0
|
||||
sta (+) +2""")
|
||||
if(incr)
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
else
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
} else {
|
||||
asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
+ dec $asmArrayvarname+$indexValue
|
||||
""")
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
+ dec $asmArrayvarname+$indexValue""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<($asmArrayvarname+$indexValue) | ldy #>($asmArrayvarname+$indexValue)")
|
||||
@@ -89,36 +127,47 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
else
|
||||
{
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
|
||||
asmgen.out(" tax")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.X)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
if(targetArrayIdx.usesPointerVariable) {
|
||||
asmgen.out("""
|
||||
txa
|
||||
clc
|
||||
adc $asmArrayvarname
|
||||
sta (+) +1
|
||||
lda $asmArrayvarname+1
|
||||
adc #0
|
||||
sta (+) +2""")
|
||||
if(incr)
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
else
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
} else {
|
||||
asmgen.out(if (incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname,x
|
||||
""")
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname,x""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.inc_var_f""")
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,9 @@ internal class ProgramAndVarsGen(
|
||||
internal fun generate() {
|
||||
header()
|
||||
val allBlocks = program.allBlocks()
|
||||
if(allBlocks.first().name != "main")
|
||||
throw AssemblyError("first block should be 'main'")
|
||||
|
||||
if(allBlocks.first().name != "p8_main" && allBlocks.first().name != "main")
|
||||
throw AssemblyError("first block should be 'main' or 'p8_main'")
|
||||
|
||||
if(errors.noErrors()) {
|
||||
program.allBlocks().forEach { block2asm(it) }
|
||||
@@ -74,8 +75,6 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
||||
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
||||
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
|
||||
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
|
||||
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
|
||||
asmgen.out(".endweak")
|
||||
|
||||
if(options.symbolDefs.isNotEmpty()) {
|
||||
@@ -104,15 +103,15 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("+\t.word 0")
|
||||
asmgen.out("prog8_entrypoint\t; assembly code starts here")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
}
|
||||
CbmPrgLauncherType.NONE -> {
|
||||
asmgen.out("; ---- program without basic sys call ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,8 +119,8 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; ---- atari xex program ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,24 +138,24 @@ internal class ProgramAndVarsGen(
|
||||
"cx16" -> {
|
||||
if(options.floats)
|
||||
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||
asmgen.out(" jsr main.start")
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
asmgen.out(" jsr p8_main.p8_start")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out(" jsr main.start | lda #31 | sta $01")
|
||||
asmgen.out(" jsr p8_main.p8_start | lda #31 | sta $01")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
else
|
||||
asmgen.out(" rts")
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out(" jsr main.start | lda #0 | sta ${"$"}ff00")
|
||||
asmgen.out(" jsr p8_main.p8_start | lda #0 | sta ${"$"}ff00")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
else
|
||||
asmgen.out(" rts")
|
||||
}
|
||||
else -> asmgen.jmp("main.start")
|
||||
else -> asmgen.jmp("p8_main.p8_start")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +174,8 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private fun footer() {
|
||||
asmgen.out("; bss sections")
|
||||
if(options.varsHigh) {
|
||||
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
|
||||
if(options.varsHighBank!=null) {
|
||||
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
|
||||
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
|
||||
}
|
||||
@@ -269,9 +269,7 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
internal fun translateAsmSubroutine(sub: PtAsmSub) {
|
||||
if(sub.inline) {
|
||||
if(options.optimize) {
|
||||
return
|
||||
}
|
||||
return // subroutine gets inlined at call site.
|
||||
}
|
||||
|
||||
asmgen.out("")
|
||||
@@ -311,7 +309,9 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
asmgen.out("${sub.name}\t$asmStartScope")
|
||||
|
||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
||||
val scope = symboltable.lookupOrElse(sub.scopedName) {
|
||||
throw AssemblyError("lookup ${sub.scopedName}")
|
||||
}
|
||||
require(scope.type==StNodeType.SUBROUTINE)
|
||||
val varsInSubroutine = getVars(scope)
|
||||
|
||||
@@ -331,7 +331,7 @@ internal class ProgramAndVarsGen(
|
||||
asmsubs2asm(sub.children)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock()!!.name=="main")
|
||||
if((sub.name=="start" || sub.name=="p8_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8_main"))
|
||||
entrypointInitialization()
|
||||
|
||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||
@@ -369,12 +369,6 @@ internal class ProgramAndVarsGen(
|
||||
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)
|
||||
@@ -434,12 +428,12 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("""
|
||||
lda #<${name}_init_value
|
||||
ldy #>${name}_init_value
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
lda #<${name}
|
||||
ldy #>${name}
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
lda #<$size
|
||||
ldy #>$size
|
||||
jsr sys.memcopy""")
|
||||
@@ -458,7 +452,6 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
asmgen.out("""+
|
||||
ldx #127 ; init estack ptr (half page)
|
||||
clv
|
||||
clc""")
|
||||
}
|
||||
@@ -480,8 +473,8 @@ internal class ProgramAndVarsGen(
|
||||
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
||||
for (variable in vars) {
|
||||
val scopedName = variable.key
|
||||
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(svar.onetimeInitializationStringValue!=null)
|
||||
val svar = symboltable.lookup(scopedName) as? StStaticVariable
|
||||
if(svar?.onetimeInitializationStringValue!=null)
|
||||
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
|
||||
}
|
||||
return result
|
||||
@@ -492,8 +485,8 @@ internal class ProgramAndVarsGen(
|
||||
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
||||
for (variable in vars) {
|
||||
val scopedName = variable.key
|
||||
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(svar.onetimeInitializationArrayValue!=null)
|
||||
val svar = symboltable.lookup(scopedName) as? StStaticVariable
|
||||
if(svar?.onetimeInitializationArrayValue!=null)
|
||||
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
|
||||
}
|
||||
return result
|
||||
@@ -543,6 +536,11 @@ internal class ProgramAndVarsGen(
|
||||
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 SplitWordArrayTypes -> {
|
||||
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
|
||||
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
|
||||
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
|
||||
asmgen.out("${variable.name}\t.fill $numbytes")
|
||||
@@ -627,6 +625,18 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .sint " + chunk.joinToString())
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UW_SPLIT -> {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
}
|
||||
DataType.ARRAY_W_SPLIT -> {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
val floatFills = array.map {
|
||||
@@ -686,7 +696,7 @@ internal class ProgramAndVarsGen(
|
||||
val number = it.number!!.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
DataType.ARRAY_UW -> array.map {
|
||||
DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
|
||||
if(it.number!=null) {
|
||||
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
@@ -718,11 +728,11 @@ internal class ProgramAndVarsGen(
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
DataType.ARRAY_UW -> array.map {
|
||||
DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
"$" + number.toString(16).padStart(4, '0')
|
||||
}
|
||||
DataType.ARRAY_W -> array.map {
|
||||
DataType.ARRAY_W, DataType.ARRAY_W_SPLIT -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if(number>=0)
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.ast.PtBinaryExpression
|
||||
import prog8.code.ast.PtExpression
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
|
||||
//
|
||||
// This contains codegen for stack-based evaluation of binary expressions.
|
||||
// It uses the CPU stack so depth is limited.
|
||||
// It is called "as a last resort" if the optimized codegen path is unable
|
||||
// to come up with a special case of the expression.
|
||||
//
|
||||
internal class AnyExprAsmGen(
|
||||
private val asmgen: AsmGen6502Internal
|
||||
) {
|
||||
fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
when(expr.type) {
|
||||
in ByteDatatypes -> {
|
||||
if(expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes)
|
||||
return assignByteBinExpr(expr, assign)
|
||||
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
require(expr.operator in ComparisonOperators)
|
||||
TODO("words operands comparison -> byte")
|
||||
}
|
||||
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
|
||||
require(expr.operator in ComparisonOperators)
|
||||
return assignFloatBinExpr(expr, assign)
|
||||
}
|
||||
TODO("weird expr operand types")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
"both operands must be words"
|
||||
}
|
||||
return assignWordBinExpr(expr)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
|
||||
"both operands must be floats"
|
||||
}
|
||||
return assignFloatBinExpr(expr, assign)
|
||||
}
|
||||
else -> throw AssemblyError("weird expression type in assignment")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignWordBinExpr(expr: PtBinaryExpression): Boolean {
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
TODO("word + at ${expr.position}")
|
||||
}
|
||||
"-" -> {
|
||||
TODO("word - at ${expr.position}")
|
||||
}
|
||||
"*" -> {
|
||||
TODO("word * at ${expr.position}")
|
||||
}
|
||||
"/" -> {
|
||||
TODO("word / at ${expr.position}")
|
||||
}
|
||||
"<<" -> {
|
||||
TODO("word << at ${expr.position}")
|
||||
}
|
||||
">>" -> {
|
||||
TODO("word >> at ${expr.position}")
|
||||
}
|
||||
"%" -> {
|
||||
TODO("word % at ${expr.position}")
|
||||
}
|
||||
"&", "and" -> {
|
||||
TODO("word and at ${expr.position}")
|
||||
}
|
||||
"|", "or" -> {
|
||||
TODO("word or at ${expr.position}")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
TODO("word xor at ${expr.position}")
|
||||
}
|
||||
"==" -> {
|
||||
TODO("word == at ${expr.position}")
|
||||
}
|
||||
"!=" -> {
|
||||
TODO("word != at ${expr.position}")
|
||||
}
|
||||
"<" -> {
|
||||
TODO("word < at ${expr.position}")
|
||||
}
|
||||
"<=" -> {
|
||||
TODO("word <= at ${expr.position}")
|
||||
}
|
||||
">" -> {
|
||||
TODO("word > at ${expr.position}")
|
||||
}
|
||||
">=" -> {
|
||||
TODO("word >= at ${expr.position}")
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||
asmgen.out(" pla | clc | adc P8ZP_SCRATCH_B1")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"-" -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||
asmgen.out(" pla | sec | sbc P8ZP_SCRATCH_B1")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"*" -> {
|
||||
TODO("byte * at ${expr.position}")
|
||||
}
|
||||
"/" -> {
|
||||
TODO("byte / at ${expr.position}")
|
||||
}
|
||||
"<<" -> {
|
||||
TODO("byte << at ${expr.position}")
|
||||
}
|
||||
">>" -> {
|
||||
TODO("byte >> at ${expr.position}")
|
||||
}
|
||||
"%" -> {
|
||||
TODO("byte % at ${expr.position}")
|
||||
}
|
||||
"&", "and" -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||
asmgen.out(" pla | and P8ZP_SCRATCH_B1")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"|", "or" -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||
asmgen.out(" pla | ora P8ZP_SCRATCH_B1")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"^", "xor" -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
|
||||
asmgen.out(" pla | eor P8ZP_SCRATCH_B1")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"==" -> {
|
||||
TODO("byte == at ${expr.position}")
|
||||
}
|
||||
"!=" -> {
|
||||
TODO("byte != at ${expr.position}")
|
||||
}
|
||||
"<" -> {
|
||||
TODO("byte < at ${expr.position}")
|
||||
}
|
||||
"<=" -> {
|
||||
TODO("byte <= at ${expr.position}")
|
||||
}
|
||||
">" -> {
|
||||
TODO("byte > at ${expr.position}")
|
||||
}
|
||||
">=" -> {
|
||||
TODO("byte >= at ${expr.position}")
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FADDT")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"-" -> {
|
||||
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||
asmgen.out(" jsr floats.FSUBT")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"*" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FMULTT")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"/" -> {
|
||||
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||
asmgen.out(" jsr floats.FDIVT")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"==" -> {
|
||||
setupFloatComparisonFAC1vsVarAY(expr)
|
||||
asmgen.out(" jsr floats.var_fac1_equal_f")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"!=" -> {
|
||||
setupFloatComparisonFAC1vsVarAY(expr)
|
||||
asmgen.out(" jsr floats.var_fac1_notequal_f")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"<" -> {
|
||||
setupFloatComparisonFAC1vsVarAY(expr)
|
||||
asmgen.out(" jsr floats.var_fac1_less_f")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
">" -> {
|
||||
setupFloatComparisonFAC1vsVarAY(expr)
|
||||
asmgen.out(" jsr floats.var_fac1_greater_f")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"<=" -> {
|
||||
setupFloatComparisonFAC1vsVarAY(expr)
|
||||
asmgen.out(" jsr floats.var_fac1_lesseq_f")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
">=" -> {
|
||||
setupFloatComparisonFAC1vsVarAY(expr)
|
||||
asmgen.out(" jsr floats.var_fac1_greatereq_f")
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
else -> TODO("float expression operator ${expr.operator}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
|
||||
when(asmgen.options.compTarget.name) {
|
||||
C64Target.NAME -> {
|
||||
// c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK) otherwise the result will be corrupt on C64
|
||||
// this requires some more forced copying around of float values in certain cases
|
||||
if (right.isSimple()) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
|
||||
asmgen.pushFAC1()
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||
asmgen.popFAC2()
|
||||
}
|
||||
}
|
||||
Cx16Target.NAME -> {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||
if (!right.isSimple()) asmgen.pushFAC1()
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
|
||||
if (!right.isSimple()) asmgen.popFAC1()
|
||||
}
|
||||
else -> TODO("don't know how to evaluate float expression for selected compilation target")
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFloatComparisonFAC1vsVarAY(expr: PtBinaryExpression) {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
|
||||
if(!expr.right.isSimple()) asmgen.pushFAC1()
|
||||
asmgen.assignExpressionToVariable(expr.right, "floats.floats_temp_var", DataType.FLOAT)
|
||||
if(!expr.right.isSimple()) asmgen.popFAC1()
|
||||
asmgen.out(" lda #<floats.floats_temp_var | ldy #>floats.floats_temp_var")
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,7 @@ internal enum class TargetStorageKind {
|
||||
VARIABLE,
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
STACK
|
||||
REGISTER
|
||||
}
|
||||
|
||||
internal enum class SourceStorageKind {
|
||||
@@ -20,7 +19,6 @@ internal enum class SourceStorageKind {
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
STACK, // value is already present on stack
|
||||
EXPRESSION, // expression in ast-form, still to be evaluated
|
||||
}
|
||||
|
||||
@@ -115,12 +113,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
left is PtIdentifier && left.name==scopedName
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
left is PtArrayIndexer && left isSameAs array!!
|
||||
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
left isSameAs memory!!
|
||||
}
|
||||
TargetStorageKind.REGISTER, TargetStorageKind.STACK -> {
|
||||
TargetStorageKind.REGISTER -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -178,8 +176,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
}
|
||||
is PtFunctionCall -> {
|
||||
val symbol = asmgen.symbolTable.lookup(value.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
|
||||
val sub = symbol.astNode as IPtSubroutine
|
||||
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+761
-557
File diff suppressed because it is too large.
Load diff
@@ -45,6 +45,10 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
|
||||
warnings.add(text)
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
override fun report() {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package prog8tests.codegencpu6502
|
||||
|
||||
import io.kotest.assertions.throwables.shouldNotThrowAny
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.*
|
||||
@@ -40,9 +42,9 @@ class TestCodegen: FunSpec({
|
||||
// xx += cx16.r0
|
||||
// }
|
||||
//}
|
||||
val codegen = AsmGen6502()
|
||||
val codegen = AsmGen6502(prefixSymbols = false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
@@ -92,7 +94,7 @@ class TestCodegen: FunSpec({
|
||||
program.add(block)
|
||||
|
||||
// define the "cx16.r0" virtual register
|
||||
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||
program.add(cx16block)
|
||||
|
||||
@@ -104,12 +106,22 @@ class TestCodegen: FunSpec({
|
||||
Files.deleteIfExists(Path("${result.name}.asm"))
|
||||
}
|
||||
|
||||
test("64tass assembler available? - if this fails you need to install 64tass in the path") {
|
||||
test("64tass assembler available? - if this fails you need to install 64tass version 1.58 or newer in the path") {
|
||||
val command = mutableListOf("64tass", "--version")
|
||||
shouldNotThrowAny {
|
||||
val proc = ProcessBuilder(command).inheritIO().start()
|
||||
val proc = ProcessBuilder(command).start()
|
||||
val output = String(proc.inputStream.readBytes())
|
||||
val result = proc.waitFor()
|
||||
result.shouldBe(0)
|
||||
val (_, version) = output.split('V')
|
||||
val (major, minor, _) = version.split('.')
|
||||
val majorNum = major.toInt()
|
||||
val minorNum = minor.toInt()
|
||||
withClue("64tass version should be 1.58 or newer") {
|
||||
majorNum shouldBeGreaterThanOrEqual 1
|
||||
if (majorNum == 1)
|
||||
minorNum shouldBeGreaterThanOrEqual 58
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -29,17 +29,17 @@ dependencies {
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class ExperiCodeGen: ICodeGeneratorBackend {
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram? {
|
||||
): IAssemblyProgram {
|
||||
|
||||
// you could write a code generator directly on the PtProgram AST,
|
||||
// but you can also use the Intermediate Representation to build a codegen on:
|
||||
@@ -26,6 +26,15 @@ class ExperiCodeGen: ICodeGeneratorBackend {
|
||||
IRFileWriter(irProgram, null).write()
|
||||
|
||||
println("** experimental codegen stub: no assembly generated **")
|
||||
return null
|
||||
return EmptyProgram
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object EmptyProgram : IAssemblyProgram {
|
||||
override val name = "<Empty Program>"
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
println("** nothing assembled **")
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,18 +28,18 @@ dependencies {
|
||||
implementation project(':intermediate')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
test {
|
||||
|
||||
@@ -13,7 +13,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
if(assignment.target.children.single() is PtMachineRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
return translateRegularAssign(assignment)
|
||||
val chunks = translateRegularAssign(assignment)
|
||||
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(assignment.position)
|
||||
return chunks
|
||||
}
|
||||
|
||||
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
|
||||
@@ -24,7 +26,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
val memory = augAssign.target.memory
|
||||
val array = augAssign.target.array
|
||||
|
||||
return if(ident!=null) {
|
||||
val chunks = if(ident!=null) {
|
||||
assignVarAugmented(ident.name, augAssign)
|
||||
} else if(memory != null) {
|
||||
if(memory.address is PtNumber)
|
||||
@@ -39,6 +41,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
} else {
|
||||
fallbackAssign(augAssign)
|
||||
}
|
||||
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(augAssign.position)
|
||||
return chunks
|
||||
}
|
||||
|
||||
private fun assignMemoryAugmented(
|
||||
@@ -48,15 +52,15 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
val value = assignment.value
|
||||
val vmDt = irType(value.type)
|
||||
return when(assignment.operator) {
|
||||
"+" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
|
||||
"-" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
|
||||
"*" -> expressionEval.operatorMultiplyInplace(address, null, vmDt, value)
|
||||
"/" -> expressionEval.operatorDivideInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||
"|" -> expressionEval.operatorOrInplace(address, null, vmDt, value)
|
||||
"&" -> expressionEval.operatorAndInplace(address, null, vmDt, value)
|
||||
"^" -> expressionEval.operatorXorInplace(address, null, vmDt, value)
|
||||
"<<" -> expressionEval.operatorShiftLeftInplace(address, null, vmDt, value)
|
||||
">>" -> expressionEval.operatorShiftRightInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||
"+=" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
|
||||
"-=" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
|
||||
"*=" -> expressionEval.operatorMultiplyInplace(address, null, vmDt, value)
|
||||
"/=" -> expressionEval.operatorDivideInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||
"|=" -> expressionEval.operatorOrInplace(address, null, vmDt, value)
|
||||
"&=" -> expressionEval.operatorAndInplace(address, null, vmDt, value)
|
||||
"^=" -> expressionEval.operatorXorInplace(address, null, vmDt, value)
|
||||
"<<=" -> expressionEval.operatorShiftLeftInplace(address, null, vmDt, value)
|
||||
">>=" -> expressionEval.operatorShiftRightInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||
"%=" -> expressionEval.operatorModuloInplace(address, null, vmDt, value)
|
||||
"==" -> expressionEval.operatorEqualsInplace(address, null, vmDt, value)
|
||||
"!=" -> expressionEval.operatorNotEqualsInplace(address, null, vmDt, value)
|
||||
@@ -96,35 +100,16 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
|
||||
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
|
||||
if (codeGen.options.slowCodegenWarnings)
|
||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
||||
val value: PtExpression
|
||||
if(origAssign.operator in PrefixOperators) {
|
||||
value = PtPrefix(origAssign.operator, origAssign.value.type, origAssign.value.position)
|
||||
value.add(origAssign.value)
|
||||
} else {
|
||||
require(origAssign.operator.endsWith('='))
|
||||
if(codeGen.options.useNewExprCode) {
|
||||
// X += Y -> temp = X, temp += Y, X = temp
|
||||
val tempvar = codeGen.getReusableTempvar(origAssign.definingSub()!!, origAssign.target.type)
|
||||
val assign = PtAssignment(origAssign.position)
|
||||
val target = PtAssignTarget(origAssign.position)
|
||||
target.add(tempvar)
|
||||
assign.add(target)
|
||||
assign.add(origAssign.target.children.single())
|
||||
val augAssign = PtAugmentedAssign(origAssign.operator, origAssign.position)
|
||||
augAssign.add(target)
|
||||
augAssign.add(origAssign.value)
|
||||
val assignBack = PtAssignment(origAssign.position)
|
||||
assignBack.add(origAssign.target)
|
||||
assignBack.add(tempvar)
|
||||
return translateRegularAssign(assign) + translate(augAssign) + translateRegularAssign(assignBack)
|
||||
} else {
|
||||
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
|
||||
val left: PtExpression = origAssign.target.children.single() as PtExpression
|
||||
value.add(left)
|
||||
value.add(origAssign.value)
|
||||
}
|
||||
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
|
||||
val left: PtExpression = origAssign.target.children.single() as PtExpression
|
||||
value.add(left)
|
||||
value.add(origAssign.value)
|
||||
}
|
||||
val normalAssign = PtAssignment(origAssign.position)
|
||||
normalAssign.add(origAssign.target)
|
||||
@@ -184,15 +169,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
} else false
|
||||
if (assignment.value is PtMachineRegister) {
|
||||
valueRegister = (assignment.value as PtMachineRegister).register
|
||||
if(extendByteToWord)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister), null)
|
||||
if(extendByteToWord) {
|
||||
valueRegister = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtMachineRegister).register), null)
|
||||
}
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(assignment.value)
|
||||
valueRegister = tr.resultReg
|
||||
addToResult(result, tr, valueRegister, -1)
|
||||
if(extendByteToWord) {
|
||||
valueRegister = codeGen.registers.nextFree()
|
||||
val opcode = if(assignment.value.type in SignedDatatypes) Opcode.EXTS else Opcode.EXT
|
||||
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1 = valueRegister), null)
|
||||
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,8 +202,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
val variable = targetArray.variable.name
|
||||
val itemsize = codeGen.program.memsizer.memorySize(targetArray.type)
|
||||
|
||||
if(targetArray.variable.type==DataType.UWORD) {
|
||||
// indexing a pointer var instead of a real array or string
|
||||
if(targetArray.usesPointerVariable) {
|
||||
if(itemsize!=1)
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
if(targetArray.index.type!=DataType.UBYTE)
|
||||
@@ -235,36 +222,71 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
|
||||
val fixedIndex = constIntValue(targetArray.index)
|
||||
val arrayLength = codeGen.symbolTable.getLength(targetArray.variable.name)
|
||||
if(zero) {
|
||||
if(fixedIndex!=null) {
|
||||
val offset = fixedIndex*itemsize
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = "$variable+$offset") }
|
||||
val chunk = IRCodeChunk(null, null).also {
|
||||
if(targetArray.splitWords) {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_lsb+$fixedIndex")
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_msb+$fixedIndex")
|
||||
}
|
||||
else
|
||||
it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = "$variable+${fixedIndex*itemsize}")
|
||||
}
|
||||
result += chunk
|
||||
} else {
|
||||
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||
result += code
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable) }
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(targetArray.splitWords) {
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, immediate = arrayLength, labelSymbol = variable+"_lsb")
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, immediate = arrayLength, labelSymbol = variable+"_msb")
|
||||
}
|
||||
else
|
||||
it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(targetDt== IRDataType.FLOAT) {
|
||||
if(fixedIndex!=null) {
|
||||
val offset = fixedIndex*itemsize
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = "$variable+$offset") }
|
||||
val chunk = IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = "$variable+$offset")
|
||||
}
|
||||
result += chunk
|
||||
} else {
|
||||
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||
result += code
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = valueFpRegister, labelSymbol = variable) }
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = valueFpRegister, labelSymbol = variable)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(fixedIndex!=null) {
|
||||
val offset = fixedIndex*itemsize
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = "$variable+$offset") }
|
||||
val chunk = IRCodeChunk(null, null).also {
|
||||
if(targetArray.splitWords) {
|
||||
val msbReg = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueRegister, immediate = arrayLength, labelSymbol = "${variable}_lsb+$fixedIndex")
|
||||
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = msbReg, reg2 = valueRegister)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = msbReg, immediate = arrayLength, labelSymbol = "${variable}_msb+$fixedIndex")
|
||||
}
|
||||
else
|
||||
it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = "$variable+${fixedIndex*itemsize}")
|
||||
}
|
||||
result += chunk
|
||||
} else {
|
||||
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
|
||||
result += code
|
||||
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable) }
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(targetArray.splitWords) {
|
||||
val msbReg = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = valueRegister, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_lsb")
|
||||
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = msbReg, reg2 = valueRegister)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = msbReg, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
|
||||
}
|
||||
else
|
||||
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,28 +323,21 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
|
||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int): Pair<IRCodeChunks, Int> {
|
||||
// returns the code to load the Index into the register, which is also return\ed.
|
||||
// returns the code to load the Index into the register, which is also returned.
|
||||
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(itemsize==1) {
|
||||
if(itemsize==1 || array.splitWords) {
|
||||
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 {
|
||||
val mult: PtExpression
|
||||
mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||
mult.children += array.index
|
||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||
val tr = expressionEval.translateExpression(mult)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
return Pair(result, tr.resultReg)
|
||||
}
|
||||
val mult: PtExpression
|
||||
mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||
mult.children += array.index
|
||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||
val tr = expressionEval.translateExpression(mult)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
return Pair(result, tr.resultReg)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.code.core.SplitWordArrayTypes
|
||||
import prog8.intermediate.*
|
||||
|
||||
|
||||
@@ -13,21 +14,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return when(call.name) {
|
||||
"any" -> funcAny(call)
|
||||
"all" -> funcAll(call)
|
||||
"abs" -> funcAbs(call)
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
|
||||
"cmp" -> funcCmp(call)
|
||||
"sgn" -> funcSgn(call)
|
||||
"sqrt16" -> funcSqrt16(call)
|
||||
"divmod" -> funcDivmod(call, IRDataType.BYTE)
|
||||
"divmodw" -> funcDivmod(call, IRDataType.WORD)
|
||||
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(call)
|
||||
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE)
|
||||
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
|
||||
"pop" -> funcPop(call)
|
||||
"popw" -> funcPopw(call)
|
||||
"push" -> funcPush(call)
|
||||
"pushw" -> funcPushw(call)
|
||||
"rsave",
|
||||
"rsavex",
|
||||
"rrestore",
|
||||
"rrestorex" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
|
||||
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
||||
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
|
||||
"callfar" -> funcCallfar(call)
|
||||
"msb" -> funcMsb(call)
|
||||
"lsb" -> funcLsb(call)
|
||||
"memory" -> funcMemory(call)
|
||||
@@ -37,17 +35,53 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"pokew" -> funcPokeW(call)
|
||||
"pokemon" -> ExpressionCodeResult.EMPTY // easter egg function
|
||||
"mkword" -> funcMkword(call)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
|
||||
"sort" -> funcSort(call)
|
||||
"reverse" -> funcReverse(call)
|
||||
"setlsb" -> funcSetLsbMsb(call, false)
|
||||
"setmsb" -> funcSetLsbMsb(call, true)
|
||||
"rol" -> funcRolRor(Opcode.ROXL, call)
|
||||
"ror" -> funcRolRor(Opcode.ROXR, call)
|
||||
"rol2" -> funcRolRor(Opcode.ROL, call)
|
||||
"ror2" -> funcRolRor(Opcode.ROR, call)
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
||||
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
return if(resultType==IRDataType.FLOAT) {
|
||||
val resultFpReg = codeGen.registers.nextFreeFloat()
|
||||
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = resultFpReg, fpReg2 = valueTr.resultFpReg), null)
|
||||
ExpressionCodeResult(result, resultType, -1, resultFpReg)
|
||||
}
|
||||
else {
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, reg1 = resultReg, reg2 = valueTr.resultReg), null)
|
||||
ExpressionCodeResult(result, resultType, resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val bankTr = exprGen.translateExpression(call.args[0])
|
||||
val addressTr = exprGen.translateExpression(call.args[1])
|
||||
val argumentwordTr = exprGen.translateExpression(call.args[2])
|
||||
addToResult(result, bankTr, bankTr.resultReg, -1)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
addToResult(result, argumentwordTr, argumentwordTr.resultReg, -1)
|
||||
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to argumentwordTr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val number = call.args[0]
|
||||
@@ -79,6 +113,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val left = exprGen.translateExpression(call.args[0])
|
||||
val right = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, left, left.resultReg, -1)
|
||||
@@ -102,9 +137,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val syscall =
|
||||
when (array.dt) {
|
||||
when (arrayName.type) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
@@ -113,19 +148,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
@@ -134,10 +170,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
}
|
||||
@@ -151,21 +188,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
when (sourceDt) {
|
||||
DataType.UBYTE -> {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = tr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
}
|
||||
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.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
|
||||
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=tr.resultReg)
|
||||
}
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
@@ -175,14 +204,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val compareReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
|
||||
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, immediate = 0x8000)
|
||||
it += IRInstruction(Opcode.BEQ, IRDataType.WORD, reg1=compareReg, immediate = 0, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
|
||||
}
|
||||
result += IRCodeChunk(notNegativeLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
DataType.FLOAT -> {
|
||||
val resultFpReg = codeGen.registers.nextFreeFloat()
|
||||
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,15 +231,37 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, vmDt, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcSqrt16(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcSqrt(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
|
||||
val dt = call.args[0].type
|
||||
when(dt) {
|
||||
DataType.UBYTE -> {
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=resultReg, reg2=tr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
val resultFpReg = codeGen.registers.nextFreeFloat()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt for sqrt")
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcPop(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
@@ -249,41 +304,45 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
|
||||
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
|
||||
in SplitWordArrayTypes -> TODO("split word reverse")
|
||||
else -> throw IllegalArgumentException("weird type to reverse")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
|
||||
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
|
||||
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
|
||||
DataType.ARRAY_W -> IMSyscall.SORT_WORD
|
||||
DataType.STR -> IMSyscall.SORT_UBYTE
|
||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||
in SplitWordArrayTypes -> TODO("split word sort")
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
@@ -294,10 +353,86 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = lsbTr.resultReg, reg2 = msbTr.resultReg)
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg, reg3 = msbTr.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, lsbTr.resultReg, -1)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val type = irType(call.type)
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
val minimumTr = exprGen.translateExpression(call.args[1])
|
||||
val maximumTr = exprGen.translateExpression(call.args[2])
|
||||
result += valueTr.chunks
|
||||
result += minimumTr.chunks
|
||||
result += maximumTr.chunks
|
||||
if(type==IRDataType.FLOAT) {
|
||||
result += codeGen.makeSyscall(
|
||||
IMSyscall.CLAMP_FLOAT, listOf(
|
||||
valueTr.dt to valueTr.resultFpReg,
|
||||
minimumTr.dt to minimumTr.resultFpReg,
|
||||
maximumTr.dt to maximumTr.resultFpReg,
|
||||
), type to valueTr.resultFpReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, -1, valueTr.resultFpReg)
|
||||
} else {
|
||||
val syscall = when(call.type) {
|
||||
DataType.UBYTE -> IMSyscall.CLAMP_UBYTE
|
||||
DataType.BYTE -> IMSyscall.CLAMP_BYTE
|
||||
DataType.UWORD -> IMSyscall.CLAMP_UWORD
|
||||
DataType.WORD -> IMSyscall.CLAMP_WORD
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
result += codeGen.makeSyscall(syscall, listOf(
|
||||
valueTr.dt to valueTr.resultReg,
|
||||
minimumTr.dt to minimumTr.resultReg,
|
||||
maximumTr.dt to maximumTr.resultReg,
|
||||
), type to valueTr.resultReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMin(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val type = irType(call.type)
|
||||
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 comparisonOpcode = if(call.type in SignedDatatypes) Opcode.BGTSR else Opcode.BGTR
|
||||
val after = codeGen.createLabelName()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(comparisonOpcode, type, reg1 = rightTr.resultReg, reg2 = leftTr.resultReg, labelSymbol = after)
|
||||
// right <= left, take right
|
||||
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
}
|
||||
result += IRCodeChunk(after, null)
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMax(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val type = irType(call.type)
|
||||
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 comparisonOpcode = if(call.type in SignedDatatypes) Opcode.BGTSR else Opcode.BGTR
|
||||
val after = codeGen.createLabelName()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(comparisonOpcode, type, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg, labelSymbol = after)
|
||||
// right >= left, take right
|
||||
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
}
|
||||
result += IRCodeChunk(after, null)
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcPokeW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
@@ -423,6 +558,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
return exprGen.translateExpression(call.args.single())
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
// TODO to be more strict, maybe we *should* introduce a new result register that is of type .b?
|
||||
}
|
||||
|
||||
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
@@ -449,6 +585,122 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSetLsbMsb(call: PtBuiltinFunctionCall, msb: Boolean): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val target = call.args[0]
|
||||
val isConstZeroValue = call.args[1].asConstInteger()==0
|
||||
when(target) {
|
||||
is PtIdentifier -> {
|
||||
if(isConstZeroValue) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
require(!target.usesPointerVariable)
|
||||
if(target.splitWords) {
|
||||
// lsb/msb in split arrays, element index 'size' is always 1
|
||||
val constIndex = target.index.asConstInteger()
|
||||
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(target.type)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
val offset = eltSize*constIndex + if(msb) 1 else 0
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
|
||||
if(msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
val offset = eltSize*constIndex + if(msb) 1 else 0
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
|
||||
if(msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird target for setlsb/setmsb: $target")
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
|
||||
}
|
||||
|
||||
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(target.position)
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.PassByValueDatatypes
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
|
||||
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
|
||||
@@ -45,9 +41,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val vmDt = irType(expr.type)
|
||||
val code = IRCodeChunk(null, null)
|
||||
if (expr.type in PassByValueDatatypes) {
|
||||
val vmDt = irType(expr.type)
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
val resultFpRegister = codeGen.registers.nextFreeFloat()
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = expr.name)
|
||||
@@ -60,6 +56,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
} else {
|
||||
// for strings and arrays etc., load the *address* of the value instead
|
||||
val vmDt = if(expr.type==DataType.UNDEFINED) IRDataType.WORD else irType(expr.type)
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
|
||||
ExpressionCodeResult(code, vmDt, resultRegister, -1)
|
||||
@@ -69,10 +66,28 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val vmDt = irType(expr.type)
|
||||
val symbol = expr.identifier.name
|
||||
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
|
||||
val code = IRCodeChunk(null, null)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
|
||||
ExpressionCodeResult(code, vmDt, resultRegister, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol), null)
|
||||
if(expr.isFromArrayElement) {
|
||||
val indexTr = translateExpression(expr.arrayIndexExpr!!)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
if(expr.identifier.type in SplitWordArrayTypes) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
|
||||
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
|
||||
}
|
||||
} else {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
|
||||
if(eltSize>1)
|
||||
it += IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=indexTr.resultReg, immediate = eltSize)
|
||||
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
ExpressionCodeResult(result, vmDt, resultRegister, -1)
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -105,9 +120,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
|
||||
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.name) as StStaticVariable
|
||||
when(iterable.dt) {
|
||||
when(check.iterable.type) {
|
||||
DataType.STR -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
@@ -116,27 +131,31 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null)
|
||||
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null)
|
||||
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
|
||||
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.name}")
|
||||
else -> throw AssemblyError("weird iterable dt ${check.iterable.type} for ${check.iterable.name}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +165,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val arrayVarSymbol = arrayIx.variable.name
|
||||
|
||||
if(arrayIx.variable.type==DataType.UWORD) {
|
||||
// indexing a pointer var instead of a real array or string
|
||||
if(arrayIx.usesPointerVariable) {
|
||||
if(eltSize!=1)
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
if(arrayIx.index.type!=DataType.UBYTE)
|
||||
@@ -160,6 +178,33 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
var resultRegister = -1
|
||||
|
||||
if(arrayIx.splitWords) {
|
||||
require(vmDt==IRDataType.WORD)
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayIx.variable.name)
|
||||
resultRegister = codeGen.registers.nextFree()
|
||||
val finalResultReg = codeGen.registers.nextFree()
|
||||
if(arrayIx.index is PtNumber) {
|
||||
val memOffset = (arrayIx.index as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val tmpRegMsb = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
|
||||
}
|
||||
} else {
|
||||
val tr = translateExpression(arrayIx.index)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val tmpRegMsb = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
|
||||
}
|
||||
}
|
||||
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
|
||||
}
|
||||
|
||||
var resultFpRegister = -1
|
||||
if(arrayIx.index is PtNumber) {
|
||||
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize).toString()
|
||||
@@ -247,13 +292,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE -> {
|
||||
// byte -> uword: sign extend
|
||||
actualResultReg2 = tr.resultReg
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
|
||||
actualResultReg2 = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null)
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
// ubyte -> uword: sign extend
|
||||
actualResultReg2 = tr.resultReg
|
||||
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
|
||||
actualResultReg2 = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
actualResultReg2 = tr.resultReg
|
||||
@@ -269,13 +314,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE -> {
|
||||
// byte -> word: sign extend
|
||||
actualResultReg2 = tr.resultReg
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
|
||||
actualResultReg2 = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null)
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
// byte -> word: sign extend
|
||||
actualResultReg2 = tr.resultReg
|
||||
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
|
||||
actualResultReg2 = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
actualResultReg2 = tr.resultReg
|
||||
@@ -312,7 +357,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
private fun translate(binExpr: PtBinaryExpression): ExpressionCodeResult {
|
||||
require(!codeGen.options.useNewExprCode)
|
||||
val vmDt = irType(binExpr.left.type)
|
||||
val signed = binExpr.left.type in SignedDatatypes
|
||||
return when(binExpr.operator) {
|
||||
@@ -340,6 +384,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
|
||||
is StSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
|
||||
// assign the arguments
|
||||
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
@@ -360,8 +405,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
|
||||
}
|
||||
// create the call
|
||||
val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
|
||||
addInstr(result, call, null)
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
|
||||
return if(fcall.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else if(fcall.type==DataType.FLOAT)
|
||||
@@ -371,6 +415,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
is StRomSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
|
||||
// assign the arguments
|
||||
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
@@ -428,6 +473,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
greaterEquals: Boolean
|
||||
): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val cmpResultReg = codeGen.registers.nextFree()
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
addToResult(result, leftTr, -1, leftTr.resultFpReg)
|
||||
@@ -442,10 +488,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
} else {
|
||||
if (greaterEquals) Opcode.SGE else Opcode.SGT
|
||||
}
|
||||
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1=cmpResultReg, reg2 = resultRegister, reg3 = zeroRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultReg, -1)
|
||||
} else {
|
||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
||||
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
|
||||
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
|
||||
} else {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
@@ -457,8 +503,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
} else {
|
||||
if (greaterEquals) Opcode.SGE else Opcode.SGT
|
||||
}
|
||||
addInstr(result, IRInstruction(ins, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(ins, vmDt, reg1=cmpResultReg, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultReg, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,6 +516,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
lessEquals: Boolean
|
||||
): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val cmpResultRegister = codeGen.registers.nextFree()
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
addToResult(result, leftTr, -1, leftTr.resultFpReg)
|
||||
@@ -484,10 +531,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
} else {
|
||||
if (lessEquals) Opcode.SLE else Opcode.SLT
|
||||
}
|
||||
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1=cmpResultRegister, reg2 = resultRegister, reg3 = zeroRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultRegister, -1)
|
||||
} else {
|
||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
||||
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
|
||||
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
|
||||
} else {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
@@ -499,8 +546,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
} else {
|
||||
if (lessEquals) Opcode.SLE else Opcode.SLT
|
||||
}
|
||||
addInstr(result, IRInstruction(ins, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(ins, vmDt, reg1=cmpResultRegister, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultRegister, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,18 +562,20 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
val valueReg = codeGen.registers.nextFree()
|
||||
val label = codeGen.createLabelName()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1), null)
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
if (notEquals) {
|
||||
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0)
|
||||
it += if (notEquals)
|
||||
IRInstruction(Opcode.BSTNE, labelSymbol = label)
|
||||
else
|
||||
IRInstruction(Opcode.BSTEQ, labelSymbol = label)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
|
||||
result += IRCodeChunk(label, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
|
||||
if(binExpr.left.type==DataType.STR || binExpr.right.type==DataType.STR) {
|
||||
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
|
||||
} else {
|
||||
return if(constValue(binExpr.right)==0.0) {
|
||||
@@ -542,8 +591,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, rightTr.resultReg, -1)
|
||||
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultReg, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1213,6 +1263,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
): MutableList<IRCodeChunkBase> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val valueReg = codeGen.registers.nextFree()
|
||||
val cmpResultReg = codeGen.registers.nextFree()
|
||||
if(operand is PtNumber) {
|
||||
val numberReg = codeGen.registers.nextFree()
|
||||
if (knownAddress != null) {
|
||||
@@ -1220,16 +1271,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, address = knownAddress)
|
||||
it += IRInstruction(Opcode.LOAD, vmDt, reg1=numberReg, immediate = operand.number.toInt())
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = numberReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, address = knownAddress)
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = cmpResultReg, reg2 = valueReg, reg3 = numberReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, address = knownAddress)
|
||||
}
|
||||
} else {
|
||||
// in-place modify a symbol (variable)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, labelSymbol = symbol)
|
||||
it += IRInstruction(Opcode.LOAD, vmDt, reg1=numberReg, immediate = operand.number.toInt())
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = numberReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, labelSymbol = symbol)
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1=cmpResultReg, reg2 = valueReg, reg3 = numberReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1239,15 +1290,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
// in-place modify a memory location
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, address = knownAddress)
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = tr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, address = knownAddress)
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1=cmpResultReg, reg2 = valueReg, reg3 = tr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, address = knownAddress)
|
||||
}
|
||||
} else {
|
||||
// in-place modify a symbol (variable)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = valueReg, labelSymbol = symbol)
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = tr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, labelSymbol = symbol)
|
||||
it += IRInstruction(compareAndSetOpcode, vmDt, reg1=cmpResultReg, reg2 = valueReg, reg3 = tr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1266,6 +1317,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val zeroReg = codeGen.registers.nextFree()
|
||||
if(operand is PtNumber) {
|
||||
val numberReg = codeGen.registers.nextFreeFloat()
|
||||
val cmpResultReg = codeGen.registers.nextFree()
|
||||
if (knownAddress != null) {
|
||||
// in-place modify a memory location
|
||||
result += IRCodeChunk(null, null).also {
|
||||
@@ -1273,8 +1325,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
|
||||
}
|
||||
} else {
|
||||
@@ -1284,13 +1336,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val tr = translateExpression(operand)
|
||||
val cmpResultReg = codeGen.registers.nextFree()
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
if (knownAddress != null) {
|
||||
// in-place modify a memory location
|
||||
@@ -1298,8 +1351,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = tr.resultFpReg)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
|
||||
}
|
||||
} else {
|
||||
@@ -1308,8 +1361,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = tr.resultFpReg)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
|
||||
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -23,7 +23,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
|
||||
private fun optimizeOnlyJoinChunks() {
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
irprog.foreachSub { sub ->
|
||||
joinChunks(sub)
|
||||
removeEmptyChunks(sub)
|
||||
joinChunks(sub)
|
||||
@@ -32,10 +32,11 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
|
||||
private fun peepholeOptimize() {
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
irprog.foreachSub { sub ->
|
||||
joinChunks(sub)
|
||||
removeEmptyChunks(sub)
|
||||
joinChunks(sub)
|
||||
|
||||
sub.chunks.withIndex().forEach { (index, chunk1) ->
|
||||
// we don't optimize Inline Asm chunks here.
|
||||
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
|
||||
@@ -46,17 +47,19 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
val changed = removeNops(chunk1, indexedInstructions)
|
||||
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|
||||
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
||||
|| removeNeedlessCompares(chunk1, indexedInstructions)
|
||||
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|
||||
|| removeDoubleSecClc(chunk1, indexedInstructions)
|
||||
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||
// TODO other optimizations:
|
||||
// more complex optimizations such as unused registers
|
||||
// TODO other optimizations
|
||||
} while (changed)
|
||||
}
|
||||
}
|
||||
removeEmptyChunks(sub)
|
||||
}
|
||||
|
||||
// TODO also do register optimization step here at the end?
|
||||
|
||||
irprog.linkChunks() // re-link
|
||||
}
|
||||
|
||||
@@ -102,7 +105,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
|
||||
relabelChunks.forEach { (index, label) ->
|
||||
val chunk = IRCodeChunk(label, null)
|
||||
chunk.instructions += sub.chunks[index].instructions
|
||||
val subChunk = sub.chunks[index]
|
||||
chunk.instructions += subChunk.instructions
|
||||
if(subChunk is IRCodeChunk)
|
||||
chunk.appendSrcPositions(subChunk.sourceLinesPositions)
|
||||
sub.chunks[index] = chunk
|
||||
}
|
||||
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
|
||||
@@ -137,6 +143,8 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
if(mayJoinCodeChunks(lastChunk, candidate)) {
|
||||
lastChunk.instructions += candidate.instructions
|
||||
lastChunk.next = candidate.next
|
||||
if(lastChunk is IRCodeChunk)
|
||||
lastChunk.appendSrcPositions(candidate.sourceLinesPositions)
|
||||
}
|
||||
else
|
||||
chunks += candidate
|
||||
@@ -264,6 +272,28 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeNeedlessCompares(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// a CMPI with 0, after an instruction like LOAD that already sets the status bits, can be removed.
|
||||
// but only if the instruction after it is not using the Carry bit because that won't be set by a LOAD instruction etc.
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate==0) {
|
||||
val previous = indexedInstructions[idx-1].value
|
||||
if(previous.opcode in OpcodesThatSetStatusbitsIncludingCarry) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
} else if(previous.opcode in OpcodesThatSetStatusbitsButNotCarry) {
|
||||
val next = indexedInstructions[idx+1].value
|
||||
if(next.opcode !in arrayOf(Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTPOS, Opcode.BSTNEG)) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
|
||||
var changed = false
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.intermediate.IRProgram
|
||||
|
||||
|
||||
class IRRegisterOptimizer(private val irProg: IRProgram) {
|
||||
fun optimize() {
|
||||
// reuseRegisters()
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: this register re-use renumbering isn't going to work like this,
|
||||
because subroutines will be clobbering the registers that the subroutine
|
||||
which is calling them might be using...
|
||||
|
||||
|
||||
private fun reuseRegisters() {
|
||||
|
||||
fun addToUsage(usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>>,
|
||||
regnum: Int,
|
||||
dt: IRDataType,
|
||||
chunk: IRCodeChunkBase) {
|
||||
val key = regnum to dt
|
||||
val chunks = usage[key] ?: mutableSetOf()
|
||||
chunks.add(chunk)
|
||||
usage[key] = chunks
|
||||
}
|
||||
|
||||
val usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>> = mutableMapOf()
|
||||
|
||||
irProg.foreachCodeChunk { chunk ->
|
||||
chunk.usedRegisters().regsTypes.forEach { (regNum, types) ->
|
||||
types.forEach { dt ->
|
||||
addToUsage(usage, regNum, dt, chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val registerReplacements = usage.asSequence()
|
||||
.filter { it.value.size==1 }
|
||||
.map { it.key to it.value.iterator().next() }
|
||||
.groupBy({ it.second }, {it.first})
|
||||
.asSequence()
|
||||
.associate { (chunk, registers) ->
|
||||
chunk to registers.withIndex().associate { (index, reg) -> reg to 50000+index }
|
||||
}
|
||||
|
||||
registerReplacements.forEach { replaceRegisters(it.key, it.value) }
|
||||
}
|
||||
|
||||
private fun replaceRegisters(chunk: IRCodeChunkBase, replacements: Map<Pair<Int, IRDataType>, Int>) {
|
||||
val (rF, rI) = replacements.asSequence().partition { it.key.second==IRDataType.FLOAT }
|
||||
val replacementsInt = rI.associate { it.key.first to it.value }
|
||||
val replacementsFloat = rF.associate { it.key.first to it.value }
|
||||
|
||||
fun replaceRegs(fcallArgs: FunctionCallArgs?): FunctionCallArgs? {
|
||||
if(fcallArgs==null)
|
||||
return null
|
||||
val args = if(fcallArgs.arguments.isEmpty()) fcallArgs.arguments else {
|
||||
fcallArgs.arguments.map {
|
||||
FunctionCallArgs.ArgumentSpec(
|
||||
it.name,
|
||||
it.address,
|
||||
FunctionCallArgs.RegSpec(
|
||||
it.reg.dt,
|
||||
if(it.reg.dt==IRDataType.FLOAT)
|
||||
replacementsFloat.getOrDefault(it.reg.registerNum, it.reg.registerNum)
|
||||
else
|
||||
replacementsInt.getOrDefault(it.reg.registerNum, it.reg.registerNum),
|
||||
it.reg.cpuRegister
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
val rt = fcallArgs.returns
|
||||
val returns = if(rt==null) null else {
|
||||
FunctionCallArgs.RegSpec(
|
||||
rt.dt,
|
||||
if(rt.dt==IRDataType.FLOAT)
|
||||
replacementsFloat.getOrDefault(rt.registerNum, rt.registerNum)
|
||||
else
|
||||
replacementsInt.getOrDefault(rt.registerNum, rt.registerNum),
|
||||
rt.cpuRegister
|
||||
)
|
||||
}
|
||||
return FunctionCallArgs(args, returns)
|
||||
}
|
||||
|
||||
fun replaceRegs(instruction: IRInstruction): IRInstruction {
|
||||
val reg1 = replacementsInt.getOrDefault(instruction.reg1, instruction.reg1)
|
||||
val reg2 = replacementsInt.getOrDefault(instruction.reg2, instruction.reg2)
|
||||
val fpReg1 = replacementsFloat.getOrDefault(instruction.fpReg1, instruction.fpReg1)
|
||||
val fpReg2 = replacementsFloat.getOrDefault(instruction.fpReg2, instruction.fpReg2)
|
||||
return instruction.copy(reg1 = reg1, reg2 = reg2, fpReg1 = fpReg1, fpReg2 = fpReg2, fcallArgs = replaceRegs(instruction.fcallArgs))
|
||||
}
|
||||
val newInstructions = chunk.instructions.map {
|
||||
replaceRegs(it)
|
||||
}
|
||||
chunk.instructions.clear()
|
||||
chunk.instructions.addAll(newInstructions)
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8.code.core.SourceCode.Companion.LIBRARYFILEPREFIX
|
||||
import prog8.intermediate.*
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class IRUnusedCodeRemover(
|
||||
irprog.blocks.reversed().forEach { block ->
|
||||
if(block.isEmpty()) {
|
||||
irprog.blocks.remove(block)
|
||||
irprog.st.removeTree(block.label)
|
||||
pruneSymboltable(block.label)
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,44 @@ class IRUnusedCodeRemover(
|
||||
return numRemoved
|
||||
}
|
||||
|
||||
private fun pruneSymboltable(blockLabel: String) {
|
||||
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
|
||||
val prefix = "$blockLabel."
|
||||
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
|
||||
blockVars.forEach { stVar ->
|
||||
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
|
||||
chunk.instructions.forEach { ins ->
|
||||
if(ins.labelSymbol == stVar.name) {
|
||||
return // symbol occurs in an instruction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
irprog.st.allVariables().forEach { stVar->
|
||||
val initValue = stVar.onetimeInitializationArrayValue
|
||||
if(initValue!=null && !initValue.isEmpty()) {
|
||||
if(initValue.any {
|
||||
it.addressOfSymbol?.startsWith(blockLabel)==true
|
||||
})
|
||||
return // symbol occurs in an initializer value (address-of this symbol)_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
irprog.st.removeTree(blockLabel)
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
irprog.foreachCodeChunk { 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)) {
|
||||
if(!sub.position.file.startsWith(LIBRARYFILEPREFIX)) {
|
||||
errors.warn("unused subroutine ${sub.label}", sub.position)
|
||||
}
|
||||
block.children.remove(sub)
|
||||
@@ -57,7 +82,7 @@ class IRUnusedCodeRemover(
|
||||
irprog.blocks.forEach { block ->
|
||||
block.children.filterIsInstance<IRAsmSubroutine>().reversed().forEach { sub ->
|
||||
if(sub.isEmpty()) {
|
||||
if(!sub.position.file.startsWith(libraryFilePrefix)) {
|
||||
if(!sub.position.file.startsWith(LIBRARYFILEPREFIX)) {
|
||||
errors.warn("unused asmsubroutine ${sub.label}", sub.position)
|
||||
}
|
||||
block.children.remove(sub)
|
||||
@@ -97,12 +122,10 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
irprog.foreachCodeChunk { 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +146,22 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
|
||||
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||
val entrypointSub = irprog.blocks.single { it.label=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||
val entrypointSub = irprog.blocks.single { it.label=="main" }
|
||||
.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||
|
||||
// all chunks referenced in array initializer values are also 'reachable':
|
||||
irprog.st.allVariables()
|
||||
.filter { !it.uninitialized }
|
||||
.forEach {
|
||||
it.onetimeInitializationArrayValue?.let { array ->
|
||||
array.forEach {elt ->
|
||||
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
|
||||
reachable.add(irprog.getChunkWithLabel(elt.addressOfSymbol!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun grow() {
|
||||
val new = mutableSetOf<IRCodeChunkBase>()
|
||||
reachable.forEach {
|
||||
@@ -154,29 +190,37 @@ class IRUnusedCodeRemover(
|
||||
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
|
||||
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
|
||||
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
sub.chunks.forEach { chunk ->
|
||||
chunk.next?.let { next -> linkedChunks += next }
|
||||
chunk.instructions.forEach {
|
||||
if(it.branchTarget==null) {
|
||||
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
|
||||
} else {
|
||||
linkedChunks += it.branchTarget!!
|
||||
// all chunks referenced in array initializer values are linked as well!:
|
||||
irprog.st.allVariables()
|
||||
.filter { !it.uninitialized }
|
||||
.forEach {
|
||||
it.onetimeInitializationArrayValue?.let { array ->
|
||||
array.forEach {elt ->
|
||||
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
|
||||
linkedChunks += irprog.getChunkWithLabel(elt.addressOfSymbol!!)
|
||||
}
|
||||
}
|
||||
if (chunk.label == "main.start")
|
||||
linkedChunks += chunk
|
||||
}
|
||||
|
||||
irprog.foreachCodeChunk { chunk ->
|
||||
chunk.next?.let { next -> linkedChunks += next }
|
||||
chunk.instructions.forEach {
|
||||
if(it.branchTarget==null) {
|
||||
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
|
||||
} else {
|
||||
linkedChunks += it.branchTarget!!
|
||||
}
|
||||
}
|
||||
if (chunk.label == "main.start")
|
||||
linkedChunks += chunk
|
||||
}
|
||||
|
||||
return removeUnlinkedChunks(linkedChunks)
|
||||
}
|
||||
|
||||
private fun removeUnlinkedChunks(
|
||||
linkedChunks: Set<IRCodeChunkBase>
|
||||
): Int {
|
||||
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
|
||||
var numRemoved = 0
|
||||
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
|
||||
irprog.foreachSub { sub ->
|
||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||
if (chunk !in linkedChunks) {
|
||||
if (chunk === sub.chunks[0]) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class VmCodeGen: ICodeGeneratorBackend {
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram? {
|
||||
): IAssemblyProgram {
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||
val irProgram = irCodeGen.generate()
|
||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||
|
||||
@@ -43,6 +43,10 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
|
||||
warnings.add(text)
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
override fun report() {
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
return makeIRProgram(listOf(chunk))
|
||||
}
|
||||
|
||||
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
|
||||
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.allSubs().flatMap { it.chunks }.toList()
|
||||
|
||||
test("remove nops") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
@@ -91,7 +91,7 @@ class TestVmCodeGen: FunSpec({
|
||||
program.add(block)
|
||||
|
||||
// define the "cx16.r0" virtual register
|
||||
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||
program.add(cx16block)
|
||||
|
||||
@@ -120,7 +120,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -183,7 +183,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -242,7 +242,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -289,7 +289,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -352,7 +352,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -411,7 +411,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -451,7 +451,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
block.add(romsub)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
@@ -466,7 +466,11 @@ class TestVmCodeGen: FunSpec({
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||
irChunks.size shouldBe 1
|
||||
val callInstr = irChunks.single().instructions.single()
|
||||
irChunks[0].instructions.size shouldBe 2
|
||||
val preparecallInstr = irChunks[0].instructions[0]
|
||||
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
|
||||
preparecallInstr.immediate shouldBe 0
|
||||
val callInstr = irChunks[0].instructions[1]
|
||||
callInstr.opcode shouldBe Opcode.CALL
|
||||
callInstr.address shouldBe 0x5000
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ dependencies {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.AssignmentOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AugmentAssignmentOperators
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
class BinExprSplitter(private val program: Program, private val options: CompilationOptions) : AstWalker() {
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(options.compTarget.name == VMTarget.NAME)
|
||||
return noModifications // don't split expressions when targeting the vm codegen, it handles nested expressions well
|
||||
|
||||
if(assignment.value.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
|
||||
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
if(assignment.target isSameAs binExpr.left) {
|
||||
if(binExpr.right.isSimple)
|
||||
return noModifications
|
||||
val leftBx = binExpr.left as? BinaryExpression
|
||||
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
|
||||
return noModifications
|
||||
val rightBx = binExpr.right as? BinaryExpression
|
||||
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.right.isSimple) {
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, AssignmentOrigin.OPTIMIZER, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
|
||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Further unraveling of binary expressions is really complicated here and
|
||||
// often results in much bigger code, thereby defeating the purpose a bit.
|
||||
// All in all this should probably be fixed in a better code generation backend
|
||||
// that doesn't require this at all.
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
!target.isIOAddress(options.compTarget.machine)
|
||||
else
|
||||
false
|
||||
|
||||
}
|
||||
@@ -3,27 +3,29 @@ package prog8.optimizer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.UndefinedSymbolError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.maySwapOperandOrder
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.RepeatLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AssociativeOperators
|
||||
import prog8.code.core.DataType
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
// @( &thing ) --> thing (but only if thing is a byte type!)
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
return if(addrOf!=null)
|
||||
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||
else
|
||||
noModifications
|
||||
if(addrOf!=null) {
|
||||
if(addrOf.identifier.inferType(program).isBytes)
|
||||
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
@@ -234,6 +236,47 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
if(rightconst!=null && (expr.operator=="<<" || expr.operator==">>")) {
|
||||
val dt = expr.left.inferType(program)
|
||||
if(dt.isBytes && rightconst.number>=8) {
|
||||
if(dt.istype(DataType.UBYTE)) {
|
||||
val zeroUB = NumericLiteral(DataType.UBYTE, 0.0, expr.position)
|
||||
modifications.add(IAstModification.ReplaceNode(expr, zeroUB, parent))
|
||||
} else {
|
||||
if(leftconst!=null) {
|
||||
val zeroB = NumericLiteral(DataType.BYTE, 0.0, expr.position)
|
||||
val minusoneB = NumericLiteral(DataType.BYTE, -1.0, expr.position)
|
||||
if(leftconst.number<0.0) {
|
||||
if(expr.operator=="<<")
|
||||
modifications.add(IAstModification.ReplaceNode(expr, zeroB, parent))
|
||||
else
|
||||
modifications.add(IAstModification.ReplaceNode(expr, minusoneB, parent))
|
||||
} else {
|
||||
modifications.add(IAstModification.ReplaceNode(expr, zeroB, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(dt.isWords && rightconst.number>=16) {
|
||||
if(dt.istype(DataType.UWORD)) {
|
||||
val zeroUW = NumericLiteral(DataType.UWORD, 0.0, expr.position)
|
||||
modifications.add(IAstModification.ReplaceNode(expr, zeroUW, parent))
|
||||
} else {
|
||||
if(leftconst!=null) {
|
||||
val zeroW = NumericLiteral(DataType.WORD, 0.0, expr.position)
|
||||
val minusoneW = NumericLiteral(DataType.WORD, -1.0, expr.position)
|
||||
if(leftconst.number<0.0) {
|
||||
if(expr.operator=="<<")
|
||||
modifications.add(IAstModification.ReplaceNode(expr, zeroW, parent))
|
||||
else
|
||||
modifications.add(IAstModification.ReplaceNode(expr, minusoneW, parent))
|
||||
} else {
|
||||
modifications.add(IAstModification.ReplaceNode(expr, zeroW, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
@@ -307,7 +350,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
val rangeTo = iterableRange.to as? NumericLiteral
|
||||
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: return noModifications
|
||||
|
||||
val stepLiteral = iterableRange.step as? NumericLiteral
|
||||
when(loopvar.datatype) {
|
||||
@@ -364,6 +407,16 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val count = (repeatLoop.iterations as? NumericLiteral)?.number
|
||||
if(count!=null && floor(count)!=count) {
|
||||
val integer = NumericLiteral.optimalInteger(count.toInt(), repeatLoop.position)
|
||||
repeatLoop.iterations = integer
|
||||
integer.linkParents(repeatLoop)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private class ShuffleOperands(val expr: BinaryExpression,
|
||||
val exprOperator: String?,
|
||||
val subExpr: BinaryExpression,
|
||||
@@ -576,5 +629,4 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,7 +53,8 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
}
|
||||
|
||||
if(to==null) {
|
||||
if(!range.to.inferType(program).isInteger)
|
||||
val toType = range.to.inferType(program)
|
||||
if(toType.isKnown && !range.to.inferType(program).isInteger)
|
||||
errors.err("range expression to value must be integer", range.to.position)
|
||||
} else if(to-to.toInt()>0) {
|
||||
errors.err("range expression to value must be integer", range.to.position)
|
||||
@@ -68,6 +69,124 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
// choose specific builtin function for the given types
|
||||
val func = functionCallExpr.target.nameInSource
|
||||
if(func==listOf("clamp")) {
|
||||
val t1 = functionCallExpr.args[0].inferType(program)
|
||||
if(t1.isKnown) {
|
||||
val replaceFunc: String
|
||||
if(t1.isBytes) {
|
||||
replaceFunc = if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
|
||||
} else if(t1.isInteger) {
|
||||
replaceFunc = if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
|
||||
} else {
|
||||
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
|
||||
return noModifications
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
}
|
||||
else if(func==listOf("min") || func==listOf("max")) {
|
||||
val t1 = functionCallExpr.args[0].inferType(program)
|
||||
val t2 = functionCallExpr.args[1].inferType(program)
|
||||
if(t1.isKnown && t2.isKnown) {
|
||||
val funcName = func[0]
|
||||
val replaceFunc: String
|
||||
if(t1.isBytes && t2.isBytes) {
|
||||
replaceFunc = if(t1.istype(DataType.BYTE) || t2.istype(DataType.BYTE))
|
||||
"${funcName}__byte"
|
||||
else
|
||||
"${funcName}__ubyte"
|
||||
} else if(t1.isInteger && t2.isInteger) {
|
||||
replaceFunc = if(t1.istype(DataType.WORD) || t2.istype(DataType.WORD))
|
||||
"${funcName}__word"
|
||||
else
|
||||
"${funcName}__uword"
|
||||
} else if(t1.isNumeric && t2.isNumeric) {
|
||||
errors.err("min/max not supported for floats", functionCallExpr.position)
|
||||
return noModifications
|
||||
} else {
|
||||
errors.err("expected numeric arguments", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
}
|
||||
else if(func==listOf("abs")) {
|
||||
val t1 = functionCallExpr.args[0].inferType(program)
|
||||
if(t1.isKnown) {
|
||||
val dt = t1.getOrElse { throw InternalCompilerException("invalid dt") }
|
||||
val replaceFunc = when(dt) {
|
||||
DataType.BYTE -> "abs__byte"
|
||||
DataType.WORD -> "abs__word"
|
||||
DataType.FLOAT -> "abs__float"
|
||||
DataType.UBYTE, DataType.UWORD -> {
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
|
||||
}
|
||||
else -> {
|
||||
errors.err("expected numeric argument", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
}
|
||||
else if(func==listOf("sqrt")) {
|
||||
val t1 = functionCallExpr.args[0].inferType(program)
|
||||
if(t1.isKnown) {
|
||||
val dt = t1.getOrElse { throw InternalCompilerException("invalid dt") }
|
||||
val replaceFunc = when(dt) {
|
||||
DataType.UBYTE -> "sqrt__ubyte"
|
||||
DataType.UWORD -> "sqrt__uword"
|
||||
DataType.FLOAT -> "sqrt__float"
|
||||
else -> {
|
||||
errors.err("expected unsigned or float numeric argument", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
// choose specific builtin function for the given types
|
||||
val func = functionCallStatement.target.nameInSource
|
||||
if(func==listOf("divmod")) {
|
||||
val argTypes = functionCallStatement.args.map {it.inferType(program)}.toSet()
|
||||
if(argTypes.size!=1) {
|
||||
errors.err("expected all ubyte or all uword arguments", functionCallStatement.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
val t1 = argTypes.single()
|
||||
if(t1.isKnown) {
|
||||
val dt = t1.getOrElse { throw InternalCompilerException("invalid dt") }
|
||||
val replaceFunc = when(dt) {
|
||||
DataType.UBYTE -> "divmod__ubyte"
|
||||
DataType.UWORD -> "divmod__uword"
|
||||
else -> {
|
||||
errors.err("expected all ubyte or all uword arguments", functionCallStatement.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallStatement.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallStatement.target.position),
|
||||
functionCallStatement))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,101 +276,10 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = numericLv.number
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_BOOL -> {
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
|
||||
if(replacedArrayInitializer!=null)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
|
||||
}
|
||||
else -> {
|
||||
// nothing to do for this type
|
||||
@@ -261,4 +289,109 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
|
||||
// convert the initializer range expression from a range or int, to an actual array.
|
||||
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange?.isEmpty()==true) {
|
||||
if(constRange.first>constRange.last && constRange.step>=0)
|
||||
errors.err("descending range with positive step", decl.value?.position!!)
|
||||
else if(constRange.first<constRange.last && constRange.step<=0)
|
||||
errors.err("ascending range with negative step", decl.value?.position!!)
|
||||
}
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
return if(eltType in ByteDatatypes) {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
}
|
||||
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = numericLv.number
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_BOOL -> {
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if(numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
|
||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has
|
||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has?
|
||||
|
||||
class ExpressionSimplifier(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
|
||||
@@ -43,9 +43,9 @@ fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget)
|
||||
|
||||
fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
functions: IBuiltinFunctions,
|
||||
compTarget: ICompilationTarget
|
||||
options: CompilationOptions
|
||||
): Int {
|
||||
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
|
||||
val optimizer = StatementOptimizer(this, errors, functions, options)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
|
||||
@@ -54,8 +54,8 @@ fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
fun Program.inlineSubroutines(): Int {
|
||||
val inliner = Inliner(this)
|
||||
fun Program.inlineSubroutines(options: CompilationOptions): Int {
|
||||
val inliner = Inliner(this, options)
|
||||
inliner.visit(this)
|
||||
return inliner.applyModifications()
|
||||
}
|
||||
@@ -65,9 +65,3 @@ fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarg
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
fun Program.splitBinaryExpressions(options: CompilationOptions) : Int {
|
||||
val opti = BinExprSplitter(this, options)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
@@ -8,7 +8,9 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.value==null
|
||||
@@ -16,7 +18,7 @@ private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.va
|
||||
|
||||
// inliner potentially enables *ONE LINED* subroutines, wihtout to be inlined.
|
||||
|
||||
class Inliner(val program: Program): AstWalker() {
|
||||
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
|
||||
|
||||
class DetermineInlineSubs(val program: Program): IAstVisitor {
|
||||
private val modifications = mutableListOf<IAstModification>()
|
||||
@@ -211,7 +213,7 @@ class Inliner(val program: Program): AstWalker() {
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val sub = functionCallStatement.target.targetStatement(program) as? Subroutine
|
||||
return if(sub==null)
|
||||
return if(sub==null || !canInline(sub, functionCallStatement))
|
||||
noModifications
|
||||
else
|
||||
possibleInlineFcallStmt(sub, functionCallStatement, parent)
|
||||
@@ -219,7 +221,7 @@ class Inliner(val program: Program): AstWalker() {
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
|
||||
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
|
||||
if(sub!=null && sub.inline && sub.parameters.isEmpty() && canInline(sub, functionCallExpr)) {
|
||||
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
|
||||
"invalid inline sub at ${sub.position}"
|
||||
}
|
||||
@@ -246,5 +248,17 @@ class Inliner(val program: Program): AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun canInline(sub: Subroutine, fcall: IFunctionCall): Boolean {
|
||||
if(!sub.inline)
|
||||
return false
|
||||
if(options.compTarget.name!=VMTarget.NAME) {
|
||||
val stmt = sub.statements.single()
|
||||
if (stmt is IFunctionCall) {
|
||||
val existing = (fcall as Node).definingScope.lookup(stmt.target.nameInSource.take(1))
|
||||
return existing !is VarDecl
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import kotlin.math.floor
|
||||
class StatementOptimizer(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
private val functions: IBuiltinFunctions,
|
||||
private val compTarget: ICompilationTarget
|
||||
private val options: CompilationOptions
|
||||
) : AstWalker() {
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
@@ -30,7 +30,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference? = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
if(arg.arrayIndex==null) arg.identifier else null
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(string!=null) {
|
||||
val pos = functionCallStatement.position
|
||||
if (string.value.length == 1) {
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.encoding)[0]
|
||||
val firstCharEncoded = options.compTarget.encodeString(string.value, string.encoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||
@@ -51,7 +51,7 @@ class StatementOptimizer(private val program: Program,
|
||||
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
|
||||
)
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.encoding)
|
||||
val firstTwoCharsEncoded = options.compTarget.encodeString(string.value.take(2), string.encoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
|
||||
@@ -108,6 +108,14 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// remove obvious dangling elses (else after a return)
|
||||
if(ifElse.elsepart.isNotEmpty() && ifElse.truepart.statements.singleOrNull() is Return) {
|
||||
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
|
||||
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -141,7 +149,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val size = sv.value.length
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = compTarget.encodeString(sv.value, sv.encoding)[0]
|
||||
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
|
||||
val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
@@ -166,16 +174,6 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val iterationCount = forLoop.constIterationCount(program)
|
||||
if(iterationCount!=null) {
|
||||
val loopName = forLoop.loopVar.nameInSource
|
||||
if(!forLoop.iterable.referencesIdentifier(loopName) && !forLoop.body.referencesIdentifier(loopName)) {
|
||||
errors.warn("for loop can be replaced with repeat loop", forLoop.position)
|
||||
val repeat = RepeatLoop(NumericLiteral.optimalNumeric(iterationCount, forLoop.position), forLoop.body, forLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, repeat, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -326,7 +324,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
|
||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(rightCv.toInt()) {
|
||||
@@ -340,7 +338,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
|
||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(rightCv.toInt()) {
|
||||
@@ -386,9 +384,53 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> {
|
||||
return if(unrollLoop.iterations<1)
|
||||
val iterations = unrollLoop.iterations.constValue(program)?.number?.toInt()
|
||||
return if(iterations!=null && iterations<1)
|
||||
listOf(IAstModification.Remove(unrollLoop, parent as IStatementContainer))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
fun replaceWithIf(condition: Expression, trueBlock: AnonymousScope, elseBlock: AnonymousScope?): List<IAstModification> {
|
||||
val ifStmt = IfElse(condition, trueBlock, elseBlock ?: AnonymousScope(mutableListOf(), whenStmt.position), whenStmt.position)
|
||||
errors.warn("for boolean condition a normal if statement is preferred", whenStmt.position)
|
||||
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
|
||||
}
|
||||
|
||||
if(whenStmt.condition.inferType(program).isBool) {
|
||||
if(whenStmt.choices.all { it.values?.size==1 }) {
|
||||
if (whenStmt.choices.all { it.values!!.single().constValue(program)!!.number in arrayOf(0.0, 1.0) }) {
|
||||
// it's a when statement on booleans that can just be replaced by an if or if..else.
|
||||
if (whenStmt.choices.size == 1) {
|
||||
return if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
|
||||
replaceWithIf(whenStmt.condition, whenStmt.choices[0].statements, null)
|
||||
} else {
|
||||
val notCondition = BinaryExpression(whenStmt.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, whenStmt.condition.position), whenStmt.condition.position)
|
||||
replaceWithIf(notCondition, whenStmt.choices[0].statements, null)
|
||||
}
|
||||
} else if (whenStmt.choices.size == 2) {
|
||||
var trueBlock: AnonymousScope? = null
|
||||
var elseBlock: AnonymousScope? = null
|
||||
if(whenStmt.choices[0].values!![0].constValue(program)!!.number==1.0) {
|
||||
trueBlock = whenStmt.choices[0].statements
|
||||
} else {
|
||||
elseBlock = whenStmt.choices[0].statements
|
||||
}
|
||||
if(whenStmt.choices[1].values!![0].constValue(program)!!.number==1.0) {
|
||||
trueBlock = whenStmt.choices[1].statements
|
||||
} else {
|
||||
elseBlock = whenStmt.choices[1].statements
|
||||
}
|
||||
if(trueBlock!=null && elseBlock!=null) {
|
||||
return replaceWithIf(whenStmt.condition, trueBlock, elseBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,7 +75,9 @@ class UnusedCodeRemover(private val program: Program,
|
||||
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
}
|
||||
program.removeInternedStringsFromRemovedBlock(block)
|
||||
if(!block.statements.any { it is Subroutine && it.hasBeenInlined }) {
|
||||
program.removeInternedStringsFromRemovedBlock(block)
|
||||
}
|
||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
+22
-11
@@ -1,9 +1,10 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||
id "io.kotest" version "0.3.9"
|
||||
id 'io.kotest' version '0.3.9'
|
||||
id 'com.peterabeles.gversion' version '1.10.2'
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -24,8 +25,6 @@ compileTestKotlin {
|
||||
}
|
||||
}
|
||||
|
||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||
|
||||
dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation project(':codeOptimizers')
|
||||
@@ -34,14 +33,14 @@ dependencies {
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation "org.antlr:antlr4-runtime:4.12.0"
|
||||
implementation "org.antlr:antlr4-runtime:4.13.1"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
testImplementation project(':intermediate')
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@@ -60,10 +59,10 @@ configurations {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
test {
|
||||
@@ -82,7 +81,7 @@ application {
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName = 'prog8compiler'
|
||||
archiveVersion = prog8version
|
||||
archiveVersion = version
|
||||
// minimize()
|
||||
}
|
||||
|
||||
@@ -100,4 +99,16 @@ test {
|
||||
}
|
||||
}
|
||||
|
||||
gversion {
|
||||
srcDir = "src/" // path is relative to the sub-project by default
|
||||
classPackage = "prog8.buildversion"
|
||||
className = "BuildVersion"
|
||||
language = "kotlin"
|
||||
}
|
||||
|
||||
|
||||
build.finalizedBy installDist, installShadowDist
|
||||
|
||||
compileKotlin.dependsOn createVersionFile // , failDirtyNotSnapshot
|
||||
compileJava.dependsOn createVersionFile
|
||||
|
||||
@@ -2,44 +2,41 @@
|
||||
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||
|
||||
atari {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
; ---- kernal routines ----
|
||||
; TODO
|
||||
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; TODO
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
clc
|
||||
; TODO reset screen mode etc etc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the Atari
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; TODO
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
clc
|
||||
; TODO reset screen mode etc etc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the Atari
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
@@ -188,6 +185,19 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
; TODO
|
||||
@@ -208,6 +218,7 @@ _longcopy
|
||||
}
|
||||
|
||||
cx16 {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||
; they are simulated on the Atari as well but their location in memory is different
|
||||
@@ -313,4 +324,31 @@ cx16 {
|
||||
&byte r13sH = $1b1b
|
||||
&byte r14sH = $1b1d
|
||||
&byte r15sH = $1b1f
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
sta _cx16_vreg_storage,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda save_virtual_registers._cx16_vreg_storage,y
|
||||
sta cx16.r0,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
txt {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 24
|
||||
|
||||
@@ -30,7 +32,7 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
; TODO
|
||||
@@ -40,7 +42,7 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
|
||||
; ---- clear the character screen with the given fill character (leaves colors)
|
||||
; (assumes screen matrix is at the default address)
|
||||
; TODO
|
||||
@@ -70,7 +72,7 @@ sub uppercase() {
|
||||
; TODO
|
||||
}
|
||||
|
||||
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
@@ -81,7 +83,7 @@ asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_right (bool alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
@@ -91,7 +93,7 @@ asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_up (bool alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
@@ -101,7 +103,7 @@ asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_down (bool alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
@@ -112,13 +114,12 @@ asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
}
|
||||
|
||||
|
||||
romsub $F2B0 = outchar(ubyte char @ A)
|
||||
romsub $F2B0 = outchar(ubyte character @ A)
|
||||
romsub $F2Fd = waitkey()
|
||||
|
||||
asmsub chrout(ubyte char @ A) {
|
||||
asmsub chrout(ubyte character @ A) {
|
||||
%asm {{
|
||||
sta _tmp_outchar+1
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
@@ -130,7 +131,6 @@ _tmp_outchar
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -153,10 +153,9 @@ asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
@@ -164,16 +163,13 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
pla
|
||||
jsr chrout
|
||||
txa
|
||||
jsr chrout
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp chrout
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
@@ -189,16 +185,13 @@ _print_byte_digits
|
||||
beq _ones
|
||||
jsr chrout
|
||||
_ones txa
|
||||
jsr chrout
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp chrout
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
@@ -210,10 +203,9 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
@@ -222,16 +214,13 @@ asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr chrout
|
||||
tya
|
||||
jsr chrout
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp chrout
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
@@ -244,12 +233,11 @@ asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
+ jsr chrout
|
||||
dey
|
||||
bne -
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
@@ -261,7 +249,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
@@ -274,10 +262,9 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
@@ -285,17 +272,14 @@ asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
jsr chrout
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
@@ -316,7 +300,7 @@ _allzero
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
@@ -380,7 +364,7 @@ asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
; TODO
|
||||
%asm {{
|
||||
@@ -388,7 +372,7 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) {
|
||||
; ---- set cursor at specific position
|
||||
; TODO
|
||||
%asm {{
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
; Bitmap pixel graphics module for the Commodore 128
|
||||
|
||||
; TODO c128 actually implement the graphics routines. Ideally a way to 'borrow' the code form the C64 version without just copy-pasting that here?
|
||||
|
||||
graphics {
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 200
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub disable_bitmap_mode() {
|
||||
; enables text mode, erase the text screen, color white
|
||||
; TODO
|
||||
}
|
||||
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub fillrect(uword x, uword y, uword width, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub rect(uword x, uword y, uword width, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, uword y, uword length) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, uword y, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
|
||||
%asm {{
|
||||
nop ; TODO
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
; Prog8 definitions for the Commodore-128
|
||||
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||
|
||||
c64 {
|
||||
cbm {
|
||||
; Commodore (CBM) common variables, vectors and kernal routines
|
||||
%option no_symbol_prefixing
|
||||
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
@@ -47,6 +49,92 @@ c64 {
|
||||
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
|
||||
romsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
|
||||
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 compatible ROM kernal routines ----
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
asmsub STOP2() clobbers(X) -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
%asm {{
|
||||
jsr cbm.STOP
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
%asm {{
|
||||
jsr cbm.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
c64 {
|
||||
; C64 I/O registers (VIC, SID, CIA)
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; the default locations of the 8 sprite pointers (store address of sprite / 64)
|
||||
&ubyte SPRPTR0 = 2040
|
||||
&ubyte SPRPTR1 = 2041
|
||||
@@ -196,95 +284,93 @@ c64 {
|
||||
|
||||
; ---- end of SID registers ----
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
}
|
||||
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
c128 {
|
||||
; ---- C128 specific registers ----
|
||||
%option no_symbol_prefixing
|
||||
|
||||
romsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
&ubyte VM1 = $0A2C ; shadow for VUC $d018 in text mode
|
||||
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
|
||||
&ubyte VM3 = $0A2E ; starting page for VDC screen mem
|
||||
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
|
||||
|
||||
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
; ---- C128 specific system utility routines: ----
|
||||
|
||||
; ---- end of C64 compatible ROM kernal routines ----
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
asmsub STOP2() -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
asmsub disable_basic() clobbers(A) {
|
||||
%asm {{
|
||||
txa
|
||||
pha
|
||||
jsr c64.STOP
|
||||
beq +
|
||||
pla
|
||||
tax
|
||||
lda $0a04 ; disable BASIC shadow registers
|
||||
and #$fe
|
||||
sta $0a04
|
||||
|
||||
lda #$01 ; disable BASIC IRQ service routine
|
||||
sta $12fd
|
||||
|
||||
lda #$ff ; disable screen editor IRQ setup
|
||||
sta $d8
|
||||
|
||||
lda #$b7 ; skip programmable function key check
|
||||
sta $033c
|
||||
|
||||
lda #$0e ; bank out BASIC ROM
|
||||
sta $ff00
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C128 specific system utility routines ----
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
lda #0
|
||||
rts
|
||||
+ pla
|
||||
tax
|
||||
lda #1
|
||||
sta $ff00 ; select default bank 15
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta cbm.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
rts ; no phase 2 steps on the C128
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
jmp sys.enable_runstop_and_charsetswitch
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- system utility routines that are essentially the same as on the C64: -----
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
@@ -305,7 +391,7 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
@@ -314,9 +400,9 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
sta _use_kernal
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
@@ -334,7 +420,7 @@ _modified jsr $ffff ; modified
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
|
||||
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
@@ -352,10 +438,6 @@ _irq_handler_init
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
; Set X to the bottom 32 bytes of the evaluation stack, to HOPEFULLY not clobber it.
|
||||
; This leaves 128-32=96 stack entries for the main program, and 32 stack entries for the IRQ handler.
|
||||
; We assume IRQ handlers don't contain complex expressions taking up more than that.
|
||||
ldx #32
|
||||
cld
|
||||
rts
|
||||
|
||||
@@ -386,10 +468,10 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<c64.IRQDFRT
|
||||
sta c64.CINV
|
||||
lda #>c64.IRQDFRT
|
||||
sta c64.CINV+1
|
||||
lda #<cbm.IRQDFRT
|
||||
sta cbm.CINV
|
||||
lda #>cbm.IRQDFRT
|
||||
sta cbm.CINV+1
|
||||
lda #0
|
||||
sta c64.IREQMASK ; disable raster irq
|
||||
lda #%10000001
|
||||
@@ -399,7 +481,7 @@ asmsub restore_irq() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
@@ -411,9 +493,9 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @P
|
||||
sei
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta c64.CINV
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta c64.CINV+1
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
@@ -421,8 +503,8 @@ _raster_irq_handler
|
||||
jsr set_irq._irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr set_irq._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda set_irq._use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
@@ -432,7 +514,7 @@ _modified jsr $ffff ; modified
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp c64.IRQDFRT ; continue with kernal irq routine
|
||||
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
@@ -456,99 +538,13 @@ _setup_raster_irq
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c128 {
|
||||
; ---- C128 specific registers ----
|
||||
|
||||
&ubyte VM1 = $0A2C ; shadow for VUC $d018 in text mode
|
||||
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
|
||||
&ubyte VM3 = $0A2E ; starting page for VDC screen mem
|
||||
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
|
||||
|
||||
|
||||
; ---- C128 specific system utility routines: ----
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
lda #0
|
||||
sta $ff00 ; select default bank 15
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta c64.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr c64.disable_runstop_and_charsetswitch
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the C128
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
jmp c64.enable_runstop_and_charsetswitch
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_basic() clobbers(A) {
|
||||
%asm {{
|
||||
lda $0a04 ; disable BASIC shadow registers
|
||||
and #$fe
|
||||
sta $0a04
|
||||
|
||||
lda #$01 ; disable BASIC IRQ service routine
|
||||
sta $12fd
|
||||
|
||||
lda #$ff ; disable screen editor IRQ setup
|
||||
sta $d8
|
||||
|
||||
lda #$b7 ; skip programmable function key check
|
||||
sta $033c
|
||||
|
||||
lda #$0e ; bank out BASIC ROM
|
||||
sta $ff00
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C128 specific system utility routines ----
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
sta $ff00 ; default bank 15
|
||||
jmp (c64.RESET_VEC)
|
||||
jmp (cbm.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -566,9 +562,9 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
rts
|
||||
|
||||
+ lda c64.TIME_LO
|
||||
+ lda cbm.TIME_LO
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda c64.TIME_LO
|
||||
- lda cbm.TIME_LO
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
|
||||
@@ -725,13 +721,26 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
lda #0
|
||||
sta $ff00 ; default bank 15
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
jsr c64.enable_runstop_and_charsetswitch
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr sys.enable_runstop_and_charsetswitch
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
@@ -748,113 +757,137 @@ _longcopy
|
||||
}
|
||||
|
||||
cx16 {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||
; they are simulated on the C128 as well but their location in memory is different
|
||||
; (because there's no room for them in the zeropage)
|
||||
; $1300-$1bff is unused RAM on C128. We'll use $1a00-$1bff as the lo/hi evalstack.
|
||||
; the virtual registers are allocated at the bottom of the eval-stack (should be ample space unless
|
||||
; you're doing insane nesting of expressions...)
|
||||
; NOTE: the memory location of these registers can change based on the "-esa" compiler option
|
||||
&uword r0 = $1b00
|
||||
&uword r1 = $1b02
|
||||
&uword r2 = $1b04
|
||||
&uword r3 = $1b06
|
||||
&uword r4 = $1b08
|
||||
&uword r5 = $1b0a
|
||||
&uword r6 = $1b0c
|
||||
&uword r7 = $1b0e
|
||||
&uword r8 = $1b10
|
||||
&uword r9 = $1b12
|
||||
&uword r10 = $1b14
|
||||
&uword r11 = $1b16
|
||||
&uword r12 = $1b18
|
||||
&uword r13 = $1b1a
|
||||
&uword r14 = $1b1c
|
||||
&uword r15 = $1b1e
|
||||
; $1300-$1bff is unused RAM on C128.
|
||||
&uword r0 = $1be0
|
||||
&uword r1 = $1be2
|
||||
&uword r2 = $1be4
|
||||
&uword r3 = $1be6
|
||||
&uword r4 = $1be8
|
||||
&uword r5 = $1bea
|
||||
&uword r6 = $1bec
|
||||
&uword r7 = $1bee
|
||||
&uword r8 = $1bf0
|
||||
&uword r9 = $1bf2
|
||||
&uword r10 = $1bf4
|
||||
&uword r11 = $1bf6
|
||||
&uword r12 = $1bf8
|
||||
&uword r13 = $1bfa
|
||||
&uword r14 = $1bfc
|
||||
&uword r15 = $1bfe
|
||||
|
||||
&word r0s = $1b00
|
||||
&word r1s = $1b02
|
||||
&word r2s = $1b04
|
||||
&word r3s = $1b06
|
||||
&word r4s = $1b08
|
||||
&word r5s = $1b0a
|
||||
&word r6s = $1b0c
|
||||
&word r7s = $1b0e
|
||||
&word r8s = $1b10
|
||||
&word r9s = $1b12
|
||||
&word r10s = $1b14
|
||||
&word r11s = $1b16
|
||||
&word r12s = $1b18
|
||||
&word r13s = $1b1a
|
||||
&word r14s = $1b1c
|
||||
&word r15s = $1b1e
|
||||
&word r0s = $1be0
|
||||
&word r1s = $1be2
|
||||
&word r2s = $1be4
|
||||
&word r3s = $1be6
|
||||
&word r4s = $1be8
|
||||
&word r5s = $1bea
|
||||
&word r6s = $1bec
|
||||
&word r7s = $1bee
|
||||
&word r8s = $1bf0
|
||||
&word r9s = $1bf2
|
||||
&word r10s = $1bf4
|
||||
&word r11s = $1bf6
|
||||
&word r12s = $1bf8
|
||||
&word r13s = $1bfa
|
||||
&word r14s = $1bfc
|
||||
&word r15s = $1bfe
|
||||
|
||||
&ubyte r0L = $1b00
|
||||
&ubyte r1L = $1b02
|
||||
&ubyte r2L = $1b04
|
||||
&ubyte r3L = $1b06
|
||||
&ubyte r4L = $1b08
|
||||
&ubyte r5L = $1b0a
|
||||
&ubyte r6L = $1b0c
|
||||
&ubyte r7L = $1b0e
|
||||
&ubyte r8L = $1b10
|
||||
&ubyte r9L = $1b12
|
||||
&ubyte r10L = $1b14
|
||||
&ubyte r11L = $1b16
|
||||
&ubyte r12L = $1b18
|
||||
&ubyte r13L = $1b1a
|
||||
&ubyte r14L = $1b1c
|
||||
&ubyte r15L = $1b1e
|
||||
&ubyte r0L = $1be0
|
||||
&ubyte r1L = $1be2
|
||||
&ubyte r2L = $1be4
|
||||
&ubyte r3L = $1be6
|
||||
&ubyte r4L = $1be8
|
||||
&ubyte r5L = $1bea
|
||||
&ubyte r6L = $1bec
|
||||
&ubyte r7L = $1bee
|
||||
&ubyte r8L = $1bf0
|
||||
&ubyte r9L = $1bf2
|
||||
&ubyte r10L = $1bf4
|
||||
&ubyte r11L = $1bf6
|
||||
&ubyte r12L = $1bf8
|
||||
&ubyte r13L = $1bfa
|
||||
&ubyte r14L = $1bfc
|
||||
&ubyte r15L = $1bfe
|
||||
|
||||
&ubyte r0H = $1b01
|
||||
&ubyte r1H = $1b03
|
||||
&ubyte r2H = $1b05
|
||||
&ubyte r3H = $1b07
|
||||
&ubyte r4H = $1b09
|
||||
&ubyte r5H = $1b0b
|
||||
&ubyte r6H = $1b0d
|
||||
&ubyte r7H = $1b0f
|
||||
&ubyte r8H = $1b11
|
||||
&ubyte r9H = $1b13
|
||||
&ubyte r10H = $1b15
|
||||
&ubyte r11H = $1b17
|
||||
&ubyte r12H = $1b19
|
||||
&ubyte r13H = $1b1b
|
||||
&ubyte r14H = $1b1d
|
||||
&ubyte r15H = $1b1f
|
||||
&ubyte r0H = $1be1
|
||||
&ubyte r1H = $1be3
|
||||
&ubyte r2H = $1be5
|
||||
&ubyte r3H = $1be7
|
||||
&ubyte r4H = $1be9
|
||||
&ubyte r5H = $1beb
|
||||
&ubyte r6H = $1bed
|
||||
&ubyte r7H = $1bef
|
||||
&ubyte r8H = $1bf1
|
||||
&ubyte r9H = $1bf3
|
||||
&ubyte r10H = $1bf5
|
||||
&ubyte r11H = $1bf7
|
||||
&ubyte r12H = $1bf9
|
||||
&ubyte r13H = $1bfb
|
||||
&ubyte r14H = $1bfd
|
||||
&ubyte r15H = $1bff
|
||||
|
||||
&byte r0sL = $1b00
|
||||
&byte r1sL = $1b02
|
||||
&byte r2sL = $1b04
|
||||
&byte r3sL = $1b06
|
||||
&byte r4sL = $1b08
|
||||
&byte r5sL = $1b0a
|
||||
&byte r6sL = $1b0c
|
||||
&byte r7sL = $1b0e
|
||||
&byte r8sL = $1b10
|
||||
&byte r9sL = $1b12
|
||||
&byte r10sL = $1b14
|
||||
&byte r11sL = $1b16
|
||||
&byte r12sL = $1b18
|
||||
&byte r13sL = $1b1a
|
||||
&byte r14sL = $1b1c
|
||||
&byte r15sL = $1b1e
|
||||
&byte r0sL = $1be0
|
||||
&byte r1sL = $1be2
|
||||
&byte r2sL = $1be4
|
||||
&byte r3sL = $1be6
|
||||
&byte r4sL = $1be8
|
||||
&byte r5sL = $1bea
|
||||
&byte r6sL = $1bec
|
||||
&byte r7sL = $1bee
|
||||
&byte r8sL = $1bf0
|
||||
&byte r9sL = $1bf2
|
||||
&byte r10sL = $1bf4
|
||||
&byte r11sL = $1bf6
|
||||
&byte r12sL = $1bf8
|
||||
&byte r13sL = $1bfa
|
||||
&byte r14sL = $1bfc
|
||||
&byte r15sL = $1bfe
|
||||
|
||||
&byte r0sH = $1b01
|
||||
&byte r1sH = $1b03
|
||||
&byte r2sH = $1b05
|
||||
&byte r3sH = $1b07
|
||||
&byte r4sH = $1b09
|
||||
&byte r5sH = $1b0b
|
||||
&byte r6sH = $1b0d
|
||||
&byte r7sH = $1b0f
|
||||
&byte r8sH = $1b11
|
||||
&byte r9sH = $1b13
|
||||
&byte r10sH = $1b15
|
||||
&byte r11sH = $1b17
|
||||
&byte r12sH = $1b19
|
||||
&byte r13sH = $1b1b
|
||||
&byte r14sH = $1b1d
|
||||
&byte r15sH = $1b1f
|
||||
&byte r0sH = $1be1
|
||||
&byte r1sH = $1be3
|
||||
&byte r2sH = $1be5
|
||||
&byte r3sH = $1be7
|
||||
&byte r4sH = $1be9
|
||||
&byte r5sH = $1beb
|
||||
&byte r6sH = $1bed
|
||||
&byte r7sH = $1bef
|
||||
&byte r8sH = $1bf1
|
||||
&byte r9sH = $1bf3
|
||||
&byte r10sH = $1bf5
|
||||
&byte r11sH = $1bf7
|
||||
&byte r12sH = $1bf9
|
||||
&byte r13sH = $1bfb
|
||||
&byte r14sH = $1bfd
|
||||
&byte r15sH = $1bff
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
sta _cx16_vreg_storage,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda save_virtual_registers._cx16_vreg_storage,y
|
||||
sta cx16.r0,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
|
||||
txt {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
@@ -30,10 +31,10 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr c64.PLOT
|
||||
jsr cbm.PLOT
|
||||
tay
|
||||
clc
|
||||
jmp c64.PLOT
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -57,10 +58,10 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
; (assumes screen matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
- sta c64.Screen+250*0-1,y
|
||||
sta c64.Screen+250*1-1,y
|
||||
sta c64.Screen+250*2-1,y
|
||||
sta c64.Screen+250*3-1,y
|
||||
- sta cbm.Screen+250*0-1,y
|
||||
sta cbm.Screen+250*1-1,y
|
||||
sta cbm.Screen+250*2-1,y
|
||||
sta cbm.Screen+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
@@ -72,10 +73,10 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
- sta c64.Colors+250*0-1,y
|
||||
sta c64.Colors+250*1-1,y
|
||||
sta c64.Colors+250*2-1,y
|
||||
sta c64.Colors+250*3-1,y
|
||||
- sta cbm.Colors+250*0-1,y
|
||||
sta cbm.Colors+250*1-1,y
|
||||
sta cbm.Colors+250*2-1,y
|
||||
sta cbm.Colors+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
@@ -83,7 +84,7 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
}
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.COLOR = txtcol
|
||||
cbm.COLOR = txtcol
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
@@ -96,13 +97,12 @@ sub uppercase() {
|
||||
c128.VM1 &= ~2
|
||||
}
|
||||
|
||||
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, X, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
@@ -110,10 +110,10 @@ asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row + 0,x
|
||||
lda cbm.Screen + 40*row + 1,x
|
||||
sta cbm.Screen + 40*row + 0,x
|
||||
lda cbm.Colors + 40*row + 1,x
|
||||
sta cbm.Colors + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
@@ -125,34 +125,32 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
lda cbm.Screen + 40*row + 1,x
|
||||
sta cbm.Screen + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_right (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
lda cbm.Screen + 40*row + 0,x
|
||||
sta cbm.Screen + 40*row + 1,x
|
||||
lda cbm.Colors + 40*row + 0,x
|
||||
sta cbm.Colors + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
@@ -162,33 +160,31 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
lda cbm.Screen + 40*row + 0,x
|
||||
sta cbm.Screen + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_up (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row-1),x
|
||||
lda cbm.Colors + 40*row,x
|
||||
sta cbm.Colors + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
@@ -198,33 +194,31 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_down (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row+1),x
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
lda cbm.Colors + 40*row,x
|
||||
sta cbm.Colors + 40*(row+1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
@@ -234,134 +228,121 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to c64.CHROUT of that single char.
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
_ones txa
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr c64.CHROUT
|
||||
+ jsr cbm.CHROUT
|
||||
dey
|
||||
bne -
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
@@ -373,7 +354,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
@@ -386,28 +367,24 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
@@ -417,25 +394,25 @@ asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp c64.CHROUT
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
@@ -457,7 +434,7 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr c64.CHRIN
|
||||
- jsr cbm.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
@@ -489,7 +466,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_screenrows .word $0400 + range(0, 1000, 40)
|
||||
_screenrows .word cbm.Screen + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -556,7 +533,7 @@ _mod lda $ffff ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
%asm {{
|
||||
lda row
|
||||
@@ -568,13 +545,13 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
sta _colormod+2
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc column
|
||||
adc col
|
||||
sta _charmod+1
|
||||
sta _colormod+1
|
||||
bcc +
|
||||
inc _charmod+2
|
||||
inc _colormod+2
|
||||
+ lda char
|
||||
+ lda character
|
||||
_charmod sta $ffff ; modified
|
||||
lda charcolor
|
||||
_colormod sta $ffff ; modified
|
||||
@@ -582,22 +559,17 @@ _colormod sta $ffff ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ X) {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
tax
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
; -- returns the text screen width (number of columns)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
jsr cbm.SCREEN
|
||||
txa
|
||||
rts
|
||||
}}
|
||||
@@ -606,7 +578,7 @@ asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
; -- returns the text screen height (number of rows)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
jsr cbm.SCREEN
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
|
||||
@@ -5,13 +5,11 @@ 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
|
||||
floats_temp_var .byte 0,0,0,0,0 ; temporary storage for a float
|
||||
|
||||
ub2float .proc
|
||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers A, X, Y
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
@@ -19,15 +17,12 @@ ub2float .proc
|
||||
jsr GIVAYF
|
||||
_fac_to_mem ldx P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jsr MOVMF
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp MOVMF
|
||||
.pend
|
||||
|
||||
b2float .proc
|
||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers A, X, Y
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda P8ZP_SCRATCH_B1
|
||||
@@ -37,7 +32,7 @@ b2float .proc
|
||||
|
||||
uw2float .proc
|
||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers X
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
@@ -48,7 +43,7 @@ uw2float .proc
|
||||
|
||||
w2float .proc
|
||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers X
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy P8ZP_SCRATCH_W1
|
||||
@@ -60,7 +55,7 @@ w2float .proc
|
||||
|
||||
cast_from_uw .proc
|
||||
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers X
|
||||
jsr GIVUAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
@@ -68,7 +63,7 @@ cast_from_uw .proc
|
||||
|
||||
cast_from_w .proc
|
||||
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers X
|
||||
jsr GIVAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
@@ -76,7 +71,7 @@ cast_from_w .proc
|
||||
|
||||
cast_from_ub .proc
|
||||
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers X
|
||||
jsr FREADUY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
@@ -84,169 +79,41 @@ cast_from_ub .proc
|
||||
|
||||
cast_from_b .proc
|
||||
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; clobbers X
|
||||
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
|
||||
; clobbers X
|
||||
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
|
||||
; clobbers X
|
||||
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
|
||||
; clobbers X
|
||||
jmp GETADR ; into Y/A
|
||||
.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
|
||||
; clobbers X
|
||||
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 P8ZP_SCRATCH_W1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
@@ -272,25 +139,23 @@ copy_float .proc
|
||||
|
||||
inc_var_f .proc
|
||||
; -- add 1 to float pointed to by A/Y
|
||||
; clobbers X
|
||||
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
|
||||
jmp MOVMF
|
||||
.pend
|
||||
|
||||
dec_var_f .proc
|
||||
; -- subtract 1 from float pointed to by A/Y
|
||||
; clobbers X
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr MOVFM
|
||||
@@ -299,23 +164,7 @@ dec_var_f .proc
|
||||
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
|
||||
jmp MOVMF
|
||||
.pend
|
||||
|
||||
|
||||
@@ -323,71 +172,9 @@ 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
|
||||
; -- is the float in FAC1 < the variable AY? Result in A. Clobbers X.
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #255
|
||||
beq +
|
||||
lda #0
|
||||
@@ -397,10 +184,8 @@ var_fac1_less_f .proc
|
||||
.pend
|
||||
|
||||
var_fac1_lesseq_f .proc
|
||||
; -- is the float in FAC1 <= the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; -- is the float in FAC1 <= the variable AY? Result in A. Clobbers X.
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
beq +
|
||||
cmp #255
|
||||
@@ -412,10 +197,8 @@ var_fac1_lesseq_f .proc
|
||||
.pend
|
||||
|
||||
var_fac1_greater_f .proc
|
||||
; -- is the float in FAC1 > the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; -- is the float in FAC1 > the variable AY? Result in A. Clobbers X.
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
@@ -425,10 +208,8 @@ var_fac1_greater_f .proc
|
||||
.pend
|
||||
|
||||
var_fac1_greatereq_f .proc
|
||||
; -- is the float in FAC1 >= the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; -- is the float in FAC1 >= the variable AY? Result in A. Clobbers X.
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
beq +
|
||||
cmp #1
|
||||
@@ -439,17 +220,23 @@ var_fac1_greatereq_f .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_equal_f .proc
|
||||
; -- are the floats numbers in FAC1 and the variable AY *not* identical? Result in A. Clobbers X.
|
||||
jsr FCOMP
|
||||
and #1
|
||||
eor #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_notequal_f .proc
|
||||
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
; -- are the floats numbers in FAC1 and the variable AY *not* identical? Result in A. Clobbers X.
|
||||
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?
|
||||
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical? Result in A
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
@@ -478,51 +265,13 @@ _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 ?
|
||||
; -- is float in AY < float in P8ZP_SCRATCH_W2 ? Result in A. Clobbers X.
|
||||
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
|
||||
@@ -532,13 +281,11 @@ vars_less_f .proc
|
||||
.pend
|
||||
|
||||
vars_lesseq_f .proc
|
||||
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
|
||||
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ? Result in A. Clobbers X.
|
||||
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
|
||||
@@ -550,7 +297,7 @@ vars_lesseq_f .proc
|
||||
.pend
|
||||
|
||||
less_f .proc
|
||||
; -- is f1 < f2?
|
||||
; -- is f1 < f2? Result in A. Clobbers X.
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
@@ -559,7 +306,7 @@ less_f .proc
|
||||
|
||||
|
||||
lesseq_f .proc
|
||||
; -- is f1 <= f2?
|
||||
; -- is f1 <= f2? Result in A. Clobbers X.
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
@@ -569,7 +316,7 @@ lesseq_f .proc
|
||||
.pend
|
||||
|
||||
greater_f .proc
|
||||
; -- is f1 > f2?
|
||||
; -- is f1 > f2? Result in A. Clobbers X.
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
@@ -577,7 +324,7 @@ greater_f .proc
|
||||
.pend
|
||||
|
||||
greatereq_f .proc
|
||||
; -- is f1 >= f2?
|
||||
; -- is f1 >= f2? Result in A. Clobbers X.
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
@@ -586,32 +333,9 @@ greatereq_f .proc
|
||||
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)
|
||||
; clobbers X
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
@@ -622,11 +346,8 @@ set_array_float_from_fac1 .proc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
iny
|
||||
+ stx floats_store_reg
|
||||
tax
|
||||
jsr MOVMF
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
+ tax
|
||||
jmp MOVMF
|
||||
.pend
|
||||
|
||||
|
||||
@@ -669,54 +390,63 @@ set_array_float .proc
|
||||
.pend
|
||||
|
||||
|
||||
equal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq _true
|
||||
bne _false
|
||||
_true lda #1
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
_false lda #0
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
pushFAC1 .proc
|
||||
;-- push floating point in FAC onto the cpu stack
|
||||
; save return address
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W2
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldx #<floats.floats_temp_var
|
||||
ldy #>floats.floats_temp_var
|
||||
jsr floats.MOVMF
|
||||
lda floats.floats_temp_var
|
||||
pha
|
||||
lda floats.floats_temp_var+1
|
||||
pha
|
||||
lda floats.floats_temp_var+2
|
||||
pha
|
||||
lda floats.floats_temp_var+3
|
||||
pha
|
||||
lda floats.floats_temp_var+4
|
||||
pha
|
||||
; re-push return address
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
pha
|
||||
lda P8ZP_SCRATCH_W2
|
||||
pha
|
||||
rts
|
||||
.pend
|
||||
|
||||
notequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bne equal_zero._true
|
||||
beq equal_zero._false
|
||||
.pend
|
||||
popFAC .proc
|
||||
; -- pop floating point value from cpu stack into FAC1 or FAC2 (
|
||||
; carry flag clear=FAC1, carry set=FAC2
|
||||
; save return address
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W2
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
pla
|
||||
sta floats.floats_temp_var+4
|
||||
pla
|
||||
sta floats.floats_temp_var+3
|
||||
pla
|
||||
sta floats.floats_temp_var+2
|
||||
pla
|
||||
sta floats.floats_temp_var+1
|
||||
pla
|
||||
sta floats.floats_temp_var
|
||||
lda #<floats.floats_temp_var
|
||||
ldy #>floats.floats_temp_var
|
||||
bcs +
|
||||
jsr floats.MOVFM
|
||||
jmp ++
|
||||
+ jsr floats.CONUPK
|
||||
+ ; re-push return address
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
pha
|
||||
lda P8ZP_SCRATCH_W2
|
||||
pha
|
||||
rts
|
||||
.pend
|
||||
|
||||
greater_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq equal_zero._false
|
||||
bpl equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
less_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bmi equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
greaterequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bpl equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
lessequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq equal_zero._true
|
||||
bmi equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
@@ -45,7 +46,7 @@ romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
|
||||
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse_f()
|
||||
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
||||
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
|
||||
|
||||
@@ -158,12 +159,19 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp RND ; rng into fac1
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub parse_f(str value @AY) -> float @FAC1 {
|
||||
%asm {{
|
||||
sta $22
|
||||
sty $23
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
jmp FREADSTR
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
; --- floating point builtin functions
|
||||
|
||||
|
||||
func_sign_f_stack .proc
|
||||
jsr func_sign_f_into_A
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sign_f_into_A .proc
|
||||
jsr MOVFM
|
||||
jmp SIGN
|
||||
@@ -144,3 +137,13 @@ func_all_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_stack
|
||||
.pend
|
||||
|
||||
func_abs_f_into_FAC1 .proc
|
||||
jsr MOVFM
|
||||
jmp ABS
|
||||
.pend
|
||||
|
||||
func_sqrt_into_FAC1 .proc
|
||||
jsr MOVFM
|
||||
jmp SQR
|
||||
.pend
|
||||
@@ -1,33 +1,43 @@
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monochrome 320x200 for now
|
||||
; assumes bitmap screen memory is $2000-$3fff
|
||||
;
|
||||
; Sets character matrix and bitmap screen memory at a higher memory location $5c00-$7fff
|
||||
; so that the program itself can be larger without starting to overwrite the graphics memory.
|
||||
|
||||
graphics {
|
||||
const uword BITMAP_ADDRESS = $2000
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 200
|
||||
|
||||
const uword BITMAP_ADDRESS = $6000 ; MUST BE IN REGULAR RAM if you are not messing with ROM/RAM banking. (and $2000-aligned)
|
||||
; note: this constant is also used in a asm multiplication table below!
|
||||
const uword CHARS_ADDRESS = $5c00 ; must be in same vic memory bank as the bitmap.
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
c64.SCROLY = %00111011
|
||||
c64.SCROLX = %00001000
|
||||
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
|
||||
clear_screen(1, 0)
|
||||
c64.SCROLY = %00111011 ; enable bitmap graphics mode
|
||||
c64.SCROLX = %00001000 ; 40 column mode, no scrolling, multicolor bitmap off
|
||||
c64.VMCSB = (lsb(CHARS_ADDRESS >> 6) & $F0) | (lsb((BITMAP_ADDRESS & $3fff) / $0800) << 1) ; set bitmap address
|
||||
c64.CIA2DDRA |= %11
|
||||
c64.CIA2PRA = lsb(BITMAP_ADDRESS >> 14) ^ 3 ; set VIC bank.
|
||||
}
|
||||
|
||||
sub disable_bitmap_mode() {
|
||||
; enables text mode, erase the text screen, color white
|
||||
c64.SCROLY = %00011011
|
||||
c64.SCROLX = %00001000
|
||||
c64.VMCSB = (c64.VMCSB & %11110000) | %00000100 ; $1000-$2fff
|
||||
txt.fill_screen(' ', 1)
|
||||
; enables erase the text screen, text mode
|
||||
sys.memset(CHARS_ADDRESS, 40*25, sc:' ')
|
||||
c64.SCROLY = %00011011 ; disable bitmap graphics mode
|
||||
c64.SCROLX = %00001000 ; 40 column mode, no scrolling
|
||||
c64.VMCSB = %00010100 ; screen addresses back to defaults
|
||||
c64.CIA2DDRA |= %11
|
||||
c64.CIA2PRA = %11 ; back to VIC bank 0.
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
|
||||
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
|
||||
sys.memset(CHARS_ADDRESS, 40*25, pixelcolor << 4 | bgcolor)
|
||||
sys.memset(cbm.Colors, 40*25, 255)
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
@@ -125,46 +135,46 @@ graphics {
|
||||
}
|
||||
}
|
||||
|
||||
sub rect(uword x, ubyte y, uword width, ubyte height) {
|
||||
sub rect(uword xx, ubyte yy, uword width, ubyte height) {
|
||||
if width==0 or height==0
|
||||
return
|
||||
horizontal_line(x, y, width)
|
||||
horizontal_line(xx, yy, width)
|
||||
if height==1
|
||||
return
|
||||
horizontal_line(x, y+height-1, width)
|
||||
vertical_line(x, y+1, height-2)
|
||||
horizontal_line(xx, yy+height-1, width)
|
||||
vertical_line(xx, yy+1, height-2)
|
||||
if width==1
|
||||
return
|
||||
vertical_line(x+width-1, y+1, height-2)
|
||||
vertical_line(xx+width-1, yy+1, height-2)
|
||||
}
|
||||
|
||||
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
|
||||
sub fillrect(uword xx, ubyte yy, uword width, ubyte height) {
|
||||
if width==0
|
||||
return
|
||||
repeat height {
|
||||
horizontal_line(x, y, width)
|
||||
y++
|
||||
horizontal_line(xx, yy, width)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, ubyte y, uword length) {
|
||||
sub horizontal_line(uword xx, ubyte yy, uword length) {
|
||||
if length<8 {
|
||||
internal_plotx=x
|
||||
internal_plotx=xx
|
||||
repeat lsb(length) {
|
||||
internal_plot(y)
|
||||
internal_plot(yy)
|
||||
internal_plotx++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ubyte separate_pixels = lsb(x) & 7
|
||||
uword addr = get_y_lookup(y) + (x&$fff8)
|
||||
ubyte separate_pixels = lsb(xx) & 7
|
||||
uword pixaddr = get_y_lookup(yy) + (xx&$fff8)
|
||||
|
||||
if separate_pixels {
|
||||
%asm {{
|
||||
lda addr
|
||||
lda pixaddr
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda addr+1
|
||||
lda pixaddr+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
ldy separate_pixels
|
||||
lda hline_filled_right,y
|
||||
@@ -173,7 +183,7 @@ graphics {
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
}}
|
||||
addr += 8
|
||||
pixaddr += 8
|
||||
length += separate_pixels
|
||||
length -= 8
|
||||
}
|
||||
@@ -183,16 +193,15 @@ graphics {
|
||||
lda length
|
||||
and #7
|
||||
sta separate_pixels
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lda addr
|
||||
lda pixaddr
|
||||
sta _modified+1
|
||||
lda addr+1
|
||||
lda pixaddr+1
|
||||
sta _modified+2
|
||||
lda length
|
||||
ora length+1
|
||||
@@ -208,8 +217,7 @@ _modified stx $ffff ; modified
|
||||
inc _modified+2
|
||||
+ dey
|
||||
bne _modified
|
||||
_zero ldx P8ZP_SCRATCH_REG
|
||||
|
||||
_zero
|
||||
ldy separate_pixels
|
||||
beq hline_zero2
|
||||
lda _modified+1
|
||||
@@ -227,11 +235,11 @@ hline_zero2
|
||||
}
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, ubyte y, ubyte height) {
|
||||
internal_plotx = x
|
||||
sub vertical_line(uword xx, ubyte yy, ubyte height) {
|
||||
internal_plotx = xx
|
||||
repeat height {
|
||||
internal_plot(y)
|
||||
y++
|
||||
internal_plot(yy)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,8 +308,8 @@ hline_zero2
|
||||
; here is the non-asm code for the plot routine below:
|
||||
; sub plot_nonasm(uword px, ubyte py) {
|
||||
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; uword pixaddr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||
; @(pixaddr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||
@@ -352,7 +360,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
|
||||
|
||||
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
|
||||
_plot_y_values := BITMAP_ADDRESS + 320*(range(200)>>3) + (range(200) & 7)
|
||||
|
||||
_y_lookup_lo .byte <_plot_y_values
|
||||
_y_lookup_hi .byte >_plot_y_values
|
||||
@@ -360,7 +368,7 @@ _y_lookup_hi .byte >_plot_y_values
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
|
||||
asmsub get_y_lookup(ubyte yy @Y) -> uword @AY {
|
||||
%asm {{
|
||||
lda internal_plot._y_lookup_lo,y
|
||||
pha
|
||||
|
||||
+287
-217
@@ -1,7 +1,13 @@
|
||||
; Prog8 definitions for the Commodore-64
|
||||
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||
|
||||
c64 {
|
||||
|
||||
cbm {
|
||||
|
||||
; Commodore (CBM) common variables, vectors and kernal routines
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
@@ -50,7 +56,88 @@ c64 {
|
||||
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||
|
||||
|
||||
; ---- CBM ROM kernal routines (C64 addresses) ----
|
||||
|
||||
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
|
||||
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
asmsub STOP2() clobbers(X) -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
%asm {{
|
||||
jsr cbm.STOP
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
%asm {{
|
||||
jsr cbm.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c64 {
|
||||
; C64 I/O registers (VIC, SID, CIA)
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; the default locations of the 8 sprite pointers (store address of sprite / 64)
|
||||
; (depending on the VIC bank and screen ram address selection these can be shifted around though,
|
||||
; see the two routines after this for a dynamic way of determining the correct memory location)
|
||||
&ubyte SPRPTR0 = 2040
|
||||
&ubyte SPRPTR1 = 2041
|
||||
&ubyte SPRPTR2 = 2042
|
||||
@@ -200,92 +287,47 @@ c64 {
|
||||
; ---- end of SID registers ----
|
||||
|
||||
|
||||
; ---- C64 ROM kernal routines ----
|
||||
sub get_vic_memory_base() -> uword {
|
||||
; one of the 4 possible banks. $0000/$4000/$8000/$c000.
|
||||
c64.CIA2DDRA |= %11
|
||||
return ((c64.CIA2PRA & 3) ^ 3) as uword << 14
|
||||
}
|
||||
|
||||
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
|
||||
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
sub get_char_matrix_ptr() -> uword {
|
||||
; Usually the character screen matrix is at 1024-2039 (see above)
|
||||
; However the vic memory configuration can be altered and this moves these registers with it.
|
||||
; So this routine determines it dynamically from the VIC memory setup.
|
||||
uword chars_matrix_offset = (c64.VMCSB & $f0) as uword << 6
|
||||
return get_vic_memory_base() + chars_matrix_offset
|
||||
}
|
||||
|
||||
; ---- end of C64 ROM kernal routines ----
|
||||
sub get_bitmap_ptr() -> uword {
|
||||
return get_vic_memory_base() + ((c64.VMCSB & %00001000) as uword << 10)
|
||||
}
|
||||
|
||||
; ---- utilities -----
|
||||
sub get_sprite_addr_ptrs() -> uword {
|
||||
; Usually the sprite address pointers are at addresses 2040-2047 (see above)
|
||||
; However the vic memory configuration can be altered and this moves these registers with it.
|
||||
; So this routine determines it dynamically from the VIC memory setup.
|
||||
return get_char_matrix_ptr() + 1016
|
||||
}
|
||||
|
||||
asmsub STOP2() -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
%asm {{
|
||||
txa
|
||||
pha
|
||||
jsr c64.STOP
|
||||
beq +
|
||||
pla
|
||||
tax
|
||||
lda #0
|
||||
rts
|
||||
+ pla
|
||||
tax
|
||||
lda #1
|
||||
rts
|
||||
}}
|
||||
sub set_sprite_ptr(ubyte sprite_num, uword sprite_data_address) {
|
||||
; Sets the sprite data pointer to the given address.
|
||||
; Because it takes some time to calculate things based on the vic memory setup,
|
||||
; its only suitable if you're not continuously changing the data address.
|
||||
; Otherwise store the correct sprite data pointer location somewhere yourself and reuse it.
|
||||
@(get_sprite_addr_ptrs() + sprite_num) = lsb(sprite_data_address / 64)
|
||||
}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
; ---- C64 specific system utility routines: ----
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
@@ -293,7 +335,7 @@ asmsub init_system() {
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
@@ -301,13 +343,13 @@ asmsub init_system() {
|
||||
sta $00
|
||||
lda #%00100111
|
||||
sta $01
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta c64.COLOR
|
||||
sta cbm.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
@@ -327,7 +369,7 @@ asmsub init_system_phase2() {
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
jmp c64.enable_runstop_and_charsetswitch
|
||||
jmp sys.enable_runstop_and_charsetswitch
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -351,7 +393,7 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
@@ -360,9 +402,9 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
sta _use_kernal
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
@@ -380,7 +422,7 @@ _modified jsr $ffff ; modified
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
|
||||
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
@@ -398,10 +440,6 @@ _irq_handler_init
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
; Set X to the bottom 32 bytes of the evaluation stack, to HOPEFULLY not clobber it.
|
||||
; This leaves 128-32=96 stack entries for the main program, and 32 stack entries for the IRQ handler.
|
||||
; We assume IRQ handlers don't contain complex expressions taking up more than that.
|
||||
ldx #32
|
||||
cld
|
||||
rts
|
||||
|
||||
@@ -432,10 +470,10 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<c64.IRQDFRT
|
||||
sta c64.CINV
|
||||
lda #>c64.IRQDFRT
|
||||
sta c64.CINV+1
|
||||
lda #<cbm.IRQDFRT
|
||||
sta cbm.CINV
|
||||
lda #>cbm.IRQDFRT
|
||||
sta cbm.CINV+1
|
||||
lda #0
|
||||
sta c64.IREQMASK ; disable raster irq
|
||||
lda #%10000001
|
||||
@@ -445,7 +483,7 @@ asmsub restore_irq() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
@@ -457,9 +495,9 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @P
|
||||
sei
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta c64.CINV
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta c64.CINV+1
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
@@ -467,8 +505,8 @@ _raster_irq_handler
|
||||
jsr set_irq._irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr set_irq._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda set_irq._use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
@@ -478,7 +516,7 @@ _modified jsr $ffff ; modified
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp c64.IRQDFRT ; continue with kernal irq routine
|
||||
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
@@ -502,23 +540,14 @@ _setup_raster_irq
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C64 specific system utility routines ----
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01 ; bank the kernal in
|
||||
jmp (c64.RESET_VEC)
|
||||
jmp (cbm.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -536,9 +565,9 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
rts
|
||||
|
||||
+ lda c64.TIME_LO
|
||||
+ lda cbm.TIME_LO
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda c64.TIME_LO
|
||||
- lda cbm.TIME_LO
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
|
||||
@@ -695,13 +724,26 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
lda #31
|
||||
sta $01 ; bank the kernal in
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
jsr c64.enable_runstop_and_charsetswitch
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
jsr sys.enable_runstop_and_charsetswitch
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
@@ -719,111 +761,139 @@ _longcopy
|
||||
|
||||
cx16 {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||
; they are simulated on the C64 as well but their location in memory is different
|
||||
; (because there's no room for them in the zeropage)
|
||||
; they are allocated at the bottom of the eval-stack (should be ample space unless
|
||||
; you're doing insane nesting of expressions...)
|
||||
; NOTE: the memory location of these registers can change based on the "-esa" compiler option
|
||||
&uword r0 = $cf00
|
||||
&uword r1 = $cf02
|
||||
&uword r2 = $cf04
|
||||
&uword r3 = $cf06
|
||||
&uword r4 = $cf08
|
||||
&uword r5 = $cf0a
|
||||
&uword r6 = $cf0c
|
||||
&uword r7 = $cf0e
|
||||
&uword r8 = $cf10
|
||||
&uword r9 = $cf12
|
||||
&uword r10 = $cf14
|
||||
&uword r11 = $cf16
|
||||
&uword r12 = $cf18
|
||||
&uword r13 = $cf1a
|
||||
&uword r14 = $cf1c
|
||||
&uword r15 = $cf1e
|
||||
; (because there's no room for them in the zeropage in the default configuration)
|
||||
; Note that when using ZP options that free up more of the zeropage (such as %zeropage kernalsafe)
|
||||
; there might be enough space to put them there after all, and the compiler will change these addresses!
|
||||
&uword r0 = $cfe0
|
||||
&uword r1 = $cfe2
|
||||
&uword r2 = $cfe4
|
||||
&uword r3 = $cfe6
|
||||
&uword r4 = $cfe8
|
||||
&uword r5 = $cfea
|
||||
&uword r6 = $cfec
|
||||
&uword r7 = $cfee
|
||||
&uword r8 = $cff0
|
||||
&uword r9 = $cff2
|
||||
&uword r10 = $cff4
|
||||
&uword r11 = $cff6
|
||||
&uword r12 = $cff8
|
||||
&uword r13 = $cffa
|
||||
&uword r14 = $cffc
|
||||
&uword r15 = $cffe
|
||||
|
||||
&word r0s = $cf00
|
||||
&word r1s = $cf02
|
||||
&word r2s = $cf04
|
||||
&word r3s = $cf06
|
||||
&word r4s = $cf08
|
||||
&word r5s = $cf0a
|
||||
&word r6s = $cf0c
|
||||
&word r7s = $cf0e
|
||||
&word r8s = $cf10
|
||||
&word r9s = $cf12
|
||||
&word r10s = $cf14
|
||||
&word r11s = $cf16
|
||||
&word r12s = $cf18
|
||||
&word r13s = $cf1a
|
||||
&word r14s = $cf1c
|
||||
&word r15s = $cf1e
|
||||
&word r0s = $cfe0
|
||||
&word r1s = $cfe2
|
||||
&word r2s = $cfe4
|
||||
&word r3s = $cfe6
|
||||
&word r4s = $cfe8
|
||||
&word r5s = $cfea
|
||||
&word r6s = $cfec
|
||||
&word r7s = $cfee
|
||||
&word r8s = $cff0
|
||||
&word r9s = $cff2
|
||||
&word r10s = $cff4
|
||||
&word r11s = $cff6
|
||||
&word r12s = $cff8
|
||||
&word r13s = $cffa
|
||||
&word r14s = $cffc
|
||||
&word r15s = $cffe
|
||||
|
||||
&ubyte r0L = $cf00
|
||||
&ubyte r1L = $cf02
|
||||
&ubyte r2L = $cf04
|
||||
&ubyte r3L = $cf06
|
||||
&ubyte r4L = $cf08
|
||||
&ubyte r5L = $cf0a
|
||||
&ubyte r6L = $cf0c
|
||||
&ubyte r7L = $cf0e
|
||||
&ubyte r8L = $cf10
|
||||
&ubyte r9L = $cf12
|
||||
&ubyte r10L = $cf14
|
||||
&ubyte r11L = $cf16
|
||||
&ubyte r12L = $cf18
|
||||
&ubyte r13L = $cf1a
|
||||
&ubyte r14L = $cf1c
|
||||
&ubyte r15L = $cf1e
|
||||
&ubyte r0L = $cfe0
|
||||
&ubyte r1L = $cfe2
|
||||
&ubyte r2L = $cfe4
|
||||
&ubyte r3L = $cfe6
|
||||
&ubyte r4L = $cfe8
|
||||
&ubyte r5L = $cfea
|
||||
&ubyte r6L = $cfec
|
||||
&ubyte r7L = $cfee
|
||||
&ubyte r8L = $cff0
|
||||
&ubyte r9L = $cff2
|
||||
&ubyte r10L = $cff4
|
||||
&ubyte r11L = $cff6
|
||||
&ubyte r12L = $cff8
|
||||
&ubyte r13L = $cffa
|
||||
&ubyte r14L = $cffc
|
||||
&ubyte r15L = $cffe
|
||||
|
||||
&ubyte r0H = $cf01
|
||||
&ubyte r1H = $cf03
|
||||
&ubyte r2H = $cf05
|
||||
&ubyte r3H = $cf07
|
||||
&ubyte r4H = $cf09
|
||||
&ubyte r5H = $cf0b
|
||||
&ubyte r6H = $cf0d
|
||||
&ubyte r7H = $cf0f
|
||||
&ubyte r8H = $cf11
|
||||
&ubyte r9H = $cf13
|
||||
&ubyte r10H = $cf15
|
||||
&ubyte r11H = $cf17
|
||||
&ubyte r12H = $cf19
|
||||
&ubyte r13H = $cf1b
|
||||
&ubyte r14H = $cf1d
|
||||
&ubyte r15H = $cf1f
|
||||
&ubyte r0H = $cfe1
|
||||
&ubyte r1H = $cfe3
|
||||
&ubyte r2H = $cfe5
|
||||
&ubyte r3H = $cfe7
|
||||
&ubyte r4H = $cfe9
|
||||
&ubyte r5H = $cfeb
|
||||
&ubyte r6H = $cfed
|
||||
&ubyte r7H = $cfef
|
||||
&ubyte r8H = $cff1
|
||||
&ubyte r9H = $cff3
|
||||
&ubyte r10H = $cff5
|
||||
&ubyte r11H = $cff7
|
||||
&ubyte r12H = $cff9
|
||||
&ubyte r13H = $cffb
|
||||
&ubyte r14H = $cffd
|
||||
&ubyte r15H = $cfff
|
||||
|
||||
&byte r0sL = $cf00
|
||||
&byte r1sL = $cf02
|
||||
&byte r2sL = $cf04
|
||||
&byte r3sL = $cf06
|
||||
&byte r4sL = $cf08
|
||||
&byte r5sL = $cf0a
|
||||
&byte r6sL = $cf0c
|
||||
&byte r7sL = $cf0e
|
||||
&byte r8sL = $cf10
|
||||
&byte r9sL = $cf12
|
||||
&byte r10sL = $cf14
|
||||
&byte r11sL = $cf16
|
||||
&byte r12sL = $cf18
|
||||
&byte r13sL = $cf1a
|
||||
&byte r14sL = $cf1c
|
||||
&byte r15sL = $cf1e
|
||||
&byte r0sL = $cfe0
|
||||
&byte r1sL = $cfe2
|
||||
&byte r2sL = $cfe4
|
||||
&byte r3sL = $cfe6
|
||||
&byte r4sL = $cfe8
|
||||
&byte r5sL = $cfea
|
||||
&byte r6sL = $cfec
|
||||
&byte r7sL = $cfee
|
||||
&byte r8sL = $cff0
|
||||
&byte r9sL = $cff2
|
||||
&byte r10sL = $cff4
|
||||
&byte r11sL = $cff6
|
||||
&byte r12sL = $cff8
|
||||
&byte r13sL = $cffa
|
||||
&byte r14sL = $cffc
|
||||
&byte r15sL = $cffe
|
||||
|
||||
&byte r0sH = $cfe1
|
||||
&byte r1sH = $cfe3
|
||||
&byte r2sH = $cfe5
|
||||
&byte r3sH = $cfe7
|
||||
&byte r4sH = $cfe9
|
||||
&byte r5sH = $cfeb
|
||||
&byte r6sH = $cfed
|
||||
&byte r7sH = $cfef
|
||||
&byte r8sH = $cff1
|
||||
&byte r9sH = $cff3
|
||||
&byte r10sH = $cff5
|
||||
&byte r11sH = $cff7
|
||||
&byte r12sH = $cff9
|
||||
&byte r13sH = $cffb
|
||||
&byte r14sH = $cffd
|
||||
&byte r15sH = $cfff
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
sta _cx16_vreg_storage,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda save_virtual_registers._cx16_vreg_storage,y
|
||||
sta cx16.r0,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
&byte r0sH = $cf01
|
||||
&byte r1sH = $cf03
|
||||
&byte r2sH = $cf05
|
||||
&byte r3sH = $cf07
|
||||
&byte r4sH = $cf09
|
||||
&byte r5sH = $cf0b
|
||||
&byte r6sH = $cf0d
|
||||
&byte r7sH = $cf0f
|
||||
&byte r8sH = $cf11
|
||||
&byte r9sH = $cf13
|
||||
&byte r10sH = $cf15
|
||||
&byte r11sH = $cf17
|
||||
&byte r12sH = $cf19
|
||||
&byte r13sH = $cf1b
|
||||
&byte r14sH = $cf1d
|
||||
&byte r15sH = $cf1f
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
txt {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
@@ -30,14 +32,14 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr c64.PLOT
|
||||
jsr cbm.PLOT
|
||||
tay
|
||||
clc
|
||||
jmp c64.PLOT
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
|
||||
@@ -52,15 +54,15 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
|
||||
}
|
||||
|
||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
|
||||
; ---- clear the character screen with the given fill character (leaves colors)
|
||||
; (assumes screen matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
- sta c64.Screen+250*0-1,y
|
||||
sta c64.Screen+250*1-1,y
|
||||
sta c64.Screen+250*2-1,y
|
||||
sta c64.Screen+250*3-1,y
|
||||
- sta cbm.Screen+250*0-1,y
|
||||
sta cbm.Screen+250*1-1,y
|
||||
sta cbm.Screen+250*2-1,y
|
||||
sta cbm.Screen+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
@@ -72,10 +74,10 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
- sta c64.Colors+250*0-1,y
|
||||
sta c64.Colors+250*1-1,y
|
||||
sta c64.Colors+250*2-1,y
|
||||
sta c64.Colors+250*3-1,y
|
||||
- sta cbm.Colors+250*0-1,y
|
||||
sta cbm.Colors+250*1-1,y
|
||||
sta cbm.Colors+250*2-1,y
|
||||
sta cbm.Colors+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
@@ -83,7 +85,7 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
}
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.COLOR = txtcol
|
||||
cbm.COLOR = txtcol
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
@@ -94,13 +96,12 @@ sub uppercase() {
|
||||
c64.VMCSB &= ~2
|
||||
}
|
||||
|
||||
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, X, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
@@ -108,10 +109,10 @@ asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row + 0,x
|
||||
lda cbm.Screen + 40*row + 1,x
|
||||
sta cbm.Screen + 40*row + 0,x
|
||||
lda cbm.Colors + 40*row + 1,x
|
||||
sta cbm.Colors + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
@@ -123,34 +124,32 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
lda cbm.Screen + 40*row + 1,x
|
||||
sta cbm.Screen + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_right (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
lda cbm.Screen + 40*row + 0,x
|
||||
sta cbm.Screen + 40*row + 1,x
|
||||
lda cbm.Colors + 40*row + 0,x
|
||||
sta cbm.Colors + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
@@ -160,33 +159,31 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
lda cbm.Screen + 40*row + 0,x
|
||||
sta cbm.Screen + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_up (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row-1),x
|
||||
lda cbm.Colors + 40*row,x
|
||||
sta cbm.Colors + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
@@ -196,33 +193,31 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_down (bool alsocolors @ Pc) clobbers(A,X) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row+1),x
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
lda cbm.Colors + 40*row,x
|
||||
sta cbm.Colors + 40*(row+1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
@@ -232,134 +227,121 @@ _scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
lda cbm.Screen + 40*row,x
|
||||
sta cbm.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to c64.CHROUT of that single char.
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
_ones txa
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr c64.CHROUT
|
||||
+ jsr cbm.CHROUT
|
||||
dey
|
||||
bne -
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
@@ -371,7 +353,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
@@ -384,28 +366,24 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
@@ -415,25 +393,25 @@ asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp c64.CHROUT
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
@@ -455,7 +433,7 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr c64.CHRIN
|
||||
- jsr cbm.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
@@ -487,7 +465,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_screenrows .word $0400 + range(0, 1000, 40)
|
||||
_screenrows .word cbm.Screen + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -554,7 +532,7 @@ _mod lda $ffff ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
%asm {{
|
||||
lda row
|
||||
@@ -566,13 +544,13 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
sta _colormod+2
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc column
|
||||
adc col
|
||||
sta _charmod+1
|
||||
sta _colormod+1
|
||||
bcc +
|
||||
inc _charmod+2
|
||||
inc _colormod+2
|
||||
+ lda char
|
||||
+ lda character
|
||||
_charmod sta $ffff ; modified
|
||||
lda charcolor
|
||||
_colormod sta $ffff ; modified
|
||||
@@ -580,22 +558,17 @@ _colormod sta $ffff ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ X) {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
tax
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
; -- returns the text screen width (number of columns)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
jsr cbm.SCREEN
|
||||
txa
|
||||
rts
|
||||
}}
|
||||
@@ -604,7 +577,7 @@ asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
; -- returns the text screen height (number of rows)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
jsr cbm.SCREEN
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
|
||||
@@ -2,29 +2,28 @@
|
||||
|
||||
conv {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; ----- number conversions to decimal strings ----
|
||||
|
||||
str @shared string_out = "????????????????" ; result buffer for the string conversion routines
|
||||
|
||||
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub str_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
sty string_out
|
||||
sta string_out+1
|
||||
stx string_out+2
|
||||
lda #0
|
||||
sta string_out+3
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub str_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
jsr conv.ubyte2decimal
|
||||
@@ -50,15 +49,13 @@ _output_byte_digits
|
||||
iny
|
||||
lda #0
|
||||
sta string_out,y
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_b (byte value @ A) clobbers(A,Y) {
|
||||
asmsub str_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
cmp #0
|
||||
@@ -73,7 +70,7 @@ asmsub str_b (byte value @ A) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub str_ubhex (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- convert the ubyte in A in hex string form
|
||||
%asm {{
|
||||
jsr conv.ubyte2hex
|
||||
@@ -85,7 +82,7 @@ asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_ubbin (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub str_ubbin (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- convert the ubyte in A in binary string form
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
@@ -104,7 +101,7 @@ _digit sta string_out,y
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_uwbin (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub str_uwbin (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- convert the uword in A/Y in binary string form
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
@@ -143,10 +140,9 @@ asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub str_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
@@ -154,15 +150,13 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub str_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldx #0
|
||||
_output_digits
|
||||
@@ -180,7 +174,6 @@ _gotdigit sta string_out,x
|
||||
bne _gotdigit
|
||||
_end lda #0
|
||||
sta string_out,x
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
_allzero lda #'0'
|
||||
@@ -190,12 +183,11 @@ _allzero lda #'0'
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_w (word value @ AY) clobbers(A,Y) {
|
||||
asmsub str_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl str_uw
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
lda #'-'
|
||||
sta string_out
|
||||
@@ -506,8 +498,7 @@ asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
%asm {{
|
||||
ldy #uword2decimal.ASCII_0_OFFSET
|
||||
bne uword2decimal.hex_try200
|
||||
rts
|
||||
jmp uword2decimal.hex_try200
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -699,10 +690,9 @@ asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
|
||||
asmsub ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y {
|
||||
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
and #$0f
|
||||
tax
|
||||
@@ -714,7 +704,6 @@ asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
|
||||
lsr a
|
||||
tax
|
||||
lda _hex_digits,x
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
; Cx16 specific disk drive I/O routines.
|
||||
|
||||
%import diskio
|
||||
%import string
|
||||
|
||||
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.
|
||||
; 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 specify a custom address_override, the first 2 bytes in the file are ignored
|
||||
; and the rest is loaded at the given location in memory.
|
||||
; 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.
|
||||
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||
cx16.rambank(bank)
|
||||
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.
|
||||
; 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.
|
||||
; If you load into regular system ram, use cx16.getrambank() for the bank argument,
|
||||
; 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.
|
||||
; 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_override) -> uword {
|
||||
cx16.rambank(bank)
|
||||
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):
|
||||
; Calculates the number of bytes loaded (files > 64Kb ar truncated to 16 bits)
|
||||
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
|
||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||
}
|
||||
|
||||
asmsub vload(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",drivenumber,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file has to have the usual 2 byte header (which will be skipped)
|
||||
%asm {{
|
||||
clc
|
||||
internal_vload:
|
||||
phx
|
||||
pha
|
||||
tya
|
||||
tax
|
||||
bcc +
|
||||
ldy #%00000010 ; headerless load mode
|
||||
bne ++
|
||||
+ ldy #0 ; normal load mode
|
||||
+ lda #1
|
||||
jsr c64.SETLFS
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
ldx cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr c64.SETNAM
|
||||
pla
|
||||
clc
|
||||
adc #2
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
stz P8ZP_SCRATCH_B1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
inc P8ZP_SCRATCH_B1
|
||||
+ jsr c64.CLRCHN
|
||||
lda #1
|
||||
jsr c64.CLOSE
|
||||
plx
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vload_raw(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file is read fully including the first two bytes.
|
||||
%asm {{
|
||||
sec
|
||||
jmp vload.internal_vload
|
||||
}}
|
||||
}
|
||||
|
||||
; Replacement function that makes use of fast block read capability of the X16,
|
||||
; 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 {
|
||||
; -- 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)
|
||||
if not diskio.iteration_in_progress or not num_bytes
|
||||
return 0
|
||||
|
||||
diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
|
||||
; commander X16 supports fast block-read via macptr() kernal call
|
||||
uword size
|
||||
while num_bytes {
|
||||
size = 255
|
||||
if num_bytes<size
|
||||
size = num_bytes
|
||||
size = cx16.macptr(lsb(size), bufferpointer, false)
|
||||
if_cs
|
||||
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
||||
diskio.list_blocks += size
|
||||
bufferpointer += size
|
||||
if msb(bufferpointer) == $c0
|
||||
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
|
||||
num_bytes -= size
|
||||
if c64.READST() & $40 {
|
||||
diskio.f_close() ; end of file, close it
|
||||
break
|
||||
}
|
||||
}
|
||||
return diskio.list_blocks ; number of bytes read
|
||||
|
||||
byte_read_loop: ; fallback if macptr() isn't supported on the device
|
||||
%asm {{
|
||||
lda bufferpointer
|
||||
sta m_in_buffer+1
|
||||
lda bufferpointer+1
|
||||
sta m_in_buffer+2
|
||||
}}
|
||||
while num_bytes {
|
||||
if c64.READST() {
|
||||
diskio.f_close()
|
||||
if c64.READST() & $40 ; eof?
|
||||
return diskio.list_blocks ; number of bytes read
|
||||
return 0 ; error.
|
||||
}
|
||||
%asm {{
|
||||
jsr c64.CHRIN
|
||||
m_in_buffer sta $ffff
|
||||
inc m_in_buffer+1
|
||||
bne +
|
||||
inc m_in_buffer+2
|
||||
+
|
||||
}}
|
||||
diskio.list_blocks++
|
||||
num_bytes--
|
||||
}
|
||||
return diskio.list_blocks ; number of bytes read
|
||||
}
|
||||
|
||||
; replacement function that makes use of fast block read capability of the X16
|
||||
; use this in place of regular diskio.f_read_all() on X16
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
if not diskio.iteration_in_progress
|
||||
return 0
|
||||
|
||||
uword total_read = 0
|
||||
while not c64.READST() {
|
||||
cx16.r0 = cx16diskio.f_read(bufferpointer, 256)
|
||||
total_read += cx16.r0
|
||||
bufferpointer += cx16.r0
|
||||
}
|
||||
return total_read
|
||||
}
|
||||
|
||||
|
||||
sub chdir(ubyte drivenumber, str path) {
|
||||
; -- change current directory.
|
||||
diskio.list_filename[0] = 'c'
|
||||
diskio.list_filename[1] = 'd'
|
||||
diskio.list_filename[2] = ':'
|
||||
void string.copy(path, &diskio.list_filename+3)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub mkdir(ubyte drivenumber, str name) {
|
||||
; -- make a new subdirectory.
|
||||
diskio.list_filename[0] = 'm'
|
||||
diskio.list_filename[1] = 'd'
|
||||
diskio.list_filename[2] = ':'
|
||||
void string.copy(name, &diskio.list_filename+3)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub rmdir(ubyte drivenumber, str name) {
|
||||
; -- remove a subdirectory.
|
||||
void string.find(name, '*')
|
||||
if_cs
|
||||
return ; refuse to act on a wildcard *
|
||||
diskio.list_filename[0] = 'r'
|
||||
diskio.list_filename[1] = 'd'
|
||||
diskio.list_filename[2] = ':'
|
||||
void string.copy(name, &diskio.list_filename+3)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub relabel(ubyte drivenumber, str name) {
|
||||
; -- change the disk label.
|
||||
diskio.list_filename[0] = 'r'
|
||||
diskio.list_filename[1] = '-'
|
||||
diskio.list_filename[2] = 'h'
|
||||
diskio.list_filename[3] = ':'
|
||||
void string.copy(name, &diskio.list_filename+4)
|
||||
diskio.send_command(drivenumber, diskio.list_filename)
|
||||
}
|
||||
|
||||
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||
ubyte[6] command = ['p',0,0,0,0,0]
|
||||
command[1] = 12 ; f_open uses channel 12
|
||||
command[2] = lsb(pos_loword)
|
||||
command[3] = msb(pos_loword)
|
||||
command[4] = lsb(pos_hiword)
|
||||
command[5] = msb(pos_hiword)
|
||||
send_command:
|
||||
c64.SETNAM(sizeof(command), &command)
|
||||
c64.SETLFS(15, diskio.last_drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLOSE(15)
|
||||
}
|
||||
|
||||
; TODO see if we can get this to work as well:
|
||||
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
|
||||
; cx16diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
|
||||
; cx16diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
; cx16diskio.f_seek.command[3] = msb(pos_loword)
|
||||
; cx16diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
; cx16diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
; goto cx16diskio.f_seek.send_command
|
||||
; }
|
||||
}
|
||||
@@ -0,0 +1,805 @@
|
||||
; Commander X16 disk drive I/O routines.
|
||||
; Largely compatible with the default C64 ones, but adds more stuff specific to the X16 as well.
|
||||
|
||||
%import textio
|
||||
%import string
|
||||
%import syslib
|
||||
|
||||
diskio {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte READ_IO_CHANNEL=12
|
||||
const ubyte WRITE_IO_CHANNEL=13
|
||||
|
||||
ubyte drivenumber = 8
|
||||
|
||||
sub set_drive(ubyte number) {
|
||||
drivenumber = number
|
||||
}
|
||||
|
||||
sub reset_read_channel() {
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub reset_write_channel() {
|
||||
cbm.CHKOUT(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub directory() -> bool {
|
||||
; -- Prints the directory contents to the screen. Returns success.
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
repeat 4 {
|
||||
void cbm.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not stop key pressed / EOF encountered, read data.
|
||||
status = cbm.READST()
|
||||
if status!=0 {
|
||||
status = 1
|
||||
goto io_error
|
||||
}
|
||||
|
||||
while status==0 {
|
||||
ubyte low = cbm.CHRIN()
|
||||
ubyte high = cbm.CHRIN()
|
||||
txt.print_uw(mkword(high, low))
|
||||
txt.spc()
|
||||
ubyte @zp character
|
||||
repeat {
|
||||
character = cbm.CHRIN()
|
||||
if character==0
|
||||
break
|
||||
txt.chrout(character)
|
||||
}
|
||||
txt.nl()
|
||||
void cbm.CHRIN() ; skip 2 bytes
|
||||
void cbm.CHRIN()
|
||||
status = cbm.READST()
|
||||
if cbm.STOP2()
|
||||
break
|
||||
}
|
||||
status = cbm.READST()
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
|
||||
if status and status & $40 == 0 { ; bit 6=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
txt.print_ub(status)
|
||||
txt.nl()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
sub diskname() -> uword {
|
||||
; returns disk label name or 0 if error
|
||||
cbm.SETNAM(3, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$=c"
|
||||
if_cs
|
||||
goto io_error
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
while cbm.CHRIN()!='"' {
|
||||
; skip up to entry name
|
||||
}
|
||||
|
||||
cx16.r0 = &diskio.list_filename
|
||||
repeat {
|
||||
@(cx16.r0) = cbm.CHRIN()
|
||||
if @(cx16.r0)=='"' {
|
||||
@(cx16.r0) = ' '
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
|
||||
@(cx16.r0) = 0
|
||||
cx16.r0--
|
||||
}
|
||||
break
|
||||
}
|
||||
cx16.r0++
|
||||
}
|
||||
status = cbm.READST()
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
if status and status & $40 == 0
|
||||
return 0
|
||||
return diskio.list_filename
|
||||
}
|
||||
|
||||
; internal variables for the iterative file lister / loader
|
||||
bool list_skip_disk_name
|
||||
uword list_pattern
|
||||
uword list_blocks
|
||||
bool iteration_in_progress = false
|
||||
str list_filetype = "???" ; prg, seq, dir
|
||||
str list_filename = "?" * 50
|
||||
|
||||
; ----- get a list of files (uses iteration functions internally) -----
|
||||
|
||||
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
|
||||
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||
uword buffer_start = filenames_buffer
|
||||
ubyte files_found = 0
|
||||
if lf_start_list(pattern_ptr) {
|
||||
while lf_next_entry() {
|
||||
if list_filetype!="dir" {
|
||||
filenames_buffer += string.copy(list_filename, filenames_buffer) + 1
|
||||
files_found++
|
||||
if filenames_buffer - buffer_start > filenames_buf_size-20 {
|
||||
@(filenames_buffer)=0
|
||||
lf_end_list()
|
||||
sys.set_carry()
|
||||
return files_found
|
||||
}
|
||||
}
|
||||
}
|
||||
lf_end_list()
|
||||
}
|
||||
@(filenames_buffer)=0
|
||||
sys.clear_carry()
|
||||
return files_found
|
||||
}
|
||||
|
||||
; ----- iterative file lister functions (uses the read io channel) -----
|
||||
|
||||
sub lf_start_list(uword pattern_ptr) -> bool {
|
||||
; -- start an iterative file listing with optional pattern matching.
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
lf_end_list()
|
||||
list_pattern = pattern_ptr
|
||||
list_skip_disk_name = true
|
||||
iteration_in_progress = true
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
repeat 4 {
|
||||
void cbm.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
if cbm.READST()==0
|
||||
return true
|
||||
|
||||
io_error:
|
||||
lf_end_list()
|
||||
return false
|
||||
}
|
||||
|
||||
sub lf_next_entry() -> bool {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks, list_filename, and list_filetype.
|
||||
; if it returns false though, there are no more entries (or an error occurred).
|
||||
|
||||
if not iteration_in_progress
|
||||
return false
|
||||
|
||||
repeat {
|
||||
reset_read_channel() ; use the input io channel again
|
||||
|
||||
uword nameptr = &list_filename
|
||||
ubyte blocks_lsb = cbm.CHRIN()
|
||||
ubyte blocks_msb = cbm.CHRIN()
|
||||
|
||||
if cbm.READST()
|
||||
goto close_end
|
||||
|
||||
list_blocks = mkword(blocks_msb, blocks_lsb)
|
||||
|
||||
; read until the filename starts after the first "
|
||||
while cbm.CHRIN()!='\"' {
|
||||
if cbm.READST()
|
||||
goto close_end
|
||||
}
|
||||
|
||||
; read the filename
|
||||
repeat {
|
||||
ubyte character = cbm.CHRIN()
|
||||
if character==0
|
||||
break
|
||||
if character=='\"'
|
||||
break
|
||||
@(nameptr) = character
|
||||
nameptr++
|
||||
}
|
||||
|
||||
@(nameptr) = 0
|
||||
|
||||
do {
|
||||
cx16.r15L = cbm.CHRIN()
|
||||
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
|
||||
list_filetype[0] = cx16.r15L
|
||||
list_filetype[1] = cbm.CHRIN()
|
||||
list_filetype[2] = cbm.CHRIN()
|
||||
while cbm.CHRIN() {
|
||||
; read the rest of the entry until the end
|
||||
}
|
||||
|
||||
void cbm.CHRIN() ; skip 2 bytes
|
||||
void cbm.CHRIN()
|
||||
|
||||
if not list_skip_disk_name {
|
||||
if not list_pattern
|
||||
return true
|
||||
if string.pattern_match(list_filename, list_pattern)
|
||||
return true
|
||||
}
|
||||
list_skip_disk_name = false
|
||||
}
|
||||
|
||||
close_end:
|
||||
lf_end_list()
|
||||
return false
|
||||
}
|
||||
|
||||
sub lf_end_list() {
|
||||
; -- end an iterative file listing session (close channels).
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file loader functions (uses the read io channel) -----
|
||||
|
||||
sub f_open(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative reading with f_read
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
f_close()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
|
||||
void cbm.OPEN() ; open 12,8,12,"filename"
|
||||
if_cc {
|
||||
if cbm.READST()==0 {
|
||||
iteration_in_progress = true
|
||||
reset_read_channel()
|
||||
if_cc {
|
||||
void cbm.CHRIN() ; read first byte to test for file not found
|
||||
if not cbm.READST() {
|
||||
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
|
||||
void cbm.OPEN() ; re-open the file
|
||||
cbm.CLRCHN() ; reset default i/o channels
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
f_close()
|
||||
return false
|
||||
}
|
||||
|
||||
; optimized for Commander X16 to use MACPTR block read kernal call
|
||||
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||
; -- read from the currently open file, up to the given number of bytes.
|
||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
|
||||
if not iteration_in_progress or not num_bytes
|
||||
return 0
|
||||
|
||||
reset_read_channel()
|
||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
|
||||
; commander X16 supports fast block-read via MACPTR() kernal call
|
||||
uword readsize
|
||||
while num_bytes {
|
||||
readsize = 255
|
||||
if num_bytes<readsize
|
||||
readsize = num_bytes
|
||||
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false)
|
||||
if_cs
|
||||
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
|
||||
list_blocks += readsize
|
||||
bufferpointer += readsize
|
||||
if msb(bufferpointer) == $c0
|
||||
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
|
||||
num_bytes -= readsize
|
||||
if cbm.READST() & $40 {
|
||||
f_close() ; end of file, close it
|
||||
break
|
||||
}
|
||||
}
|
||||
return list_blocks ; number of bytes read
|
||||
|
||||
byte_read_loop: ; fallback if MACPTR isn't supported on the device
|
||||
%asm {{
|
||||
lda bufferpointer
|
||||
sta m_in_buffer+1
|
||||
lda bufferpointer+1
|
||||
sta m_in_buffer+2
|
||||
}}
|
||||
while num_bytes {
|
||||
if cbm.READST() {
|
||||
f_close()
|
||||
if cbm.READST() & $40 ; eof?
|
||||
return list_blocks ; number of bytes read
|
||||
return 0 ; error.
|
||||
}
|
||||
%asm {{
|
||||
jsr cbm.CHRIN
|
||||
m_in_buffer sta $ffff
|
||||
inc m_in_buffer+1
|
||||
bne +
|
||||
inc m_in_buffer+2
|
||||
+
|
||||
}}
|
||||
list_blocks++
|
||||
num_bytes--
|
||||
}
|
||||
return list_blocks ; number of bytes read
|
||||
}
|
||||
|
||||
; optimized for Commander X16 to use MACPTR block read kernal call
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
|
||||
if not iteration_in_progress
|
||||
return 0
|
||||
|
||||
reset_read_channel()
|
||||
uword total_read = 0
|
||||
while not cbm.READST() {
|
||||
cx16.r0 = f_read(bufferpointer, 256)
|
||||
total_read += cx16.r0
|
||||
bufferpointer += cx16.r0
|
||||
}
|
||||
return total_read
|
||||
}
|
||||
|
||||
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
|
||||
; Routine to read text lines from a text file. Lines must be less than 255 characters.
|
||||
; Reads characters from the input file UNTIL a newline or return character (or EOF).
|
||||
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
|
||||
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
|
||||
; I/O error status should be checked by the caller itself via READST() routine.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr reset_read_channel
|
||||
ldy #0
|
||||
_loop jsr cbm.CHRIN
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
beq _end
|
||||
iny
|
||||
cmp #$0a
|
||||
beq _line_end
|
||||
cmp #$0d
|
||||
bne _loop
|
||||
_line_end dey ; get rid of the trailing end-of-line char
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
_end rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub f_close() {
|
||||
; -- end an iterative file loading session (close channels).
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file writing functions (uses write io channel) -----
|
||||
|
||||
sub f_open_w(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative writing with f_write
|
||||
f_close_w()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
|
||||
void cbm.OPEN() ; open 13,8,1,"filename"
|
||||
if_cc
|
||||
return not cbm.READST()
|
||||
f_close_w()
|
||||
return false
|
||||
}
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given number of bytes to the currently open file
|
||||
if num_bytes!=0 {
|
||||
reset_write_channel()
|
||||
repeat num_bytes {
|
||||
cbm.CHROUT(@(bufferpointer))
|
||||
bufferpointer++
|
||||
}
|
||||
return not cbm.READST()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
|
||||
; ---- other functions ----
|
||||
|
||||
sub status() -> uword {
|
||||
; -- retrieve the disk drive's current status message
|
||||
uword messageptr = &list_filename
|
||||
cbm.SETNAM(0, list_filename)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN() ; open 15,8,15
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(15) ; use #15 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
while not cbm.READST() {
|
||||
cx16.r5L = cbm.CHRIN()
|
||||
if cx16.r5L=='\r' or cx16.r5L=='\n'
|
||||
break
|
||||
@(messageptr) = cx16.r5L
|
||||
messageptr++
|
||||
}
|
||||
@(messageptr) = 0
|
||||
|
||||
done:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(15)
|
||||
return list_filename
|
||||
|
||||
io_error:
|
||||
list_filename = "?disk error"
|
||||
goto done
|
||||
}
|
||||
|
||||
|
||||
; saves a block of memory to disk, including the default 2 byte prg header.
|
||||
sub save(uword filenameptr, uword startaddress, uword savesize) -> bool {
|
||||
return internal_save_routine(filenameptr, startaddress, savesize, false)
|
||||
}
|
||||
|
||||
; like save() but omits the 2 byte prg header.
|
||||
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
|
||||
return internal_save_routine(filenameptr, startaddress, savesize, true)
|
||||
}
|
||||
|
||||
sub internal_save_routine(uword filenameptr, uword startaddress, uword savesize, bool headerless) -> bool {
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(1, drivenumber, 0)
|
||||
uword @shared end_address = startaddress + savesize
|
||||
cx16.r0L = 0
|
||||
|
||||
%asm {{
|
||||
lda startaddress
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda startaddress+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
ldx end_address
|
||||
ldy end_address+1
|
||||
lda headerless
|
||||
beq +
|
||||
lda #<P8ZP_SCRATCH_W1
|
||||
jsr cx16.BSAVE
|
||||
bra ++
|
||||
+ lda #<P8ZP_SCRATCH_W1
|
||||
jsr cbm.SAVE
|
||||
+ php
|
||||
plp
|
||||
}}
|
||||
|
||||
if_cc
|
||||
cx16.r0L = cbm.READST()==0
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
; 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
|
||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||
; If you specify a custom address_override, the first 2 bytes in the file are ignored
|
||||
; and the rest is loaded at the given location in memory.
|
||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||
; 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,
|
||||
; because it doesn't take the number of ram banks into account.
|
||||
; You can use the load_size() function to calcuate the size in this case.
|
||||
; NOTE: data is read into the current Ram bank if you're reading into banked ram.
|
||||
; if you require loading into another ram bank, you have to set that
|
||||
; yourself using cx16.rambank(bank) before calling load().
|
||||
sub load(uword filenameptr, uword address_override) -> uword {
|
||||
return internal_load_routine(filenameptr, address_override, false)
|
||||
}
|
||||
|
||||
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
|
||||
; No program header is assumed in the file. Everything is loaded.
|
||||
; See comments on load() for more details.
|
||||
sub load_raw(uword filenameptr, uword startaddress) -> uword {
|
||||
return internal_load_routine(filenameptr, startaddress, true)
|
||||
}
|
||||
|
||||
|
||||
sub internal_load_routine(uword filenameptr, uword address_override, bool headerless) -> uword {
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
cx16.r1 = 0
|
||||
if address_override
|
||||
secondary = 0
|
||||
if headerless
|
||||
secondary |= %00000010 ; activate cx16 kernal headerless load support
|
||||
cbm.SETLFS(1, drivenumber, secondary)
|
||||
%asm {{
|
||||
lda #0
|
||||
ldx address_override
|
||||
ldy address_override+1
|
||||
jsr cbm.LOAD
|
||||
bcs +
|
||||
stx cx16.r1
|
||||
sty cx16.r1+1
|
||||
+
|
||||
}}
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
return cx16.r1
|
||||
}
|
||||
|
||||
sub delete(uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
list_filename[0] = 's'
|
||||
list_filename[1] = ':'
|
||||
ubyte flen = string.copy(filenameptr, &list_filename+2)
|
||||
cbm.SETNAM(flen+2, list_filename)
|
||||
cbm.SETLFS(1, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
}
|
||||
|
||||
sub rename(uword oldfileptr, uword newfileptr) {
|
||||
; -- rename a file on the drive
|
||||
list_filename[0] = 'r'
|
||||
list_filename[1] = ':'
|
||||
ubyte flen_new = string.copy(newfileptr, &list_filename+2)
|
||||
list_filename[flen_new+2] = '='
|
||||
ubyte flen_old = string.copy(oldfileptr, &list_filename+3+flen_new)
|
||||
cbm.SETNAM(3+flen_new+flen_old, list_filename)
|
||||
cbm.SETLFS(1, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
}
|
||||
|
||||
sub send_command(uword commandptr) {
|
||||
; -- send a dos command to the drive
|
||||
cbm.SETNAM(string.length(commandptr), commandptr)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(15)
|
||||
}
|
||||
|
||||
|
||||
; CommanderX16 extensions over the basic C64/C128 diskio routines:
|
||||
|
||||
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
|
||||
; Calculates the number of bytes loaded (files > 64Kb ar truncated to 16 bits)
|
||||
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
|
||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||
}
|
||||
|
||||
asmsub vload(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",drivenumber,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file has to have the usual 2 byte header (which will be skipped)
|
||||
%asm {{
|
||||
clc
|
||||
internal_vload:
|
||||
pha
|
||||
ldx drivenumber
|
||||
bcc +
|
||||
ldy #%00000010 ; headerless load mode
|
||||
bne ++
|
||||
+ ldy #0 ; normal load mode
|
||||
+ lda #1
|
||||
jsr cbm.SETLFS
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
ldx cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr cbm.SETNAM
|
||||
pla
|
||||
clc
|
||||
adc #2
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
stz P8ZP_SCRATCH_B1
|
||||
jsr cbm.LOAD
|
||||
bcs +
|
||||
inc P8ZP_SCRATCH_B1
|
||||
+ jsr cbm.CLRCHN
|
||||
lda #1
|
||||
jsr cbm.CLOSE
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vload_raw(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
|
||||
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
|
||||
; loads a file into Vera's video memory in the given bank:address, returns success in A
|
||||
; the file is read fully including the first two bytes.
|
||||
%asm {{
|
||||
sec
|
||||
jmp vload.internal_vload
|
||||
}}
|
||||
}
|
||||
|
||||
sub chdir(str path) {
|
||||
; -- change current directory.
|
||||
list_filename[0] = 'c'
|
||||
list_filename[1] = 'd'
|
||||
list_filename[2] = ':'
|
||||
void string.copy(path, &list_filename+3)
|
||||
send_command(list_filename)
|
||||
}
|
||||
|
||||
sub mkdir(str name) {
|
||||
; -- make a new subdirectory.
|
||||
list_filename[0] = 'm'
|
||||
list_filename[1] = 'd'
|
||||
list_filename[2] = ':'
|
||||
void string.copy(name, &list_filename+3)
|
||||
send_command(list_filename)
|
||||
}
|
||||
|
||||
sub rmdir(str name) {
|
||||
; -- remove a subdirectory.
|
||||
void string.find(name, '*')
|
||||
if_cs
|
||||
return ; refuse to act on a wildcard *
|
||||
list_filename[0] = 'r'
|
||||
list_filename[1] = 'd'
|
||||
list_filename[2] = ':'
|
||||
void string.copy(name, &list_filename+3)
|
||||
send_command(list_filename)
|
||||
}
|
||||
|
||||
sub curdir() -> uword {
|
||||
; return current directory name or 0 if error
|
||||
; special X16 dos command to only return the current path in the entry list (R42+)
|
||||
const ubyte MAX_PATH_LEN=80
|
||||
uword reversebuffer = memory("curdir_buffer", MAX_PATH_LEN, 0)
|
||||
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
|
||||
@(cx16.r12)=0
|
||||
cbm.SETNAM(3, "$=c")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"$=c"
|
||||
if_cs
|
||||
goto io_error
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
repeat 6 {
|
||||
void cbm.CHRIN()
|
||||
}
|
||||
while cbm.CHRIN() {
|
||||
; skip first line (drive label)
|
||||
}
|
||||
while cbm.CHRIN()!='"' {
|
||||
; skip to first name
|
||||
}
|
||||
ubyte status = cbm.READST()
|
||||
cx16.r10 = &list_filename
|
||||
while status==0 {
|
||||
repeat {
|
||||
@(cx16.r10) = cbm.CHRIN()
|
||||
if @(cx16.r10)==0
|
||||
break
|
||||
cx16.r10++
|
||||
}
|
||||
while @(cx16.r10)!='"' and cx16.r10>=&list_filename {
|
||||
@(cx16.r10)=0
|
||||
cx16.r10--
|
||||
}
|
||||
@(cx16.r10)=0
|
||||
prepend(list_filename)
|
||||
cx16.r10 = &list_filename
|
||||
while cbm.CHRIN()!='"' and status==0 {
|
||||
status = cbm.READST()
|
||||
; skipping up to next entry name
|
||||
}
|
||||
}
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
if status and status & $40 == 0
|
||||
return 0
|
||||
if @(cx16.r12)==0 {
|
||||
cx16.r12--
|
||||
@(cx16.r12)='/'
|
||||
}
|
||||
return cx16.r12
|
||||
|
||||
sub prepend(str dir) {
|
||||
if dir[0]=='/' and dir[1]==0
|
||||
return
|
||||
cx16.r9L = string.length(dir)
|
||||
cx16.r12 -= cx16.r9L
|
||||
sys.memcopy(dir, cx16.r12, cx16.r9L)
|
||||
cx16.r12--
|
||||
@(cx16.r12)='/'
|
||||
}
|
||||
}
|
||||
|
||||
sub relabel(str name) {
|
||||
; -- change the disk label.
|
||||
list_filename[0] = 'r'
|
||||
list_filename[1] = '-'
|
||||
list_filename[2] = 'h'
|
||||
list_filename[3] = ':'
|
||||
void string.copy(name, &list_filename+4)
|
||||
send_command(list_filename)
|
||||
}
|
||||
|
||||
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||
ubyte[6] command = ['p',0,0,0,0,0]
|
||||
command[1] = READ_IO_CHANNEL ; f_open uses the read io channel
|
||||
command[2] = lsb(pos_loword)
|
||||
command[3] = msb(pos_loword)
|
||||
command[4] = lsb(pos_hiword)
|
||||
command[5] = msb(pos_hiword)
|
||||
send_command:
|
||||
cbm.SETNAM(sizeof(command), &command)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLOSE(15)
|
||||
reset_read_channel() ; back to the read io channel
|
||||
}
|
||||
|
||||
|
||||
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
|
||||
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
|
||||
; diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses the write io channel
|
||||
; diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
; diskio.f_seek.command[3] = msb(pos_loword)
|
||||
; diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
; diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
; goto diskio.f_seek.send_command
|
||||
; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
; Emulator debug interface.
|
||||
; Docs: https://github.com/X16Community/x16-emulator#debug-io-registers
|
||||
|
||||
emudbg {
|
||||
|
||||
const uword EMU_BASE = $9fb0
|
||||
|
||||
&ubyte EMU_DBG_HOTKEY_ENABLED = EMU_BASE + 0
|
||||
&ubyte EMU_LOG_VIDEO = EMU_BASE + 1
|
||||
&ubyte EMU_LOG_KEYBOARD = EMU_BASE + 2
|
||||
&ubyte EMU_ECHO_MODE = EMU_BASE + 3
|
||||
&ubyte EMU_SAVE_ON_EXIT = EMU_BASE + 4
|
||||
&ubyte EMU_RECORD_GIF = EMU_BASE + 5
|
||||
&ubyte EMU_RECORD_WAV = EMU_BASE + 6
|
||||
&ubyte EMU_CMDKEYS_DISABLED = EMU_BASE + 7
|
||||
&ubyte EMU_CPUCLK_L = EMU_BASE + 8 ; write: reset cpu clock to 0
|
||||
&ubyte EMU_CPUCLK_M = EMU_BASE + 9 ; write: outputs "User debug 1: $xx"
|
||||
&ubyte EMU_CPUCLK_H = EMU_BASE + 10 ; write: outputs "User debug 2: $xx"
|
||||
&ubyte EMU_CPUCLK_U = EMU_BASE + 11 ; write: outputs as character to console
|
||||
; 12 is unused for now
|
||||
&ubyte EMU_KEYMAP = EMU_BASE + 13
|
||||
&ubyte EMU_EMU_DETECT1 = EMU_BASE + 14
|
||||
&ubyte EMU_EMU_DETECT2 = EMU_BASE + 15
|
||||
|
||||
sub is_emulator() -> bool {
|
||||
; Test for emulator presence.
|
||||
; It is recommended to only use the other debug routines if this returns true.
|
||||
return EMU_EMU_DETECT1=='1' and EMU_EMU_DETECT2=='6'
|
||||
}
|
||||
|
||||
asmsub console_write(str isoString @R0) clobbers(Y) {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
beq +
|
||||
sta p8_EMU_CPUCLK_U
|
||||
iny
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub console_chrout(ubyte char @A) {
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_U
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub console_value1(ubyte value @A) {
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_M
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub console_value2(ubyte value @A) {
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_H
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
floats {
|
||||
; ---- this block contains C-64 compatible floating point related functions ----
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
@@ -26,7 +28,7 @@ romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
|
||||
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!]
|
||||
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!] see parse_f() instead
|
||||
|
||||
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
@@ -42,7 +44,7 @@ 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
|
||||
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 $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 ROUND or FADDH first to round instead of trunc
|
||||
romsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $fe36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
|
||||
@@ -97,14 +99,11 @@ asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
phx
|
||||
sty $c4 ; facmo ($64 on c128)
|
||||
sta $c5 ; facmo+1 ($65 on c128)
|
||||
ldx #$90
|
||||
sec
|
||||
jsr FLOATC
|
||||
plx
|
||||
rts
|
||||
jmp FLOATC
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -137,16 +136,26 @@ asmsub FREADUY (ubyte value @Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub parse_f(str value @AY) -> float @FAC1 {
|
||||
; -- parse a string value of a number to float in FAC1
|
||||
; warning: uses an internal BASIC routine that may be rom version dependent
|
||||
; ($ddf2 is inside the routine for VAL at $ddef)
|
||||
%asm {{
|
||||
sta $a9
|
||||
sty $aa
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
jmp $ddf2
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
||||
|
||||
sub rndf() -> float {
|
||||
%asm {{
|
||||
phx
|
||||
lda #1
|
||||
jsr RND_0
|
||||
plx
|
||||
rts
|
||||
jmp RND_0
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
+285
-452
File diff suppressed because it is too large.
Load diff
@@ -5,11 +5,13 @@
|
||||
; Wraps the graphics functions that are in ROM.
|
||||
; Only lo-res 320x240 256 color mode for now.
|
||||
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
|
||||
; For other resolutions or other color modes, use the "gfx2" module instead. (which is Cx16-specific)
|
||||
; For other resolutions or other color modes, use the "gfx2" or "monogfx" module instead. (which is Cx16-specific)
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
|
||||
|
||||
graphics {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 240
|
||||
|
||||
@@ -27,7 +29,7 @@ graphics {
|
||||
sub disable_bitmap_mode() {
|
||||
; enables text mode, erase the text screen, color white
|
||||
void cx16.screen_mode(0, false)
|
||||
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
|
||||
txt.clear_screen()
|
||||
}
|
||||
|
||||
|
||||
@@ -48,22 +50,22 @@ graphics {
|
||||
cx16.GRAPH_draw_line(x1, y1, x2, y2)
|
||||
}
|
||||
|
||||
sub fillrect(uword x, uword y, uword width, uword height) {
|
||||
cx16.GRAPH_draw_rect(x, y, width, height, 0, 1)
|
||||
sub fillrect(uword xx, uword yy, uword width, uword height) {
|
||||
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, 1)
|
||||
}
|
||||
|
||||
sub rect(uword x, uword y, uword width, uword height) {
|
||||
cx16.GRAPH_draw_rect(x, y, width, height, 0, 0)
|
||||
sub rect(uword xx, uword yy, uword width, uword height) {
|
||||
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, 0)
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, uword y, uword length) {
|
||||
sub horizontal_line(uword xx, uword yy, uword length) {
|
||||
if length
|
||||
cx16.GRAPH_draw_line(x, y, x+length-1, y)
|
||||
cx16.GRAPH_draw_line(xx, yy, xx+length-1, yy)
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, uword y, uword height) {
|
||||
sub vertical_line(uword xx, uword yy, uword height) {
|
||||
if height
|
||||
cx16.GRAPH_draw_line(x, y, x, y+height-1)
|
||||
cx16.GRAPH_draw_line(xx, yy, xx, yy+height-1)
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
|
||||
@@ -0,0 +1,791 @@
|
||||
; Monochrome Bitmap pixel graphics routines for the CommanderX16
|
||||
; Using the full-screen 640x480 and 320x240 screen modes, in 1 bpp mode (black/white).
|
||||
;
|
||||
; No text layer is currently shown, but text can be drawn as part of the bitmap itself.
|
||||
; For color bitmap graphics, see the gfx2 library.
|
||||
;
|
||||
; NOTE: a lot of the code here is similar or the same to that in gfx2
|
||||
|
||||
monogfx {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; read-only control variables:
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
bool dont_stipple_flag = true ; set to false to enable stippling mode
|
||||
|
||||
sub lores() {
|
||||
; enable 320*240 bitmap mode
|
||||
cx16.VERA_CTRL=0
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
cx16.VERA_DC_VSCALE = 64
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
width = 320
|
||||
height = 240
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub hires() {
|
||||
; enable 640*480 bitmap mode
|
||||
cx16.VERA_CTRL=0
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 128
|
||||
cx16.VERA_DC_VSCALE = 128
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
width = 640
|
||||
height = 480
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub textmode() {
|
||||
; back to normal text mode
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
cbm.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
stipple(false)
|
||||
position(0, 0)
|
||||
when width {
|
||||
320 -> {
|
||||
repeat 240/2/8
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
640 -> {
|
||||
repeat 480/8
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
}
|
||||
position(0, 0)
|
||||
}
|
||||
|
||||
sub stipple(bool enable) {
|
||||
dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
if rwidth==0 or rheight==0
|
||||
return
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
if rheight==1
|
||||
return
|
||||
horizontal_line(xx, yy+rheight-1, rwidth, draw)
|
||||
vertical_line(xx, yy+1, rheight-2, draw)
|
||||
if rwidth==1
|
||||
return
|
||||
vertical_line(xx+rwidth-1, yy+1, rheight-2, draw)
|
||||
}
|
||||
|
||||
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
if rwidth==0
|
||||
return
|
||||
repeat rheight {
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
ubyte[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111]
|
||||
ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111]
|
||||
|
||||
if length==0
|
||||
return
|
||||
if length<=8 {
|
||||
; just use 2 byte writes with shifted mask
|
||||
position2(xx,yy,true)
|
||||
%asm {{
|
||||
ldy length
|
||||
lda masked_ends,y
|
||||
sta cx16.r0L ; save left byte
|
||||
stz P8ZP_SCRATCH_B1
|
||||
lda xx
|
||||
and #7
|
||||
beq +
|
||||
tay
|
||||
lda cx16.r0L
|
||||
- lsr a
|
||||
ror P8ZP_SCRATCH_B1
|
||||
dey
|
||||
bne -
|
||||
sta cx16.r0L ; new left byte
|
||||
+
|
||||
lda dont_stipple_flag
|
||||
bne _dontstipple
|
||||
; determine stipple pattern
|
||||
lda yy
|
||||
and #1
|
||||
beq +
|
||||
lda #%10101010
|
||||
bne ++
|
||||
+ lda #%01010101
|
||||
+ sta P8ZP_SCRATCH_REG
|
||||
lda cx16.r0L
|
||||
and P8ZP_SCRATCH_REG
|
||||
sta cx16.r0L
|
||||
lda P8ZP_SCRATCH_B1
|
||||
and P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_B1
|
||||
_dontstipple
|
||||
lda draw
|
||||
beq _clear
|
||||
lda cx16.r0L ; left byte
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
lda P8ZP_SCRATCH_B1 ; right byte
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
_clear
|
||||
lda cx16.r0L ; left byte
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
lda P8ZP_SCRATCH_B1 ; right byte
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
ubyte separate_pixels = (8-lsb(xx)) & 7
|
||||
if separate_pixels {
|
||||
if dont_stipple_flag {
|
||||
position(xx,yy)
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
|
||||
if draw
|
||||
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
|
||||
else
|
||||
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
|
||||
xx += separate_pixels
|
||||
} else {
|
||||
repeat separate_pixels {
|
||||
plot(xx, yy, draw)
|
||||
xx++
|
||||
}
|
||||
}
|
||||
length -= separate_pixels
|
||||
}
|
||||
if length {
|
||||
position(xx, yy)
|
||||
separate_pixels = lsb(length) & 7
|
||||
xx += length & $fff8
|
||||
%asm {{
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lda draw
|
||||
bne +
|
||||
ldy #0 ; black
|
||||
bra _loop
|
||||
+ lda dont_stipple_flag
|
||||
beq _stipple
|
||||
ldy #255 ; don't stipple
|
||||
bra _loop
|
||||
_stipple lda yy
|
||||
and #1 ; determine stipple pattern to use
|
||||
bne +
|
||||
ldy #%01010101
|
||||
bra _loop
|
||||
+ ldy #%10101010
|
||||
_loop lda length
|
||||
ora length+1
|
||||
beq _done
|
||||
sty cx16.VERA_DATA0
|
||||
lda length
|
||||
bne +
|
||||
dec length+1
|
||||
+ dec length
|
||||
bra _loop
|
||||
_done
|
||||
}}
|
||||
|
||||
if dont_stipple_flag {
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
|
||||
if draw
|
||||
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
|
||||
else
|
||||
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
|
||||
} else {
|
||||
repeat separate_pixels {
|
||||
plot(xx, yy, draw)
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
|
||||
cx16.r15L = monogfx.plot.maskbits[xx as ubyte & 7] ; bitmask
|
||||
if draw {
|
||||
if dont_stipple_flag {
|
||||
; draw continuous line.
|
||||
position2(xx,yy,true)
|
||||
if width==320
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; draw stippled line.
|
||||
if (xx ^ yy)&1==0 {
|
||||
yy++
|
||||
lheight--
|
||||
}
|
||||
lheight++ ; because it is divided by 2 later, don't round off the last pixel
|
||||
position2(xx,yy,true)
|
||||
if width==320
|
||||
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
|
||||
repeat lheight/2 {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
position2(xx,yy,true)
|
||||
cx16.r15 = ~cx16.r15 ; erase pixels
|
||||
if width==320
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub set_both_strides(ubyte stride) {
|
||||
stride <<= 4
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, bool draw) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
cx16.r0 = x1
|
||||
x1 = x2
|
||||
x2 = cx16.r0
|
||||
cx16.r0 = y1
|
||||
y1 = y2
|
||||
y2 = cx16.r0
|
||||
}
|
||||
word @zp dx = (x2 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
|
||||
word @zp d = 0
|
||||
cx16.r1L = true ; 'positive_ix'
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
cx16.r1L = false
|
||||
}
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
cx16.r14 = x1 ; internal plot X
|
||||
|
||||
if dx >= dy {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14++
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14--
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14++
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14--
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
ubyte[8] @shared maskbits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
if draw {
|
||||
; solid color or perhaps stipple
|
||||
%asm {{
|
||||
lda xx
|
||||
eor yy
|
||||
ora dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
prepare()
|
||||
%asm {{
|
||||
tsb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; only erase
|
||||
prepare()
|
||||
%asm {{
|
||||
trb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
|
||||
sub prepare() {
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
if width==320
|
||||
xx += yy*(320/8)
|
||||
else
|
||||
xx += yy*(640/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda maskbits,y
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
if width==320
|
||||
xx += yy*(320/8)
|
||||
else
|
||||
xx += yy*(640/8)
|
||||
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda plot.maskbits,y
|
||||
and cx16.VERA_DATA0
|
||||
beq +
|
||||
lda #1
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub fill(word @zp xx, word @zp yy, bool draw) {
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 48
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
byte[MAXDEPTH] @shared stack_dy
|
||||
cx16.r12L = 0 ; stack pointer
|
||||
word x1
|
||||
word x2
|
||||
byte dy
|
||||
cx16.r10L = draw as ubyte
|
||||
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
|
||||
if cx16.r12L==MAXDEPTH
|
||||
return
|
||||
cx16.r0s = sy+sdy
|
||||
if cx16.r0s>=0 and cx16.r0s<=height-1 {
|
||||
;; stack_xl[cx16.r12L] = sxl
|
||||
;; stack_xr[cx16.r12L] = sxr
|
||||
;; stack_y[cx16.r12L] = sy
|
||||
;; stack_dy[cx16.r12L] = sdy
|
||||
;; cx16.r12L++
|
||||
%asm {{
|
||||
ldy cx16.r12L
|
||||
lda sxl
|
||||
sta stack_xl_lsb,y
|
||||
lda sxl+1
|
||||
sta stack_xl_msb,y
|
||||
lda sxr
|
||||
sta stack_xr_lsb,y
|
||||
lda sxr+1
|
||||
sta stack_xr_msb,y
|
||||
lda sy
|
||||
sta stack_y_lsb,y
|
||||
lda sy+1
|
||||
sta stack_y_msb,y
|
||||
ldy cx16.r12L
|
||||
lda sdy
|
||||
sta stack_dy,y
|
||||
inc cx16.r12L
|
||||
}}
|
||||
}
|
||||
}
|
||||
sub pop_stack() {
|
||||
;; cx16.r12L--
|
||||
;; x1 = stack_xl[cx16.r12L]
|
||||
;; x2 = stack_xr[cx16.r12L]
|
||||
;; y = stack_y[cx16.r12L]
|
||||
;; dy = stack_dy[cx16.r12L]
|
||||
%asm {{
|
||||
dec cx16.r12L
|
||||
ldy cx16.r12L
|
||||
lda stack_xl_lsb,y
|
||||
sta x1
|
||||
lda stack_xl_msb,y
|
||||
sta x1+1
|
||||
lda stack_xr_lsb,y
|
||||
sta x2
|
||||
lda stack_xr_msb,y
|
||||
sta x2+1
|
||||
lda stack_y_lsb,y
|
||||
sta yy
|
||||
lda stack_y_msb,y
|
||||
sta yy+1
|
||||
ldy cx16.r12L
|
||||
lda stack_dy,y
|
||||
sta dy
|
||||
}}
|
||||
yy+=dy
|
||||
}
|
||||
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
|
||||
if cx16.r11L == cx16.r10L
|
||||
return
|
||||
if xx<0 or xx>width-1 or yy<0 or yy>height-1
|
||||
return
|
||||
push_stack(xx, xx, yy, 1)
|
||||
push_stack(xx, xx, yy + 1, -1)
|
||||
word left = 0
|
||||
while cx16.r12L {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L
|
||||
xx--
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
|
||||
else
|
||||
goto skip
|
||||
|
||||
left = xx + 1
|
||||
if left < x1
|
||||
push_stack(left, x1 - 1, yy, -dy)
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
cx16.r9 = xx
|
||||
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L
|
||||
xx++
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
|
||||
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
|
||||
xx++
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
}
|
||||
|
||||
sub position(uword @zp xx, uword yy) {
|
||||
if width==320
|
||||
cx16.r0 = yy*(320/8)
|
||||
else
|
||||
cx16.r0 = yy*(640/8)
|
||||
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
|
||||
}
|
||||
|
||||
sub position2(uword @zp xx, uword yy, bool also_port_1) {
|
||||
position(xx, yy)
|
||||
if also_port_1
|
||||
cx16.vaddr_clone(0)
|
||||
}
|
||||
|
||||
const ubyte charset_bank = $1
|
||||
const uword charset_addr = $f000 ; in bank 1, so $1f000
|
||||
|
||||
sub text_charset(ubyte charset) {
|
||||
; -- select the text charset to use with the text() routine
|
||||
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
||||
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
|
||||
cx16.screen_set_charset(charset, 0)
|
||||
}
|
||||
|
||||
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
|
||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
uword chardataptr
|
||||
ubyte[8] @shared char_bitmap_bytes_left
|
||||
ubyte[8] @shared char_bitmap_bytes_right
|
||||
|
||||
cx16.r3 = sctextptr
|
||||
while @(cx16.r3) {
|
||||
chardataptr = charset_addr + @(cx16.r3) * $0008
|
||||
; copy the character bitmap into RAM
|
||||
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
|
||||
%asm {{
|
||||
; pre-shift the bits
|
||||
lda text.xx
|
||||
and #7
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
- lda cx16.VERA_DATA0
|
||||
stz P8ZP_SCRATCH_REG
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
cpx #0
|
||||
beq +
|
||||
- lsr a
|
||||
ror P8ZP_SCRATCH_REG
|
||||
dex
|
||||
bne -
|
||||
+ sta char_bitmap_bytes_left,y
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta char_bitmap_bytes_right,y
|
||||
iny
|
||||
cpy #8
|
||||
bne --
|
||||
}}
|
||||
; left part of shifted char
|
||||
position2(xx, yy, true)
|
||||
set_autoincrs()
|
||||
if draw {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_left,y
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_left,y
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
; right part of shifted char
|
||||
if lsb(xx) & 7 {
|
||||
position2(xx+8, yy, true)
|
||||
set_autoincrs()
|
||||
if draw {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_right,y
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_right,y
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
}
|
||||
cx16.r3++
|
||||
xx += 8
|
||||
}
|
||||
|
||||
sub set_autoincrs() {
|
||||
; set autoincrements to go to next pixel row (40 or 80 increment)
|
||||
if width==320 {
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
|
||||
} else {
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
|
||||
%asm {{
|
||||
cmp #0
|
||||
beq +
|
||||
lda #255
|
||||
+ ldy #80
|
||||
- sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
|
||||
|
||||
palette {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
uword vera_palette_ptr
|
||||
ubyte c
|
||||
ubyte cc
|
||||
|
||||
sub set_color(ubyte index, uword color) {
|
||||
vera_palette_ptr = $fa00+(index as uword * 2)
|
||||
@@ -79,13 +80,13 @@ palette {
|
||||
sub set_grayscale() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
c=0
|
||||
cc=0
|
||||
repeat 16 {
|
||||
cx16.vpoke(1, vera_palette_ptr, c)
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, c)
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
c += $11
|
||||
cc += $11
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,11 +151,11 @@ palette {
|
||||
sub set_c64pepto() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for c in 0 to 15 {
|
||||
uword cc = C64_colorpalette_pepto[c]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_pepto[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
@@ -163,11 +164,11 @@ palette {
|
||||
sub set_c64light() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for c in 0 to 15 {
|
||||
uword cc = C64_colorpalette_light[c]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_light[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
@@ -176,11 +177,11 @@ palette {
|
||||
sub set_c64dark() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for c in 0 to 15 {
|
||||
uword cc = C64_colorpalette_dark[c]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_dark[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
%import syslib
|
||||
|
||||
psg {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; $1F9C0 - $1F9FF 16 blocks of 4 PSG registers (16 voices)
|
||||
; 00 frequency word LSB
|
||||
; 01 frequency word MSB. freqword = HERZ / 0.3725290298461914
|
||||
@@ -14,31 +16,26 @@ psg {
|
||||
const ubyte LEFT = %01000000
|
||||
const ubyte RIGHT = %10000000
|
||||
|
||||
sub voice(ubyte voice_num, ubyte channel, ubyte volume, ubyte waveform, ubyte pulsewidth) {
|
||||
sub voice(ubyte voice_num, ubyte channel, ubyte vol, ubyte waveform, ubyte pulsewidth) {
|
||||
; -- Enables a 'voice' on the PSG.
|
||||
; voice_num = 0-15, the voice number.
|
||||
; channel = either LEFT or RIGHT or (LEFT|RIGHT). Specifies the stereo channel(s) to use.
|
||||
; volume = 0-63, the starting volume for the voice
|
||||
; vol = 0-63, the starting volume for the voice
|
||||
; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE.
|
||||
; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE.
|
||||
envelope_states[voice_num] = 255
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
sys.irqsafe_set_irqd()
|
||||
cx16.r0 = $f9c2 + voice_num * 4
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
||||
cx16.VERA_ADDR_M = msb(cx16.r0)
|
||||
cx16.VERA_ADDR_H = 1
|
||||
cx16.VERA_DATA0 = channel | volume
|
||||
cx16.VERA_DATA0 = channel | vol
|
||||
cx16.VERA_ADDR_L++
|
||||
cx16.VERA_DATA0 = waveform | pulsewidth
|
||||
envelope_volumes[voice_num] = mkword(volume, 0)
|
||||
envelope_maxvolumes[voice_num] = volume
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
envelope_volumes[voice_num] = mkword(vol, 0)
|
||||
envelope_maxvolumes[voice_num] = vol
|
||||
sys.irqsafe_clear_irqd()
|
||||
}
|
||||
|
||||
; sub freq_hz(ubyte voice_num, float hertz) {
|
||||
@@ -53,10 +50,7 @@ psg {
|
||||
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
|
||||
; (https://github.com/x16community/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
|
||||
; Write freq MSB first and then LSB to reduce the chance on clicks
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
sys.irqsafe_set_irqd()
|
||||
cx16.r0 = $f9c1 + voice_num * 4
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
||||
@@ -65,9 +59,7 @@ psg {
|
||||
cx16.VERA_DATA0 = msb(vera_freq)
|
||||
cx16.VERA_ADDR_L--
|
||||
cx16.VERA_DATA0 = lsb(vera_freq)
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
sys.irqsafe_clear_irqd()
|
||||
}
|
||||
|
||||
sub volume(ubyte voice_num, ubyte vol) {
|
||||
@@ -181,7 +173,7 @@ psg {
|
||||
}
|
||||
|
||||
ubyte[16] envelope_states
|
||||
uword[16] envelope_volumes ; scaled by 256
|
||||
uword[16] @split envelope_volumes ; scaled by 256
|
||||
ubyte[16] envelope_attacks
|
||||
ubyte[16] envelope_sustains
|
||||
ubyte[16] envelope_releases
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
; Simple routines to control sprites.
|
||||
; They're not written for high performance, but for simplicity.
|
||||
; That's why they control 1 sprite at a time. The exception is pos_batch().
|
||||
; which is quite efficient to update sprite positions of multiple sprites in one go.
|
||||
|
||||
; note: sprites z-order will be in front of all layers.
|
||||
; note: collision mask is not supported here yet.
|
||||
|
||||
sprites {
|
||||
const ubyte SIZE_8 = 0
|
||||
const ubyte SIZE_16 = 1
|
||||
const ubyte SIZE_32 = 2
|
||||
const ubyte SIZE_64 = 3
|
||||
const ubyte COLORS_16 = 0
|
||||
const ubyte COLORS_256 = 128
|
||||
const uword VERA_SPRITEREGS = $fc00 ; $1fc00
|
||||
uword @zp sprite_reg
|
||||
|
||||
sub init(ubyte spritenum,
|
||||
ubyte databank, uword dataaddr,
|
||||
ubyte width_flag, ubyte height_flag,
|
||||
ubyte colors_flag, ubyte palette_offset_idx) {
|
||||
hide(spritenum)
|
||||
cx16.VERA_DC_VIDEO |= %01000000 ; enable sprites globally
|
||||
dataaddr >>= 5
|
||||
dataaddr |= (databank as uword)<<11
|
||||
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
|
||||
cx16.vpoke(1, sprite_reg, lsb(dataaddr)) ; address 12:5
|
||||
cx16.vpoke(1, sprite_reg+1, colors_flag | msb(dataaddr)) ; 4 bpp + address 16:13
|
||||
cx16.vpoke(1, sprite_reg+6, %00001100) ; z depth %11 = in front of both layers, no flips
|
||||
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset_idx>>4) ; 64x64 pixels, palette offset
|
||||
}
|
||||
|
||||
sub data(ubyte spritenum, ubyte bank, uword addr) {
|
||||
addr >>= 5
|
||||
addr |= (bank as uword)<<11
|
||||
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
|
||||
cx16.vpoke(1, sprite_reg, lsb(addr)) ; address 12:5
|
||||
cx16.vpoke_mask(1, sprite_reg+1, %11110000, msb(addr)) ; address 16:13
|
||||
}
|
||||
|
||||
sub pos(ubyte spritenum, word xpos, word ypos) {
|
||||
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
|
||||
cx16.vpoke(1, sprite_reg, lsb(xpos))
|
||||
cx16.vpoke(1, sprite_reg+1, msb(xpos))
|
||||
cx16.vpoke(1, sprite_reg+2, lsb(ypos))
|
||||
cx16.vpoke(1, sprite_reg+3, msb(ypos))
|
||||
}
|
||||
|
||||
sub pos_batch(ubyte first_spritenum, ubyte num_sprites, uword xpositions_ptr, uword ypositions_ptr) {
|
||||
; -- note: the x and y positions word arrays must be regular arrays, they cannot be @split arrays!
|
||||
sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
|
||||
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
|
||||
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
|
||||
repeat num_sprites {
|
||||
cx16.VERA_DATA0 = @(xpositions_ptr)
|
||||
xpositions_ptr ++
|
||||
cx16.VERA_DATA1 = @(xpositions_ptr)
|
||||
xpositions_ptr ++
|
||||
}
|
||||
sprite_reg += 2
|
||||
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
|
||||
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
|
||||
repeat num_sprites {
|
||||
cx16.VERA_DATA0 = @(ypositions_ptr)
|
||||
ypositions_ptr ++
|
||||
cx16.VERA_DATA1 = @(ypositions_ptr)
|
||||
ypositions_ptr ++
|
||||
}
|
||||
}
|
||||
|
||||
sub setx(ubyte spritenum, word xpos) {
|
||||
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
|
||||
cx16.vpoke(1, sprite_reg, lsb(xpos))
|
||||
cx16.vpoke(1, sprite_reg+1, msb(xpos))
|
||||
}
|
||||
|
||||
sub sety(ubyte spritenum, word ypos) {
|
||||
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
|
||||
cx16.vpoke(1, sprite_reg, lsb(ypos))
|
||||
cx16.vpoke(1, sprite_reg+1, msb(ypos))
|
||||
}
|
||||
|
||||
sub getx(ubyte spritenum) -> word {
|
||||
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
|
||||
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
|
||||
}
|
||||
|
||||
sub gety(ubyte spritenum) -> word {
|
||||
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
|
||||
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
|
||||
}
|
||||
|
||||
sub hide(ubyte spritenum) {
|
||||
pos(spritenum, -64, -64)
|
||||
}
|
||||
|
||||
sub flipx(ubyte spritenum, bool flipped) {
|
||||
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111110, flipped)
|
||||
}
|
||||
|
||||
sub flipy(ubyte spritenum, bool flipped) {
|
||||
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111101, flipped<<1)
|
||||
}
|
||||
|
||||
sub set_palette_offset(ubyte spritenum, ubyte offset) {
|
||||
sprite_reg = VERA_SPRITEREGS + 7 + spritenum*$0008
|
||||
cx16.vpoke_mask(1, sprite_reg, %11110000, offset>>4)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
; Prog8 definitions for the CommanderX16
|
||||
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||
|
||||
c64 {
|
||||
cbm {
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
; Commodore (CBM) common variables, vectors and kernal routines
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
@@ -13,12 +15,12 @@ romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
@@ -30,55 +32,51 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- utility
|
||||
|
||||
asmsub STOP2() -> ubyte @A {
|
||||
asmsub STOP2() clobbers(X) -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.STOP
|
||||
jsr cbm.STOP
|
||||
beq +
|
||||
plx
|
||||
lda #0
|
||||
rts
|
||||
+ plx
|
||||
lda #1
|
||||
+ lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience. Also avoids ram bank issue for irqs.
|
||||
%asm {{
|
||||
phx
|
||||
php
|
||||
sei
|
||||
jsr c64.RDTIM
|
||||
jsr cbm.RDTIM
|
||||
plp
|
||||
cli
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -87,38 +85,40 @@ asmsub RDTIM16() -> uword @AY {
|
||||
|
||||
cx16 {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; irq, system and hardware vectors:
|
||||
&uword IERROR = $0300
|
||||
&uword IMAIN = $0302
|
||||
&uword ICRNCH = $0304
|
||||
&uword IQPLOP = $0306
|
||||
&uword IGONE = $0308
|
||||
&uword IEVAL = $030a
|
||||
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||
&uword IERROR = $0300
|
||||
&uword IMAIN = $0302
|
||||
&uword ICRNCH = $0304
|
||||
&uword IQPLOP = $0306
|
||||
&uword IGONE = $0308
|
||||
&uword IEVAL = $030a
|
||||
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||
; $0313 is unused.
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword IOPEN = $031a
|
||||
&uword ICLOSE = $031c
|
||||
&uword ICHKIN = $031e
|
||||
&uword ICKOUT = $0320
|
||||
&uword ICLRCH = $0322
|
||||
&uword IBASIN = $0324
|
||||
&uword IBSOUT = $0326
|
||||
&uword ISTOP = $0328
|
||||
&uword IGETIN = $032a
|
||||
&uword ICLALL = $032c
|
||||
&uword KEYHDL = $032e ; keyboard scan code handler see examples/cx16/keyboardhandler.p8
|
||||
&uword ILOAD = $0330
|
||||
&uword ISAVE = $0332
|
||||
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword IOPEN = $031a
|
||||
&uword ICLOSE = $031c
|
||||
&uword ICHKIN = $031e
|
||||
&uword ICKOUT = $0320
|
||||
&uword ICLRCH = $0322
|
||||
&uword IBASIN = $0324
|
||||
&uword IBSOUT = $0326
|
||||
&uword ISTOP = $0328
|
||||
&uword IGETIN = $032a
|
||||
&uword ICLALL = $032c
|
||||
&uword KEYHDL = $032e ; keyboard scan code handler see examples/cx16/keyboardhandler.p8
|
||||
&uword ILOAD = $0330
|
||||
&uword ISAVE = $0332
|
||||
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
@@ -267,6 +267,17 @@ cx16 {
|
||||
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
|
||||
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
|
||||
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
|
||||
|
||||
; experimental Vera FX registers: (depends on particular value set in VERA_CTRL!!!)
|
||||
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_MULT = VERA_BASE + $000C
|
||||
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000A
|
||||
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000B
|
||||
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000C
|
||||
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009 ; (DCSEL=6)
|
||||
|
||||
|
||||
; VERA_PSG_BASE = $1F9C0
|
||||
; VERA_PALETTE_BASE = $1FA00
|
||||
; VERA_SPRITES_BASE = $1FC00
|
||||
@@ -274,44 +285,44 @@ cx16 {
|
||||
; I/O
|
||||
|
||||
const uword VIA1_BASE = $9f00 ;VIA 6522 #1
|
||||
&ubyte via1prb = VIA1_BASE + 0
|
||||
&ubyte via1pra = VIA1_BASE + 1
|
||||
&ubyte via1ddrb = VIA1_BASE + 2
|
||||
&ubyte via1ddra = VIA1_BASE + 3
|
||||
&ubyte via1t1l = VIA1_BASE + 4
|
||||
&ubyte via1t1h = VIA1_BASE + 5
|
||||
&ubyte via1t1ll = VIA1_BASE + 6
|
||||
&ubyte via1t1lh = VIA1_BASE + 7
|
||||
&ubyte via1t2l = VIA1_BASE + 8
|
||||
&ubyte via1t2h = VIA1_BASE + 9
|
||||
&ubyte via1sr = VIA1_BASE + 10
|
||||
&ubyte via1acr = VIA1_BASE + 11
|
||||
&ubyte via1pcr = VIA1_BASE + 12
|
||||
&ubyte via1ifr = VIA1_BASE + 13
|
||||
&ubyte via1ier = VIA1_BASE + 14
|
||||
&ubyte via1ora = VIA1_BASE + 15
|
||||
&ubyte via1prb = VIA1_BASE + 0
|
||||
&ubyte via1pra = VIA1_BASE + 1
|
||||
&ubyte via1ddrb = VIA1_BASE + 2
|
||||
&ubyte via1ddra = VIA1_BASE + 3
|
||||
&ubyte via1t1l = VIA1_BASE + 4
|
||||
&ubyte via1t1h = VIA1_BASE + 5
|
||||
&ubyte via1t1ll = VIA1_BASE + 6
|
||||
&ubyte via1t1lh = VIA1_BASE + 7
|
||||
&ubyte via1t2l = VIA1_BASE + 8
|
||||
&ubyte via1t2h = VIA1_BASE + 9
|
||||
&ubyte via1sr = VIA1_BASE + 10
|
||||
&ubyte via1acr = VIA1_BASE + 11
|
||||
&ubyte via1pcr = VIA1_BASE + 12
|
||||
&ubyte via1ifr = VIA1_BASE + 13
|
||||
&ubyte via1ier = VIA1_BASE + 14
|
||||
&ubyte via1ora = VIA1_BASE + 15
|
||||
|
||||
const uword VIA2_BASE = $9f10 ;VIA 6522 #2
|
||||
&ubyte via2prb = VIA2_BASE + 0
|
||||
&ubyte via2pra = VIA2_BASE + 1
|
||||
&ubyte via2ddrb = VIA2_BASE + 2
|
||||
&ubyte via2ddra = VIA2_BASE + 3
|
||||
&ubyte via2t1l = VIA2_BASE + 4
|
||||
&ubyte via2t1h = VIA2_BASE + 5
|
||||
&ubyte via2t1ll = VIA2_BASE + 6
|
||||
&ubyte via2t1lh = VIA2_BASE + 7
|
||||
&ubyte via2t2l = VIA2_BASE + 8
|
||||
&ubyte via2t2h = VIA2_BASE + 9
|
||||
&ubyte via2sr = VIA2_BASE + 10
|
||||
&ubyte via2acr = VIA2_BASE + 11
|
||||
&ubyte via2pcr = VIA2_BASE + 12
|
||||
&ubyte via2ifr = VIA2_BASE + 13
|
||||
&ubyte via2ier = VIA2_BASE + 14
|
||||
&ubyte via2ora = VIA2_BASE + 15
|
||||
&ubyte via2prb = VIA2_BASE + 0
|
||||
&ubyte via2pra = VIA2_BASE + 1
|
||||
&ubyte via2ddrb = VIA2_BASE + 2
|
||||
&ubyte via2ddra = VIA2_BASE + 3
|
||||
&ubyte via2t1l = VIA2_BASE + 4
|
||||
&ubyte via2t1h = VIA2_BASE + 5
|
||||
&ubyte via2t1ll = VIA2_BASE + 6
|
||||
&ubyte via2t1lh = VIA2_BASE + 7
|
||||
&ubyte via2t2l = VIA2_BASE + 8
|
||||
&ubyte via2t2h = VIA2_BASE + 9
|
||||
&ubyte via2sr = VIA2_BASE + 10
|
||||
&ubyte via2acr = VIA2_BASE + 11
|
||||
&ubyte via2pcr = VIA2_BASE + 12
|
||||
&ubyte via2ifr = VIA2_BASE + 13
|
||||
&ubyte via2ier = VIA2_BASE + 14
|
||||
&ubyte via2ora = VIA2_BASE + 15
|
||||
|
||||
; YM-2151 sound chip
|
||||
&ubyte YM_ADDRESS = $9f40
|
||||
&ubyte YM_DATA = $9f41
|
||||
&ubyte YM_DATA = $9f41
|
||||
|
||||
const uword extdev = $9f60
|
||||
|
||||
@@ -320,17 +331,16 @@ cx16 {
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
|
||||
; supported C128 additions
|
||||
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, ubyte getCurrent @Pc) clobbers(A, X, Y) -> ubyte @Pc
|
||||
romsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) clobbers(X, Y) -> ubyte @A, bool @Pc ; note: X,Y size result is not supported, use SCREEN or get_screen_mode routine for that
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff6e = jsrfar() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7d = primm()
|
||||
romsub $ff7d = PRIMM()
|
||||
|
||||
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
|
||||
|
||||
@@ -340,14 +350,14 @@ romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||
romsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
|
||||
romsub $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3) clobbers(A,X,Y)
|
||||
romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, ubyte fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, bool fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5) clobbers(A,X,Y)
|
||||
romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, bool fill @Pc) clobbers(A,X,Y)
|
||||
romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
|
||||
romsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
|
||||
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte char @A) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_next_char(ubyte char @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
|
||||
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, bool is_control @Pc) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte character @A) clobbers(A,X,Y)
|
||||
romsub $ff41 = GRAPH_put_next_char(ubyte character @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
|
||||
|
||||
; framebuffer
|
||||
romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||
@@ -367,24 +377,26 @@ romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A
|
||||
romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
|
||||
|
||||
; misc
|
||||
romsub $fec6 = i2c_read_byte(ubyte device @X, ubyte offset @Y) clobbers (X,Y) -> ubyte @A, ubyte @Pc
|
||||
romsub $fec9 = i2c_write_byte(ubyte device @X, ubyte offset @Y, ubyte data @A) clobbers (A,X,Y) -> ubyte @Pc
|
||||
romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc
|
||||
romsub $FEBA = BSAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; like cbm.SAVE, but omits the 2-byte prg header
|
||||
romsub $fec6 = i2c_read_byte(ubyte device @X, ubyte offset @Y) clobbers (X,Y) -> ubyte @A, bool @Pc
|
||||
romsub $fec9 = i2c_write_byte(ubyte device @X, ubyte offset @Y, ubyte data @A) clobbers (A,X,Y) -> bool @Pc
|
||||
romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, bool apply_mask @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
|
||||
romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
|
||||
romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
|
||||
romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
|
||||
romsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
|
||||
romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
|
||||
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
|
||||
romsub $fede = console_put_char(ubyte character @A, bool wrapping @Pc) clobbers(A,X,Y)
|
||||
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
|
||||
romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
|
||||
romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
@@ -394,6 +406,7 @@ romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length
|
||||
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
||||
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
||||
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
|
||||
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
|
||||
romsub $ff68 = mouse_config(ubyte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
|
||||
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
@@ -401,18 +414,37 @@ romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
||||
|
||||
; Audio (bank 10)
|
||||
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
|
||||
; Audio (rom bank 10)
|
||||
romsub $C04B = psg_init() clobbers(A,X,Y) ; (re)init Vera PSG
|
||||
romsub $C063 = ym_init() clobbers(A,X,Y) -> bool @Pc ; (re)init YM chip
|
||||
romsub $C066 = ym_loaddefpatches() clobbers(A,X,Y) -> bool @Pc ; load default YM patches
|
||||
romsub $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)initialize both vera PSG and YM audio chips
|
||||
; TODO: add more of the audio routines?
|
||||
|
||||
|
||||
asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) -> bool @Pc {
|
||||
; -- convenience wrapper for screen_mode() to just set a new mode (and return success)
|
||||
%asm {{
|
||||
clc
|
||||
jmp screen_mode
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_screen_mode() -> byte @A, byte @X, byte @Y {
|
||||
; -- convenience wrapper for screen_mode() to just get the current mode in A, and size in characters in X+Y
|
||||
; this does need a piece of inlined asm to call it ans store the result values if you call this from prog8 code
|
||||
; Note: you can also just do the SEC yourself and simply call screen_mode() directly,
|
||||
; or use the existing SCREEN kernal routine for just getting the size in characters.
|
||||
%asm {{
|
||||
sec
|
||||
jmp screen_mode
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub kbdbuf_clear() {
|
||||
; -- convenience helper routine to clear the keyboard buffer
|
||||
%asm {{
|
||||
- jsr c64.GETIN
|
||||
- jsr cbm.GETIN
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
@@ -429,15 +461,12 @@ asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub mouse_pos() -> ubyte @A {
|
||||
asmsub mouse_pos() clobbers(X) -> ubyte @A {
|
||||
; -- short wrapper around mouse_get() kernal routine:
|
||||
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status.
|
||||
%asm {{
|
||||
phx
|
||||
ldx #cx16.r0
|
||||
jsr cx16.mouse_get
|
||||
plx
|
||||
rts
|
||||
jmp cx16.mouse_get
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -475,7 +504,7 @@ inline asmsub getrambank() -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub numbanks() -> uword @AY {
|
||||
asmsub numbanks() clobbers(X) -> uword @AY {
|
||||
; -- 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:
|
||||
@@ -483,15 +512,13 @@ asmsub numbanks() -> uword @AY {
|
||||
; 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 {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.MEMTOP
|
||||
jsr cbm.MEMTOP
|
||||
ldy #0
|
||||
cmp #0
|
||||
bne +
|
||||
iny
|
||||
+ plx
|
||||
rts
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -500,7 +527,8 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
|
||||
; note: inefficient when reading multiple sequential bytes!
|
||||
%asm {{
|
||||
pha
|
||||
lda #1
|
||||
lda cx16.VERA_CTRL
|
||||
ora #1
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
and #1
|
||||
@@ -514,6 +542,8 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
|
||||
|
||||
asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) {
|
||||
; -- setup the VERA's data address register 0 or 1
|
||||
; with optional auto increment or decrement of 1.
|
||||
; Note that the vaddr_autoincr() and vaddr_autodecr() routines allow to set all possible strides, not just 1.
|
||||
%asm {{
|
||||
and #1
|
||||
pha
|
||||
@@ -537,6 +567,105 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
|
||||
; -- clones Vera addresses from the given source port to the other one.
|
||||
; leaves CTRL on the destination port.
|
||||
%asm {{
|
||||
sta VERA_CTRL
|
||||
ldx VERA_ADDR_L
|
||||
ldy VERA_ADDR_H
|
||||
phy
|
||||
ldy VERA_ADDR_M
|
||||
eor #1
|
||||
sta VERA_CTRL
|
||||
stx VERA_ADDR_L
|
||||
sty VERA_ADDR_M
|
||||
ply
|
||||
sty VERA_ADDR_H
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vaddr_autoincr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword autoIncrAmount @R2) clobbers(A,Y) {
|
||||
; -- setup the VERA's data address register 0 or 1
|
||||
; including setting up optional auto increment amount.
|
||||
; Specifiying an unsupported amount results in amount of zero. See the Vera docs about what amounts are possible.
|
||||
%asm {{
|
||||
jsr _setup
|
||||
lda cx16.r2H
|
||||
ora cx16.r2L
|
||||
beq +
|
||||
jsr _determine_incr_bits
|
||||
+ ora P8ZP_SCRATCH_REG
|
||||
sta cx16.VERA_ADDR_H
|
||||
rts
|
||||
|
||||
_setup and #1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda cx16.r1
|
||||
and #1
|
||||
sta cx16.VERA_CTRL
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
rts
|
||||
|
||||
_determine_incr_bits
|
||||
lda cx16.r2H
|
||||
bne _large
|
||||
lda cx16.r2L
|
||||
ldy #13
|
||||
- cmp _strides_lsb,y
|
||||
beq +
|
||||
dey
|
||||
bpl -
|
||||
+ tya
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
rts
|
||||
_large ora cx16.r2L
|
||||
cmp #1 ; 256
|
||||
bne +
|
||||
lda #9<<4
|
||||
rts
|
||||
+ cmp #2 ; 512
|
||||
bne +
|
||||
lda #10<<4
|
||||
rts
|
||||
+ cmp #65 ; 320
|
||||
bne +
|
||||
lda #14<<4
|
||||
rts
|
||||
+ cmp #130 ; 640
|
||||
bne +
|
||||
lda #15<<4
|
||||
rts
|
||||
+ lda #0
|
||||
rts
|
||||
_strides_lsb .byte 0,1,2,4,8,16,32,64,128,255,255,40,80,160,255,255
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vaddr_autodecr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, uword autoDecrAmount @R2) clobbers(A,Y) {
|
||||
; -- setup the VERA's data address register 0 or 1
|
||||
; including setting up optional auto decrement amount.
|
||||
; Specifiying an unsupported amount results in amount of zero. See the Vera docs about what amounts are possible.
|
||||
%asm {{
|
||||
jsr vaddr_autoincr._setup
|
||||
lda cx16.r2H
|
||||
ora cx16.r2L
|
||||
beq +
|
||||
jsr vaddr_autoincr._determine_incr_bits
|
||||
ora #%00001000 ; autodecrement
|
||||
+ ora P8ZP_SCRATCH_REG
|
||||
sta cx16.VERA_ADDR_H
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||
; -- write a single byte to VERA's video memory
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
@@ -627,152 +756,32 @@ asmsub vpoke_mask(ubyte bank @A, uword address @R0, ubyte mask @X, ubyte value @
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- system stuff -----
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
jsr cx16.mouse_config ; disable mouse
|
||||
cld
|
||||
lda VERA_DC_VIDEO
|
||||
and #%00000111 ; retain chroma + output mode
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda #$0a
|
||||
sta $01 ; rom bank 10 (audio)
|
||||
jsr audio_init ; silence
|
||||
stz $01 ; rom bank 0 (kernal)
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
lda VERA_DC_VIDEO
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta VERA_DC_VIDEO ; restore old output mode
|
||||
lda #$90 ; black
|
||||
jsr c64.CHROUT
|
||||
lda #1
|
||||
sta $00 ; select ram bank 1
|
||||
jsr c64.CHROUT ; swap fg/bg
|
||||
lda #$9e ; yellow
|
||||
jsr c64.CHROUT
|
||||
lda #147 ; clear screen
|
||||
jsr c64.CHROUT
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
sta _cx16_vreg_storage,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_cx16_vreg_storage
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
.word 0,0,0,0,0,0,0,0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_virtual_registers() clobbers(A,Y) {
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda save_virtual_registers._cx16_vreg_storage,y
|
||||
sta cx16.r0,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
sei
|
||||
lda cx16.CINV
|
||||
sta restore_irq._orig_irqvec
|
||||
lda cx16.CINV+1
|
||||
sta restore_irq._orig_irqvec+1
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #1
|
||||
sta $00 ; ram bank 1
|
||||
lda #4
|
||||
sta $01 ; rom bank 4 (basic)
|
||||
stz $2d ; hack to reset machine code monitor bank to 0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
rol a
|
||||
sta _use_kernal
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta cx16.CINV
|
||||
lda #>_irq_handler
|
||||
sta cx16.CINV+1
|
||||
lda cx16.VERA_IEN
|
||||
ora #%00000001 ; enable the vsync irq
|
||||
sta cx16.VERA_IEN
|
||||
cli
|
||||
rts
|
||||
|
||||
_irq_handler jsr _irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr _irq_handler_end
|
||||
lda _use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
lda #1
|
||||
sta cx16.VERA_ISR ; clear Vera Vsync irq status
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
_irq_handler_init
|
||||
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta IRQ_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta IRQ_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta IRQ_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta IRQ_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
; Set X to the bottom 32 bytes of the evaluation stack, to HOPEFULLY not clobber it.
|
||||
; This leaves 128-32=96 stack entries for the main program, and 32 stack entries for the IRQ handler.
|
||||
; We assume IRQ handlers don't contain complex expressions taking up more than that.
|
||||
ldx #32
|
||||
cld
|
||||
rts
|
||||
|
||||
_irq_handler_end
|
||||
; restore all zp scratch registers
|
||||
lda IRQ_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda IRQ_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda IRQ_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda IRQ_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda IRQ_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda IRQ_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
|
||||
IRQ_SCRATCH_ZPB1 .byte 0
|
||||
IRQ_SCRATCH_ZPREG .byte 0
|
||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub save_vera_context() clobbers(A) {
|
||||
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
|
||||
@@ -823,6 +832,159 @@ asmsub restore_vera_context() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte target = 16 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
jsr cx16.mouse_config ; disable mouse
|
||||
cld
|
||||
lda cx16.VERA_DC_VIDEO
|
||||
and #%00000111 ; retain chroma + output mode
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda #$0a
|
||||
sta $01 ; rom bank 10 (audio)
|
||||
jsr cx16.audio_init ; silence
|
||||
stz $01 ; rom bank 0 (kernal)
|
||||
jsr cbm.IOINIT
|
||||
jsr cbm.RESTOR
|
||||
jsr cbm.CINT
|
||||
lda cx16.VERA_DC_VIDEO
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta cx16.VERA_DC_VIDEO ; restore old output mode
|
||||
lda #$90 ; black
|
||||
jsr cbm.CHROUT
|
||||
lda #1
|
||||
jsr cbm.CHROUT ; swap fg/bg
|
||||
lda #$9e ; yellow
|
||||
jsr cbm.CHROUT
|
||||
lda #147 ; clear screen
|
||||
jsr cbm.CHROUT
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
sei
|
||||
lda cx16.CINV
|
||||
sta restore_irq._orig_irqvec
|
||||
lda cx16.CINV+1
|
||||
sta restore_irq._orig_irqvec+1
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
lda #1
|
||||
sta $00 ; ram bank 1
|
||||
lda #4
|
||||
sta $01 ; rom bank 4 (basic)
|
||||
stz $2d ; hack to reset machine code monitor bank to 0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
rol a
|
||||
sta _use_kernal
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta cx16.CINV
|
||||
lda #>_irq_handler
|
||||
sta cx16.CINV+1
|
||||
lda cx16.VERA_IEN
|
||||
ora #%00000001 ; enable the vsync irq
|
||||
sta cx16.VERA_IEN
|
||||
cli
|
||||
rts
|
||||
|
||||
_irq_handler jsr _irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr _irq_handler_end
|
||||
lda _use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
lda #1
|
||||
sta cx16.VERA_ISR ; clear Vera Vsync irq status
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
_irq_handler_init
|
||||
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta IRQ_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta IRQ_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta IRQ_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta IRQ_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
cld
|
||||
rts
|
||||
|
||||
_irq_handler_end
|
||||
; restore all zp scratch registers
|
||||
lda IRQ_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda IRQ_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda IRQ_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda IRQ_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda IRQ_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda IRQ_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
|
||||
IRQ_SCRATCH_ZPB1 .byte 0
|
||||
IRQ_SCRATCH_ZPREG .byte 0
|
||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
@@ -893,23 +1055,14 @@ asmsub set_rasterline(uword line @AY) {
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 16 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; 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 {{
|
||||
sei
|
||||
ldx #$42
|
||||
ldy #1
|
||||
tya
|
||||
ldy #2
|
||||
lda #0
|
||||
jsr cx16.i2c_write_byte
|
||||
bra *
|
||||
}}
|
||||
@@ -925,27 +1078,25 @@ sys {
|
||||
void cx16.i2c_write_byte($42, $05, activity)
|
||||
}
|
||||
|
||||
asmsub wait(uword jiffies @AY) {
|
||||
asmsub wait(uword jiffies @AY) clobbers(X) {
|
||||
; --- 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: this routine cannot be used from inside a irq handler
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
|
||||
_loop lda P8ZP_SCRATCH_W1
|
||||
ora P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
plx
|
||||
rts
|
||||
|
||||
+ sei
|
||||
jsr c64.RDTIM
|
||||
jsr cbm.RDTIM
|
||||
cli
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- sei
|
||||
jsr c64.RDTIM
|
||||
jsr cbm.RDTIM
|
||||
cli
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
@@ -1086,10 +1237,23 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_set_irqd() {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub irqsafe_clear_irqd() {
|
||||
%asm {{
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
|
||||
Loaded 100 of 315 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user