Compare commits

..
1 Commits
Author SHA1 Message Date
Irmen de Jong e55a675d2e release 8.14 2023-05-29 23:00:43 +02:00
351 changed files with 14053 additions and 23534 deletions

No files matched your search

+4 -9
View File
@@ -10,18 +10,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v2
- 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: Install 64tass
run: sudo apt-get update -y && sudo apt-get install -y 64tass
- name: Set up JDK 11
uses: actions/setup-java@v3
uses: actions/setup-java@v2
with:
java-version: 11
distribution: adopt
-3
View File
@@ -15,7 +15,6 @@ out/
parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
compiler/src/prog8/buildversion/*
*.py[cod]
*.egg
*.egg-info
@@ -30,8 +29,6 @@ parsetab.py
compiler/lib/
.gradle
**/BuildVersion.kt
/prog8compiler.jar
sd*.img
*.d64
+1 -1
View File
@@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" />
<option name="version" value="1.8.21-release-380" />
</component>
</project>
+17 -24
View File
@@ -1,26 +1,19 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0-sources.jar!/" />
</SOURCES>
</library>
<library name="KotlinJavaRuntime">
<CLASSES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
</SOURCES>
</library>
</component>
+3 -3
View File
@@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.13.1">
<properties maven-id="org.antlr:antlr4:4.12.0">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<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/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/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
View File
@@ -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.8.0" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.5.5" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.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.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.5.5/kotest-assertions-core-jvm-5.5.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/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/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.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.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.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/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!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+24
View File
@@ -0,0 +1,24 @@
<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>
+41 -28
View File
@@ -1,42 +1,55 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.8.0" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.5.5" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.8.0/kotest-runner-junit5-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.8.0/kotest-framework-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.5.5/kotest-runner-junit5-jvm-5.5.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.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/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.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.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.8.0/kotest-framework-engine-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.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$/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.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$/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$/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.8.0/kotest-framework-discovery-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.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.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.8.0/kotest-extensions-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.8.0/kotest-framework-concurrency-jvm-5.8.0.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$/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$/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.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!/" />
<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!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+2 -2
View File
@@ -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.6" />
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.5" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+6 -6
View File
@@ -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.18" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16" />
<CLASSES>
<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$/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$/org/jetbrains/annotations/13.0/annotations-13.0.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-stdlib-jdk7/1.6.20/kotlin-stdlib-jdk7-1.6.20.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+3 -3
View File
@@ -1,9 +1,9 @@
<component name="libraryTable">
<library name="slf4j.simple" type="repository">
<properties maven-id="org.slf4j:slf4j-simple:2.0.9" />
<properties maven-id="org.slf4j:slf4j-simple:2.0.7" />
<CLASSES>
<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!/" />
<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!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
-3
View File
@@ -16,9 +16,6 @@
</list>
</option>
</component>
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
-17
View File
@@ -14,21 +14,6 @@ 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:
@@ -74,8 +59,6 @@ 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)
+3 -3
View File
@@ -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.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
}
sourceSets {
main {
java {
srcDir "${project.projectDir}/src"
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDir "${project.projectDir}/res"
srcDirs = ["${project.projectDir}/res"]
}
}
}
+10 -15
View File
@@ -80,16 +80,6 @@ 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
}
}
}
@@ -196,10 +186,13 @@ 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)
require(length==onetimeInitializationArrayValue.size)
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
}
}
if(onetimeInitializationStringValue!=null) {
require(dt == DataType.STR)
@@ -210,7 +203,8 @@ 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,
@@ -232,11 +226,12 @@ 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,
+6 -10
View File
@@ -26,6 +26,8 @@ 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))
@@ -74,9 +76,10 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
numElements = value.value.length + 1 // include the terminating 0-byte
}
is PtArray -> {
initialArray = makeInitialArray(value)
val array = makeInitialArray(value)
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
initialString = null
numElements = initialArray.size
numElements = array.size
require(node.arraySize?.toInt()==numElements)
}
else -> {
@@ -91,9 +94,6 @@ 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,11 +125,7 @@ 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 -> {
if(it.isFromArrayElement)
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name)
}
is PtAddressOf -> StArrayElement(null, it.identifier.name)
is PtIdentifier -> StArrayElement(null, it.name)
is PtNumber -> StArrayElement(it.number, null)
else -> throw AssemblyError("invalid array element $it")
+10 -15
View File
@@ -37,17 +37,16 @@ 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
get() {
var namedParent: PtNode = this.parent
return if(namedParent is PtProgram)
name
else {
while (namedParent !is PtNamedNode)
namedParent = namedParent.parent
namedParent.scopedName + "." + name
}
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
}
}
}
@@ -64,9 +63,7 @@ class PtProgram(
children.asSequence().filterIsInstance<PtBlock>()
fun entrypoint(): 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?
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start") } as PtSub?
}
@@ -74,8 +71,6 @@ 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
+8 -26
View File
@@ -1,6 +1,9 @@
package prog8.code.ast
import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.core.Encoding
import prog8.code.core.NumericDatatypes
import prog8.code.core.Position
import java.util.*
import kotlin.math.abs
import kotlin.math.round
@@ -24,18 +27,8 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
infix fun isSameAs(other: PtExpression): Boolean {
return when(this) {
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 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 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
@@ -58,7 +51,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.splitWords==target.array!!.splitWords
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index
}
else -> false
}
@@ -115,12 +108,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
val identifier: 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
get() = children.single() as PtIdentifier
}
@@ -130,12 +118,6 @@ 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)
}
+4 -4
View File
@@ -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)} ${if(node.splitWords) "[splitwords]" else ""}"
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {
val str = if(node.void) "void " else ""
@@ -96,14 +96,13 @@ 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}] $split ${node.name}"
"${eltType.name.lowercase()}[${node.arraySize}] ${node.name}"
}
else if(node.type in ArrayDatatypes) {
val eltType = ArrayToElementTypes.getValue(node.type)
"${eltType.name.lowercase()}[] $split ${node.name}"
"${eltType.name.lowercase()}[] ${node.name}"
}
else
"${node.type.name.lowercase()} ${node.name}"
@@ -126,6 +125,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
else
"->"
}
else -> throw InternalCompilerException("unrecognised ast node $node")
}
}
@@ -68,10 +68,9 @@ 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),
@@ -80,59 +79,35 @@ 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),
"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),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
"abs" to FSignature(true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD),
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
// normal functions follow:
"sizeof" to FSignature(true, listOf(FParam("object", DataType.entries.toTypedArray())), DataType.UBYTE),
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
"sqrt" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), null),
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"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),
"sqrt16" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
"divmodw" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
"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),
"peekf" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.FLOAT),
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
"pokef" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.FLOAT))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
"pushw" to FSignature(false, listOf(FParam("value", WordDatatypes)), null),
"rsave" to FSignature(false, emptyList(), null),
"rsavex" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"rrestorex" to FSignature(false, emptyList(), null),
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
@@ -8,29 +8,24 @@ class CompilationOptions(val output: OutputType,
val launcher: CbmPrgLauncherType,
val zeropage: ZeropageType,
val zpReserved: List<UIntRange>,
val zpAllowed: List<UIntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var warnSymbolShadowing: Boolean = false,
var slowCodegenWarnings: 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 varsHighBank: Int? = null,
var splitWordArrays: Boolean = false,
var breakpointCpuInstruction: Boolean = false,
var varsHigh: Boolean = false,
var useNewExprCode: Boolean = false,
var evalStackBaseAddress: UInt? = null,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
) {
init {
compTarget.machine.initializeMemoryAreas(this)
}
companion object {
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
}
}
+18 -39
View File
@@ -1,20 +1,17 @@
package prog8.code.core
enum class DataType {
UBYTE, // pass by value 8 bits unsigned
BYTE, // pass by value 8 bits signed
UWORD, // pass by value 16 bits unsigned
WORD, // pass by value 16 bits signed
LONG, // pass by value 32 bits signed
FLOAT, // pass by value machine dependent
BOOL, // pass by value bit 0 of a 8 bit byte
UBYTE, // pass by value
BYTE, // pass by value
UWORD, // pass by value
WORD, // pass by value
FLOAT, // pass by value
BOOL, // pass by value
STR, // pass by reference
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;
@@ -24,12 +21,11 @@ enum class DataType {
*/
infix fun isAssignableTo(targetType: DataType) =
when(this) {
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
WORD -> targetType.oneOf(WORD, LONG, FLOAT)
LONG -> targetType.oneOf(LONG, FLOAT)
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL)
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
UWORD -> targetType.oneOf(UWORD, FLOAT)
WORD -> targetType.oneOf(WORD, FLOAT)
FLOAT -> targetType.oneOf(FLOAT)
STR -> targetType.oneOf(STR, UWORD)
in ArrayDatatypes -> targetType == this
@@ -43,8 +39,7 @@ enum class DataType {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this == LONG -> other in ByteDatatypes+WordDatatypes
this == STR && other == UWORD || this == UWORD && other == STR -> false
this== STR && other== UWORD || this== UWORD && other== STR -> false
else -> true
}
@@ -79,13 +74,6 @@ enum class RegisterOrPair {
companion object {
val names by lazy { values().map { it.toString()} }
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
return when(cpu) {
CpuRegister.A -> A
CpuRegister.X -> X
CpuRegister.Y -> Y
}
}
}
fun asCpuRegister(): CpuRegister = when(this) {
@@ -126,19 +114,17 @@ enum class BranchCondition {
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
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 IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
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 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
@@ -149,8 +135,6 @@ 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
)
@@ -162,7 +146,6 @@ val ElementToArrayTypes = mapOf(
DataType.FLOAT to DataType.ARRAY_F,
DataType.BOOL to DataType.ARRAY_BOOL
)
val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
@@ -170,10 +153,6 @@ val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
val CpuRegisters = setOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
)
enum class OutputType {
@@ -3,6 +3,8 @@ package prog8.code.core
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
val supportedEncodings: Set<Encoding>
val defaultEncoding: Encoding
override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
@@ -3,7 +3,6 @@ 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,6 +13,8 @@ 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
@@ -27,4 +29,11 @@ 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" }
}
}
@@ -9,8 +9,6 @@ enum class Encoding(val prefix: String) {
}
interface IStringEncoding {
val defaultEncoding: Encoding
fun encodeString(str: String, encoding: Encoding): List<UByte>
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
}
@@ -42,13 +42,6 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
}
}
fun retainAllowed() {
synchronized(this) {
for(allowed in options.zpAllowed)
free.retainAll { it in allowed }
}
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
fun hasWordAvailable(): Boolean {
+2 -2
View File
@@ -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()
+7 -40
View File
@@ -3,7 +3,6 @@ package prog8.code.core
import java.io.File
import java.io.IOException
import java.nio.file.Path
import java.text.Normalizer
import kotlin.io.path.Path
import kotlin.io.path.readText
@@ -55,14 +54,12 @@ 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))
fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX)
!(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix))
}
/**
@@ -72,7 +69,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>"
}
@@ -95,7 +92,7 @@ sealed class SourceCode {
val normalized = path.normalize()
origin = relative(normalized).toString()
try {
text = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
text = normalized.readText()
name = normalized.toFile().nameWithoutExtension
} catch (nfx: java.nio.file.NoSuchFileException) {
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
@@ -113,7 +110,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
@@ -127,7 +124,7 @@ sealed class SourceCode {
)
}
val stream = object {}.javaClass.getResourceAsStream(normalized)
text = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
text = stream!!.reader().use { it.readText() }
name = Path(pathString).toFile().nameWithoutExtension
}
}
@@ -142,33 +139,3 @@ 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
}
}
@@ -7,6 +7,7 @@ import prog8.code.target.atari.AtariMachineDefinition
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = AtariMachineDefinition()
override val supportedEncodings = setOf(Encoding.ATASCII)
override val defaultEncoding = Encoding.ATASCII
companion object {
@@ -11,6 +11,7 @@ import prog8.code.target.cbm.CbmMemorySizer
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C128MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -11,6 +11,7 @@ import prog8.code.target.cbm.CbmMemorySizer
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C64MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -11,6 +11,7 @@ import prog8.code.target.cx16.CX16MachineDefinition
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = CX16MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -10,8 +10,6 @@ import prog8.code.target.cbm.PetsciiEncoding
object Encoder: IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
@@ -1,19 +0,0 @@
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 defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "pet32"
}
}
@@ -6,6 +6,7 @@ import prog8.code.target.virtual.VirtualMachineDefinition
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = VirtualMachineDefinition()
override val supportedEncodings = setOf(Encoding.ISO)
override val defaultEncoding = Encoding.ISO
companion object {
@@ -13,6 +13,10 @@ 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
@@ -48,7 +48,6 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -15,6 +15,10 @@ 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
@@ -69,7 +69,6 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -16,8 +16,12 @@ 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 = 0xd000u
override val BSSHIGHRAM_END = ESTACK_LO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
@@ -58,7 +62,7 @@ class C64MachineDefinition: IMachineDefinition {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0xc000u until 0xd000u)
golden = GoldenRam(compilerOptions, 0xc000u until ESTACK_LO)
}
}
@@ -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, 0xa6,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
@@ -75,8 +75,6 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
allocateCx16VirtualRegisters()
}
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
@@ -24,7 +24,7 @@ object PetsciiEncoding {
'\ufffe', // 0x0A -> UNDEFINED
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\n' , // 0x0D -> LINE FEED (RETURN)
'\r' , // 0x0D -> CARRIAGE 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)
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\n' , // 0x8D -> LINE FEED
'\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
'\n' , // 0x0D -> LINE FEED (RETURN)
'\r' , // 0x0D -> CARRIAGE 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)
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\n' , // 0x8D -> LINE FEED
'\u000f', // 0x8E -> SHIFT IN
'\ufffe', // 0x8F -> UNDEFINED
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
@@ -1061,7 +1061,6 @@ object PetsciiEncoding {
'}' -> '├'
'|' -> '│'
'\\' -> '╲'
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
else -> chr
}
@@ -1077,10 +1076,7 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
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}'")
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
}
}
}
@@ -1123,10 +1119,7 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
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}'")
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
}
}
}
@@ -15,6 +15,10 @@ 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.
@@ -60,7 +64,7 @@ class CX16MachineDefinition: IMachineDefinition {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0x0400u until 0x0800u)
golden = GoldenRam(compilerOptions, 0x0400u until ESTACK_LO)
}
}
@@ -40,6 +40,7 @@ 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()
@@ -47,8 +48,8 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
}
@@ -1,55 +0,0 @@
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.
}
}
@@ -1,59 +0,0 @@
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()
retainAllowed()
}
override fun allocateCx16VirtualRegisters() {
TODO("Not known if PET can put the virtual regs in ZP")
}
}
@@ -12,9 +12,11 @@ class VirtualMachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 8 // 64-bits double
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
@@ -22,8 +24,8 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
val bits = num.toDouble().toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val bits = num.toFloat().toBits().toUInt()
val hexStr = bits.toString(16).padStart(8, '0')
val parts = hexStr.chunked(2).map { "\$" + it }
return parts.joinToString(", ")
}
+4 -4
View File
@@ -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.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
}
sourceSets {
main {
java {
srcDir "${project.projectDir}/src"
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDir "${project.projectDir}/res"
srcDirs = ["${project.projectDir}/res"]
}
}
test {
+267 -361
View File
@@ -3,7 +3,6 @@ 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.*
@@ -15,178 +14,18 @@ import kotlin.io.path.writeLines
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
class AsmGen6502: ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
): IAssemblyProgram? {
val st = if(prefixSymbols) prefixSymbols(program, options, symbolTable) else symbolTable
val asmgen = AsmGen6502Internal(program, st, options, errors)
val asmgen = AsmGen6502Internal(program, symbolTable, 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,
@@ -201,12 +40,12 @@ class AsmGen6502Internal (
private val allocator = VariableAllocator(symbolTable, options, errors)
private val assemblyLines = mutableListOf<String>()
private val breakpointLabels = mutableListOf<String>()
private val forloopsAsmGen = ForLoopsAsmGen(this, zeropage)
private val forloopsAsmGen = ForLoopsAsmGen(program, 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 anyExprGen = AnyExprAsmGen(this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
private val assignmentAsmGen = AssignmentAsmGen(program, symbolTable, this, allocator)
private val expressionsAsmGen = ExpressionsAsmGen(program, this, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
fun compileToAssembly(): IAssemblyProgram? {
@@ -238,19 +77,8 @@ class AsmGen6502Internal (
internal fun isTargetCpu(cpu: CpuType) = options.compTarget.machine.cpu == cpu
private var lastSourceLineNumber: Int = -1
internal fun outputSourceLine(node: PtNode) {
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)
out(" ;\tsrc line: ${node.position.file}:${node.position.line}")
}
internal fun out(str: String, splitlines: Boolean = true) {
@@ -403,6 +231,32 @@ 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")
@@ -427,6 +281,24 @@ 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 -> {
@@ -490,63 +362,111 @@ class AsmGen6502Internal (
internal fun loadScaledArrayIndexIntoRegister(
expr: PtArrayIndexer,
elementDt: DataType,
register: CpuRegister
register: CpuRegister,
addOneExtra: Boolean = false
) {
val reg = register.toString().lowercase()
val indexnum = expr.index.asConstInteger()
if (indexnum != null) {
val indexValue = if(expr.splitWords)
indexnum
else
indexnum * options.compTarget.memorySize(elementDt)
val indexValue = indexnum * options.compTarget.memorySize(elementDt) + if (addOneExtra) 1 else 0
out(" ld$reg #$indexValue")
return
}
if(expr.splitWords) {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
return
}
val indexVar = expr.index as? PtIdentifier
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.index.position}")
when (elementDt) {
in ByteDatatypes -> {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
}
in WordDatatypes -> {
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
out(" asl a")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
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 -> {
}
}
}
}
DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
out("""
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG"""
)
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
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")
}
} 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")
}
}
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")
}
else -> throw AssemblyError("weird dt")
}
}
internal fun translateBuiltinFunctionCallExpression(bfc: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?): DataType? =
builtinFunctionsAsmGen.translateFunctioncallExpression(bfc, resultRegister)
@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 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)
@@ -563,7 +483,7 @@ class AsmGen6502Internal (
when(reg) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes, true)
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
@@ -575,36 +495,9 @@ class AsmGen6502Internal (
}
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
// don't use translateExpression() to avoid evalstack
when (target.datatype) {
in ByteDatatypes -> {
if (value.asConstInteger()==0) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${target.asmVarname}")
else
out("stz ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
val address = target.memory!!.address.asConstInteger()
if(address!=null) {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${address.toHex()}")
else
out(" stz ${address.toHex()}")
return
}
}
TargetStorageKind.REGISTER -> {
val zero = PtNumber(DataType.UBYTE, 0.0, value.position)
zero.parent = value
assignExpressionToRegister(zero, target.register!!, false)
return
}
else -> { }
}
}
assignExpressionToRegister(value, RegisterOrPair.A)
assignRegister(RegisterOrPair.A, target)
}
@@ -653,6 +546,7 @@ 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()
@@ -780,48 +674,65 @@ class AsmGen6502Internal (
private fun repeatWordCount(count: Int, stmt: PtRepeatLoop) {
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
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
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
out("""
lda #<$count
ldy #>$count
sta $counterVar
sty $counterVar+1
$repeatLabel""")
translate(stmt.statements)
out("""
dec $counterVar
bne $repeatLabel
dec $counterVar+1
bne $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""")
}
}
private fun repeatWordCountInAY(endLabel: String, stmt: PtRepeatLoop) {
// note: A/Y must have been loaded with the number of iterations!
// the iny + double dec is microoptimization of the 16 bit loop
// no need to explicitly test for 0 iterations as this is done in the countdown logic below
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)
out("""
cmp #0
beq +
iny
+ sta $counterVar
sty $counterVar+1
ora $counterVar+1
beq $endLabel
$repeatLabel""")
sta $counterVar
sty $counterVar+1
$repeatLabel lda $counterVar
bne +
lda $counterVar+1
beq $endLabel
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
""")
translate(stmt.statements)
out("""
dec $counterVar
bne $repeatLabel
dec $counterVar+1
bne $repeatLabel""")
jmp(repeatLabel)
out(endLabel)
}
private fun repeatByteCount(count: Int, stmt: PtRepeatLoop) {
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
@@ -884,7 +795,7 @@ $repeatLabel""")
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
@@ -1024,10 +935,7 @@ $repeatLabel""")
}
private fun translate(asm: PtInlineAssembly) {
if(asm.isIR)
throw AssemblyError("%asm containing IR code cannot be translated to 6502 assembly")
else
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
}
private fun translate(incbin: PtIncludeBinary) {
@@ -1049,15 +957,6 @@ $repeatLabel""")
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) {
@@ -1075,6 +974,20 @@ $repeatLabel""")
}
}
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) {
@@ -1110,10 +1023,18 @@ $repeatLabel""")
}
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: PtExpression): Pair<PtExpression, PtExpression>? {
if (pointerOffsetExpr !is PtBinaryExpression) return null
val operator = pointerOffsetExpr.operator
val left = pointerOffsetExpr.left
val right = pointerOffsetExpr.right
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 (operator != "+") return null
val leftDt = left.type
val rightDt = right.type
@@ -1225,7 +1146,10 @@ $repeatLabel""")
}
internal fun findSubroutineParameter(name: String, asmgen: AsmGen6502Internal): PtSubroutineParameter? {
val stScope = asmgen.symbolTable.lookup(name) ?: return null
val stScope = asmgen.symbolTable.lookup(name)
require(stScope!=null) {
"invalid name lookup $name"
}
val node = stScope.astNode
if(node is PtSubroutineParameter)
return node
@@ -1703,7 +1627,7 @@ $repeatLabel""")
}
else if (left is PtMemoryByte) {
return if(rightConstVal.number.toInt()!=0) {
translateDirectMemReadExpressionToRegA(left)
translateDirectMemReadExpressionToRegAorStack(left, false)
code("#${rightConstVal.number.toInt()}")
}
else
@@ -1850,7 +1774,7 @@ $repeatLabel""")
out(" beq $jumpIfFalseLabel")
}
else if (left is PtMemoryByte) {
translateDirectMemReadExpressionToRegA(left)
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number.toInt()}")
else
@@ -2011,7 +1935,7 @@ $repeatLabel""")
out(" bne $jumpIfFalseLabel")
}
else if (left is PtMemoryByte) {
translateDirectMemReadExpressionToRegA(left)
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number.toInt()}")
else
@@ -2175,7 +2099,7 @@ $repeatLabel""")
}
else if (left is PtMemoryByte) {
if(rightConstVal.number.toInt()!=0) {
translateDirectMemReadExpressionToRegA(left)
translateDirectMemReadExpressionToRegAorStack(left, false)
code("#${rightConstVal.number.toInt()}")
}
return
@@ -2338,7 +2262,7 @@ $repeatLabel""")
out(" bne $jumpIfFalseLabel")
}
else if (left is PtMemoryByte) {
translateDirectMemReadExpressionToRegA(left)
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number.toInt()}")
else
@@ -2384,7 +2308,7 @@ $repeatLabel""")
out(" beq $jumpIfFalseLabel")
}
else if (left is PtMemoryByte) {
translateDirectMemReadExpressionToRegA(left)
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number.toInt()}")
else
@@ -2449,7 +2373,8 @@ $repeatLabel""")
cmp #<$number
bne $jumpIfFalseLabel
cpy #>$number
bne $jumpIfFalseLabel""")
bne $jumpIfFalseLabel
""")
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -2457,21 +2382,18 @@ $repeatLabel""")
cmp ${asmVariableName(right)}
bne $jumpIfFalseLabel
cpy ${asmVariableName(right)}+1
bne $jumpIfFalseLabel""")
bne $jumpIfFalseLabel
""")
}
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
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""")
}
out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel
""")
}
else -> {
if(left.isSimple()) {
@@ -2552,16 +2474,12 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
}
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
}
else -> {
if(left.isSimple()) {
@@ -2846,14 +2764,22 @@ $repeatLabel""")
+""")
}
internal fun translateDirectMemReadExpressionToRegA(expr: PtMemoryByte) {
internal fun translateDirectMemReadExpressionToRegAorStack(expr: PtMemoryByte, pushResultOnEstack: Boolean) {
fun assignViaExprEval() {
assignExpressionToVariable(expr.address, "P8ZP_SCRATCH_W2", DataType.UWORD)
if (isTargetCpu(CpuType.CPU65c02)) {
out(" lda (P8ZP_SCRATCH_W2)")
if (pushResultOnEstack) {
out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
} else {
out(" lda (P8ZP_SCRATCH_W2)")
}
} else {
out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
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")
}
}
}
@@ -2861,14 +2787,22 @@ $repeatLabel""")
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(tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
if(pushResultOnEstack)
out(" sta P8ESTACK_LO,x | dex")
} else {
assignViaExprEval()
}
}
@@ -2887,12 +2821,8 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
val name = asmSymbolName(left.identifier)
if(left.isFromArrayElement) {
TODO("address-of array element $name at ${left.position}")
} else {
code("#>$name", "#<$name")
return true
}
code("#>$name", "#<$name")
return true
}
is PtIdentifier -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
@@ -2940,12 +2870,8 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
code("#>$name", "#<$name")
return true
}
code("#>$name", "#<$name")
return true
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -3072,18 +2998,6 @@ $repeatLabel""")
}
}
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())
@@ -3107,17 +3021,6 @@ $repeatLabel""")
return "prog8_label_asm_${generatedLabelSequenceNumber}_$postfix"
}
fun assignConstFloatToPointerAY(number: PtNumber) {
val floatConst = allocator.getFloatAsmConst(number.number)
out("""
pha
lda #<$floatConst
sta P8ZP_SCRATCH_W1
lda #>$floatConst
sta P8ZP_SCRATCH_W1+1
pla
jsr floats.copy_float""")
}
}
/**
@@ -3127,6 +3030,9 @@ $repeatLabel""")
* 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,7 +15,21 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
var linesByFour = getLinesBy(lines, 4)
var mods = optimizeIncDec(linesByFour)
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)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@@ -29,21 +43,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
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)
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@@ -90,6 +90,44 @@ 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,
@@ -479,63 +517,3 @@ 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,8 +1,9 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtAsmSub
import prog8.code.ast.*
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
@@ -10,19 +11,50 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (order Y,X,A),
// 3) single CPU registers (X last), except A,
// 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
regsWithoutA.forEach {
if(it.value.first.registerOrPair != RegisterOrPair.X)
order += it.index
}
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
rest.forEach { order += it.index }
regA.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
fun asmsub6502ArgsHaveRegisterClobberRisk(
args: List<PtExpression>,
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
): Boolean {
fun isClobberRisk(expr: PtExpression): Boolean {
when (expr) {
is PtArrayIndexer -> {
return params.any {
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is PtBuiltinFunctionCall -> {
if (expr.name == "lsb" || expr.name == "msb")
return isClobberRisk(expr.args[0])
if (expr.name == "mkword")
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple()
}
else -> return !expr.isSimple()
}
}
return args.size>1 && args.any { isClobberRisk(it) }
}
@@ -1,8 +1,14 @@
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(
@@ -22,20 +28,15 @@ internal class AssemblyProgram(
val assemblerCommand: List<String>
when (compTarget.name) {
in setOf("c64", "c128", "cx16", "pet32") -> {
in setOf("c64", "c128", "cx16") -> {
// 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", "-Werror",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-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")
@@ -64,15 +65,10 @@ internal class AssemblyProgram(
// TODO are these options okay for atari?
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", // "-Werror", "-Wno-strict-bool"
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--no-monitor"
)
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
@@ -134,3 +130,18 @@ 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)
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,929 @@
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,6 +6,26 @@ 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,10 +5,9 @@ import prog8.code.ast.*
import prog8.code.core.*
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(
private val asmgen: AsmGen6502Internal,
private val zeropage: Zeropage
) {
internal class ForLoopsAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val zeropage: Zeropage) {
internal fun translate(stmt: PtForLoop) {
val iterableDt = stmt.iterable.type
@@ -52,35 +51,7 @@ internal class ForLoopsAsmGen(
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, 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.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt))
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
asmgen.out("""
@@ -98,35 +69,7 @@ $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.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.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt))
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
if(stepsize>0) {
@@ -157,9 +100,8 @@ $modifiedLabel cmp #0 ; modified
stepsize == 1 || stepsize == -1 -> {
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@@ -192,9 +134,8 @@ $modifiedLabel2 cmp #0 ; modified
// (u)words, step >= 2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@@ -241,25 +182,41 @@ $endLabel""")
// (u)words, step <= -2
val varname = asmgen.asmVariableName(stmt.variable)
assignLoopvarWord(stmt, range)
assignLoopvar(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
txa
$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
$modifiedLabel2 cmp #0 ; modified
lda $varname+1
$modifiedLabel sbc #0 ; modified
@@ -267,6 +224,7 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
}
}
}
}
@@ -276,56 +234,6 @@ $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")
@@ -383,7 +291,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 {
@@ -424,46 +332,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""") },
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""") },
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
@@ -721,7 +590,7 @@ $loopLabel""")
asmgen.loopEndLabels.pop()
}
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
asmgen.assignExpressionToVariable(
range.from,
asmgen.asmVariableName(stmt.variable),
@@ -11,10 +11,42 @@ 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)
@@ -31,8 +63,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(sub is PtAsmSub) {
argumentsViaRegisters(sub, call)
if (sub.inline) {
// inline the subroutine. (regardless of optimization settings!)
if (sub.inline && asmgen.options.optimize) {
// inline the subroutine.
// 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)
@@ -69,62 +101,40 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
return when(arg) {
is PtBuiltinFunctionCall -> {
if (arg.name == "lsb" || arg.name == "msb")
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
return !arg.isSimple()
}
is PtAddressOf -> false
is PtIdentifier -> false
is PtMachineRegister -> false
is PtMemoryByte -> false
is PtNumber -> false
else -> true
}
}
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
val registersUsed = mutableListOf<RegisterOrStatusflag>();
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}
fun usedY() = registersUsed.any {it.registerOrPair==RegisterOrPair.Y || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.XY}
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
optimalEvalOrder.forEach {
val param = sub.parameters[it]
val arg = call.args[it]
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
else if(registersUsed.any {it.statusflag!=null}) {
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
}
else {
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
if(usedX()) asmgen.restoreRegisterStack(CpuRegister.X, true)
used
}
} else {
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
registerArgsViaCpuStackEvaluation(call, sub)
} else {
asmsub6502ArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
}
}
}
}
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
if(callee.parameters.isEmpty())
return
// use the cpu hardware stack as intermediate storage for the arguments.
val argOrder = asmsub6502ArgsEvalOrder(callee)
argOrder.reversed().forEach {
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
}
argOrder.forEach {
val param = callee.parameters[it]
asmgen.popCpuStack(callee, param.second, param.first)
}
}
private fun argumentViaVariable(sub: PtSub, parameter: PtSubroutineParameter, value: PtExpression) {
// pass parameter via a regular variable (not via registers)
if(!isArgumentTypeCompatible(value.type, parameter.type))
@@ -134,7 +144,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
asmgen.assignExpressionToVariable(value, varName, parameter.type)
}
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
// pass argument via a register parameter
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
throw AssemblyError("argument type incompatible")
@@ -154,7 +164,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 boolean param needs to be set last, right before the jsr
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is PtNumber -> {
@@ -163,7 +173,6 @@ 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
@@ -174,11 +183,15 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" ror a")
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
return RegisterOrStatusflag(null, statusflag)
}
else {
// via register or register pair
@@ -211,7 +224,6 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
}
return RegisterOrStatusflag(register, null)
}
}
@@ -233,5 +245,3 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
return false
}
}
@@ -13,6 +13,7 @@ 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)
@@ -64,59 +65,20 @@ 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 -> {
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 ByteDatatypes -> 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)")
@@ -127,47 +89,36 @@ internal class PostIncrDecrAsmGen(private val program: PtProgram, private val as
}
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.X)
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
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")
}
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,9 +33,8 @@ internal class ProgramAndVarsGen(
internal fun generate() {
header()
val allBlocks = program.allBlocks()
if(allBlocks.first().name != "p8_main" && allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main' or 'p8_main'")
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
if(errors.noErrors()) {
program.allBlocks().forEach { block2asm(it) }
@@ -75,6 +74,8 @@ 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()) {
@@ -103,15 +104,15 @@ internal class ProgramAndVarsGen(
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint\t; assembly code starts here")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
CbmPrgLauncherType.NONE -> {
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
}
}
@@ -119,8 +120,8 @@ internal class ProgramAndVarsGen(
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
if(!options.noSysInit)
asmgen.out(" jsr sys.init_system")
asmgen.out(" jsr sys.init_system_phase2")
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
}
@@ -138,24 +139,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 p8_main.p8_start")
asmgen.out(" jmp sys.cleanup_at_exit")
asmgen.out(" jsr main.start")
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
}
"c64" -> {
asmgen.out(" jsr p8_main.p8_start | lda #31 | sta $01")
asmgen.out(" jsr main.start | lda #31 | sta $01")
if(!options.noSysInit)
asmgen.out(" jmp sys.cleanup_at_exit")
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
else
asmgen.out(" rts")
}
"c128" -> {
asmgen.out(" jsr p8_main.p8_start | lda #0 | sta ${"$"}ff00")
asmgen.out(" jsr main.start | lda #0 | sta ${"$"}ff00")
if(!options.noSysInit)
asmgen.out(" jmp sys.cleanup_at_exit")
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
else
asmgen.out(" rts")
}
else -> asmgen.jmp("p8_main.p8_start")
else -> asmgen.jmp("main.start")
}
}
@@ -174,8 +175,7 @@ internal class ProgramAndVarsGen(
private fun footer() {
asmgen.out("; bss sections")
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
if(options.varsHighBank!=null) {
if(options.varsHigh) {
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
}
@@ -269,7 +269,9 @@ internal class ProgramAndVarsGen(
internal fun translateAsmSubroutine(sub: PtAsmSub) {
if(sub.inline) {
return // subroutine gets inlined at call site.
if(options.optimize) {
return
}
}
asmgen.out("")
@@ -309,9 +311,7 @@ internal class ProgramAndVarsGen(
asmgen.out("${sub.name}\t$asmStartScope")
val scope = symboltable.lookupOrElse(sub.scopedName) {
throw AssemblyError("lookup ${sub.scopedName}")
}
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
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.name=="p8_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8_main"))
if(sub.name=="start" && sub.definingBlock()!!.name=="main")
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
@@ -369,6 +369,12 @@ 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)
@@ -428,12 +434,12 @@ internal class ProgramAndVarsGen(
asmgen.out("""
lda #<${name}_init_value
ldy #>${name}_init_value
sta cx16.r0
sty cx16.r0+1
sta cx16.r0L
sty cx16.r0H
lda #<${name}
ldy #>${name}
sta cx16.r1
sty cx16.r1+1
sta cx16.r1L
sty cx16.r1H
lda #<$size
ldy #>$size
jsr sys.memcopy""")
@@ -452,6 +458,7 @@ internal class ProgramAndVarsGen(
}
asmgen.out("""+
ldx #127 ; init estack ptr (half page)
clv
clc""")
}
@@ -473,8 +480,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.lookup(scopedName) as? StStaticVariable
if(svar?.onetimeInitializationStringValue!=null)
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationStringValue!=null)
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
}
return result
@@ -485,8 +492,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.lookup(scopedName) as? StStaticVariable
if(svar?.onetimeInitializationArrayValue!=null)
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationArrayValue!=null)
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
}
return result
@@ -497,15 +504,7 @@ internal class ProgramAndVarsGen(
for ((scopedName, zpvar) in zpVariables) {
if (scopedName.startsWith("cx16.r"))
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
if(variable.dt in SplitWordArrayTypes) {
val lsbAddr = zpvar.address
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
asmgen.out("${scopedName.substringAfterLast('.')}_msb \t= $msbAddr \t; zp ${zpvar.dt} (msbs)")
} else {
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
}
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
}
}
@@ -544,11 +543,6 @@ 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")
@@ -633,18 +627,6 @@ 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 {
@@ -704,7 +686,7 @@ internal class ProgramAndVarsGen(
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
}
DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
DataType.ARRAY_UW -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
@@ -736,11 +718,11 @@ internal class ProgramAndVarsGen(
else
"-$$hexnum"
}
DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
DataType.ARRAY_UW -> array.map {
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
DataType.ARRAY_W, DataType.ARRAY_W_SPLIT -> array.map {
DataType.ARRAY_W -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
@@ -1,281 +0,0 @@
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)
throw AssemblyError("words operands comparison -> byte, should have been handled by assignOptimizedComparisonWords()")
}
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
require(expr.operator in ComparisonOperators)
return assignFloatBinExpr(expr, assign)
}
throw AssemblyError("weird expr operand types: ${expr.left.type} and {${expr.right.type}")
}
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,7 +10,8 @@ internal enum class TargetStorageKind {
VARIABLE,
ARRAY,
MEMORY,
REGISTER
REGISTER,
STACK
}
internal enum class SourceStorageKind {
@@ -19,6 +20,7 @@ internal enum class SourceStorageKind {
ARRAY,
MEMORY,
REGISTER,
STACK, // value is already present on stack
EXPRESSION, // expression in ast-form, still to be evaluated
}
@@ -113,12 +115,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
left is PtIdentifier && left.name==scopedName
}
TargetStorageKind.ARRAY -> {
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords
left is PtArrayIndexer && left isSameAs array!!
}
TargetStorageKind.MEMORY -> {
left isSameAs memory!!
}
TargetStorageKind.REGISTER -> {
TargetStorageKind.REGISTER, TargetStorageKind.STACK -> {
false
}
}
@@ -176,8 +178,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
}
is PtFunctionCall -> {
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val symbol = asmgen.symbolTable.lookup(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
File diff suppressed because it is too large. Load diff
-6
View File
@@ -18,8 +18,6 @@ internal object DummyMemsizer : IMemSizer {
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
@@ -47,10 +45,6 @@ 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() {
+5 -18
View File
@@ -1,9 +1,7 @@
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.*
@@ -22,7 +20,6 @@ class TestCodegen: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
compTarget = target,
@@ -43,9 +40,9 @@ class TestCodegen: FunSpec({
// xx += cx16.r0
// }
//}
val codegen = AsmGen6502(prefixSymbols = false)
val codegen = AsmGen6502()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -95,7 +92,7 @@ class TestCodegen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
@@ -107,22 +104,12 @@ class TestCodegen: FunSpec({
Files.deleteIfExists(Path("${result.name}.asm"))
}
test("64tass assembler available? - if this fails you need to install 64tass version 1.58 or newer in the path") {
test("64tass assembler available? - if this fails you need to install 64tass in the path") {
val command = mutableListOf("64tass", "--version")
shouldNotThrowAny {
val proc = ProcessBuilder(command).start()
val output = String(proc.inputStream.readBytes())
val proc = ProcessBuilder(command).inheritIO().start()
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
}
}
}
})
+3 -3
View File
@@ -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.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
}
sourceSets {
main {
java {
srcDir "${project.projectDir}/src"
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDir "${project.projectDir}/res"
srcDirs = ["${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,15 +26,6 @@ class ExperiCodeGen: ICodeGeneratorBackend {
IRFileWriter(irProgram, null).write()
println("** experimental codegen stub: no assembly generated **")
return EmptyProgram
return null
}
}
private object EmptyProgram : IAssemblyProgram {
override val name = "<Empty Program>"
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
println("** nothing assembled **")
return true
}
}
}
+4 -4
View File
@@ -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.18"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
}
sourceSets {
main {
java {
srcDir "${project.projectDir}/src"
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDir "${project.projectDir}/res"
srcDirs = ["${project.projectDir}/res"]
}
}
test {
@@ -13,9 +13,7 @@ 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")
val chunks = translateRegularAssign(assignment)
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(assignment.position)
return chunks
return translateRegularAssign(assignment)
}
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
@@ -26,7 +24,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val memory = augAssign.target.memory
val array = augAssign.target.array
val chunks = if(ident!=null) {
return if(ident!=null) {
assignVarAugmented(ident.name, augAssign)
} else if(memory != null) {
if(memory.address is PtNumber)
@@ -41,8 +39,6 @@ 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(
@@ -52,15 +48,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)
@@ -100,16 +96,35 @@ 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('='))
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)
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)
}
}
val normalAssign = PtAssignment(origAssign.position)
normalAssign.add(origAssign.target)
@@ -169,18 +184,15 @@ 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) {
valueRegister = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtMachineRegister).register), null)
}
if(extendByteToWord)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister), 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, reg2=tr.resultReg), null)
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1 = valueRegister), null)
}
}
}
@@ -202,7 +214,8 @@ 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.usesPointerVariable) {
if(targetArray.variable.type==DataType.UWORD) {
// indexing a pointer var instead of a real array or string
if(itemsize!=1)
throw AssemblyError("non-array var indexing requires bytes dt")
if(targetArray.index.type!=DataType.UBYTE)
@@ -222,71 +235,36 @@ 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 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}")
}
val offset = fixedIndex*itemsize
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
result += code
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)
}
result += IRCodeChunk(null, null).also { 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 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}")
}
val offset = fixedIndex*itemsize
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
result += code
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)
}
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable) }
}
}
}
@@ -323,21 +301,28 @@ 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 returned.
// returns the code to load the Index into the register, which is also return\ed.
val result = mutableListOf<IRCodeChunkBase>()
if(itemsize==1 || array.splitWords) {
if(itemsize==1) {
val tr = expressionEval.translateExpression(array.index)
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)
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)
}
}
}
@@ -1,10 +1,9 @@
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.*
@@ -14,76 +13,41 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return when(call.name) {
"any" -> funcAny(call)
"all" -> funcAll(call)
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
"abs" -> funcAbs(call)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(call)
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE)
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
"sqrt16" -> funcSqrt16(call)
"divmod" -> funcDivmod(call, IRDataType.BYTE)
"divmodw" -> funcDivmod(call, IRDataType.WORD)
"pop" -> funcPop(call)
"popw" -> funcPopw(call)
"push" -> funcPush(call)
"pushw" -> funcPushw(call)
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> funcCallfar(call)
"rsave",
"rsavex",
"rrestore",
"rrestorex" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"peekf" -> funcPeek(call, IRDataType.FLOAT)
"poke" -> funcPoke(call, IRDataType.BYTE)
"pokew" -> funcPoke(call, IRDataType.WORD)
"pokef" -> funcPoke(call, IRDataType.FLOAT)
"pokemon" -> funcPokemon(call)
"peek" -> funcPeek(call)
"peekw" -> funcPeekW(call)
"poke" -> funcPoke(call)
"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]
@@ -115,7 +79,6 @@ 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)
@@ -139,9 +102,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 arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when (arrayName.type) {
when (array.dt) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
DataType.ARRAY_UW,
@@ -150,20 +113,19 @@ 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 = arrayLength), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), 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 arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(arrayName.type) {
when(array.dt) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
DataType.ARRAY_UW,
@@ -172,11 +134,10 @@ 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 = arrayLength), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), 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)
}
@@ -190,13 +151,21 @@ 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.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, immediate = 0x80)
it += IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=compareReg, immediate = 0, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
@@ -206,18 +175,14 @@ 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.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, immediate = 0x8000)
it += IRInstruction(Opcode.BEQ, IRDataType.WORD, reg1=compareReg, immediate = 0, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
}
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")
else -> throw AssemblyError("weird type")
}
}
@@ -233,37 +198,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, vmDt, resultReg, -1)
}
private fun funcSqrt(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcSqrt16(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args.single())
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")
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)
}
private fun funcPop(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -306,283 +249,168 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(arrayName.type) {
when(array.dt) {
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(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(arrayName.type) {
when(array.dt) {
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(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.nextFree()
if((call.args[0] as? PtNumber)?.number == 0.0) {
// msb is 0, use EXT
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
}
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()
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
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)
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = lsbTr.resultReg, reg2 = msbTr.resultReg)
}
result += IRCodeChunk(after, null)
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
return ExpressionCodeResult(result, IRDataType.WORD, lsbTr.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 funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
private fun funcPokeW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, dt, address = address)
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, address = address)
}
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg)
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = tr.resultReg)
}
}
} else {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
if(dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, fpReg1 = tr.resultFpReg, address = address)
}
} else {
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, reg1 = tr.resultReg, address = address)
}
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = tr.resultReg, address = address)
}
} else {
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
if(dt==IRDataType.FLOAT) {
addToResult(result, valueTr, -1, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = valueTr.resultFpReg)
}
} else {
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
}
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
}
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPeek(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
private fun funcPoke(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(dt==IRDataType.FLOAT) {
if(call.args[0] is PtNumber) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = resultFpRegister, address = address)
}
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultFpReg = codeGen.registers.nextFreeFloat()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = resultFpReg)
}
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
} else {
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address)
}
ExpressionCodeResult(result, dt, resultRegister, -1)
} else {
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.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg)
}
ExpressionCodeResult(result, dt, resultReg, -1)
}
}
}
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val address = call.args[0]
fun pokeM(result: MutableList<IRCodeChunkBase>, address: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
}
} else {
val tr = exprGen.translateExpression(value)
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = tr.resultReg)
}
}
} else {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
}
}
}
fun pokeI(result: MutableList<IRCodeChunkBase>, register: Int, value: PtExpression) {
if(codeGen.isZero(value)) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = register)
}
} else {
val valueTr = exprGen.translateExpression(value)
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = register)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
}
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
return if(address is PtNumber) {
private fun funcPeekW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val addressNum = address.number.toInt()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, address = address)
}
pokeM(result, addressNum, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
val addressTr = exprGen.translateExpression(address)
addToResult(result, addressTr, addressTr.resultReg, -1)
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.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg)
}
pokeI(result, addressTr.resultReg, call.args[1])
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
}
private fun funcPeek(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = address)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
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.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
}
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val name = (call.args[0] as PtString).value
@@ -595,7 +423,6 @@ 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 {
@@ -622,122 +449,6 @@ 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,9 +1,13 @@
package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StStaticVariable
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PassByValueDatatypes
import prog8.code.core.SignedDatatypes
import prog8.intermediate.*
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
@@ -17,8 +21,6 @@ internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType
if(resultReg!=-1) require(resultFpReg==-1)
if(resultFpReg!=-1) require(resultReg==-1)
}
fun lastInstruction() = chunks.last().instructions.last()
}
internal class ExpressionGen(private val codeGen: IRCodeGen) {
@@ -33,7 +35,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val code = IRCodeChunk(null, null)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number)
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number.toFloat())
ExpressionCodeResult(code, vmDt,-1, resultFpRegister)
}
else {
@@ -43,9 +45,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)
@@ -58,7 +60,6 @@ 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)
@@ -68,28 +69,10 @@ 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 result = mutableListOf<IRCodeChunkBase>()
val code = IRCodeChunk(null, null)
val resultRegister = codeGen.registers.nextFree()
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)
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
ExpressionCodeResult(code, vmDt, resultRegister, -1)
}
is PtMemoryByte -> {
val result = mutableListOf<IRCodeChunkBase>()
@@ -122,9 +105,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
when(check.iterable.type) {
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.name) as StStaticVariable
when(iterable.dt) {
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)
@@ -133,31 +116,27 @@ 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()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), 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()
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), 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 ${check.iterable.type} for ${check.iterable.name}")
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.name}")
}
}
@@ -167,7 +146,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
val arrayVarSymbol = arrayIx.variable.name
if(arrayIx.usesPointerVariable) {
if(arrayIx.variable.type==DataType.UWORD) {
// indexing a pointer var instead of a real array or string
if(eltSize!=1)
throw AssemblyError("non-array var indexing requires bytes dt")
if(arrayIx.index.type!=DataType.UBYTE)
@@ -180,33 +160,6 @@ 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=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
} 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=tmpRegMsb, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
}
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
}
var resultFpRegister = -1
if(arrayIx.index is PtNumber) {
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize).toString()
@@ -294,13 +247,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when(cast.value.type) {
DataType.BYTE -> {
// byte -> uword: sign extend
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null)
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
}
DataType.UBYTE -> {
// ubyte -> uword: sign extend
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null)
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
}
DataType.WORD -> {
actualResultReg2 = tr.resultReg
@@ -316,13 +269,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when(cast.value.type) {
DataType.BYTE -> {
// byte -> word: sign extend
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null)
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
}
DataType.UBYTE -> {
// byte -> word: sign extend
actualResultReg2 = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null)
actualResultReg2 = tr.resultReg
addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2), null)
}
DataType.UWORD -> {
actualResultReg2 = tr.resultReg
@@ -359,6 +312,7 @@ 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) {
@@ -386,7 +340,6 @@ 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)) {
@@ -407,7 +360,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
}
// create the call
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
addInstr(result, call, null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
@@ -417,7 +371,6 @@ 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)) {
@@ -475,7 +428,6 @@ 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)
@@ -490,10 +442,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (greaterEquals) Opcode.SGE else Opcode.SGT
}
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1=cmpResultReg, reg2 = resultRegister, reg3 = zeroRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultReg, -1)
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister), 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 {
val leftTr = translateExpression(binExpr.left)
@@ -505,8 +457,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (greaterEquals) Opcode.SGE else Opcode.SGT
}
addInstr(result, IRInstruction(ins, vmDt, reg1=cmpResultReg, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultReg, -1)
addInstr(result, IRInstruction(ins, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
}
}
}
@@ -518,7 +470,6 @@ 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)
@@ -533,10 +484,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (lessEquals) Opcode.SLE else Opcode.SLT
}
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1=cmpResultRegister, reg2 = resultRegister, reg3 = zeroRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultRegister, -1)
addInstr(result, IRInstruction(ins, IRDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister), 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 {
val leftTr = translateExpression(binExpr.left)
@@ -548,8 +499,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
if (lessEquals) Opcode.SLE else Opcode.SLT
}
addInstr(result, IRInstruction(ins, vmDt, reg1=cmpResultRegister, reg2 = leftTr.resultReg, reg3 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, cmpResultRegister, -1)
addInstr(result, IRInstruction(ins, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
}
}
}
@@ -564,20 +515,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val resultRegister = codeGen.registers.nextFree()
val valueReg = codeGen.registers.nextFree()
val label = codeGen.createLabelName()
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 = 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)
}
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) {
@@ -593,9 +542,8 @@ 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
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)
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, IRDataType.BYTE, leftTr.resultReg, -1)
}
}
}
@@ -713,7 +661,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorRight.number
val factor = constFactorRight.number.toFloat()
result += codeGen.divideByConstFloat(tr.resultFpReg, factor)
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
@@ -769,13 +717,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(constFactorLeft!=null) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorLeft.number
val factor = constFactorLeft.number.toFloat()
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else if(constFactorRight!=null) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorRight.number
val factor = constFactorRight.number.toFloat()
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
@@ -823,7 +771,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
@@ -878,7 +826,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
@@ -950,7 +898,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val constFactorRight = operand as? PtNumber
if(vmDt==IRDataType.FLOAT) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val factor = constFactorRight.number
val factor = constFactorRight.number.toFloat()
result += codeGen.divideByConstFloatInplace(knownAddress, symbol, factor)
} else {
val tr = translateExpression(operand)
@@ -999,7 +947,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val constFactorRight = operand as? PtNumber
if(vmDt==IRDataType.FLOAT) {
if(constFactorRight!=null) {
val factor = constFactorRight.number
val factor = constFactorRight.number.toFloat()
result += codeGen.multiplyByConstFloatInplace(knownAddress, symbol, factor)
} else {
val tr = translateExpression(operand)
@@ -1265,7 +1213,6 @@ 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) {
@@ -1273,16 +1220,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 = cmpResultReg, reg2 = valueReg, reg3 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, address = knownAddress)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, 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=cmpResultReg, reg2 = valueReg, reg3 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = symbol)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = numberReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, labelSymbol = symbol)
}
}
} else {
@@ -1292,15 +1239,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=cmpResultReg, reg2 = valueReg, reg3 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, address = knownAddress)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, 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=cmpResultReg, reg2 = valueReg, reg3 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = cmpResultReg, labelSymbol = symbol)
it += IRInstruction(compareAndSetOpcode, vmDt, reg1 = valueReg, reg2 = tr.resultReg)
it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueReg, labelSymbol = symbol)
}
}
}
@@ -1319,33 +1266,31 @@ 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 {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
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=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
}
} else {
// in-place modify a symbol (variable)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
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=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, 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
@@ -1353,8 +1298,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=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg)
it += IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
}
} else {
@@ -1363,8 +1308,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=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpResultReg, fpReg1 = valueReg)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpReg, reg2=zeroReg)
it += IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=cmpReg, 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.foreachSub { sub ->
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
joinChunks(sub)
removeEmptyChunks(sub)
joinChunks(sub)
@@ -32,11 +32,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun peepholeOptimize() {
irprog.foreachSub { sub ->
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { 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
@@ -45,41 +44,22 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
val indexedInstructions = chunk1.instructions.withIndex()
.map { IndexedValue(it.index, it.value) }
val changed = removeNops(chunk1, indexedInstructions)
|| replaceConcatZeroMsbWithExt(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
// TODO other optimizations:
// more complex optimizations such as unused registers
} while (changed)
}
}
removeEmptyChunks(sub)
}
// TODO also do register optimization step here at the end?
irprog.linkChunks() // re-link
}
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.CONCAT) {
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
val prev = indexedInstructions[idx-1].value
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
chunk.instructions[idx] = IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = ins.reg1, reg2 = ins.reg3)
chunk.instructions.removeAt(idx-1)
changed = true
}
}
}
return changed
}
private fun removeEmptyChunks(sub: IRSubroutine) {
if(sub.chunks.isEmpty())
return
@@ -87,7 +67,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
/*
Empty Code chunk with label ->
If next chunk has no label -> move label to next chunk, remove original
If next chunk has label -> label name should be the same, remove original. Otherwise merge both labels into 1.
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: merge both labels into 1)
If is last chunk -> keep chunk in place because of the label.
Empty Code chunk without label ->
should not have been generated! ERROR.
@@ -96,7 +76,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
val relabelChunks = mutableListOf<Pair<Int, String>>()
val removeChunks = mutableListOf<Int>()
val replaceLabels = mutableMapOf<String, String>()
sub.chunks.withIndex().forEach { (index, chunk) ->
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
@@ -110,18 +89,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
relabelChunks += Pair(index + 1, chunk.label!!)
removeChunks += index
} else {
// merge both labels into 1 except if this is the label chunk at the start of the subroutine
if(index>0) {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
removeChunks += index
replaceLabels[chunk.label!!] = nextchunk.label!!
replaceLabels.entries.forEach { (key, value) ->
if (value == chunk.label)
replaceLabels[key] = nextchunk.label!!
}
}
if (chunk.label == nextchunk.label)
removeChunks += index
else {
// TODO: merge labels on same chunk
}
}
}
@@ -131,32 +102,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
relabelChunks.forEach { (index, label) ->
val chunk = IRCodeChunk(label, null)
val subChunk = sub.chunks[index]
chunk.instructions += subChunk.instructions
if(subChunk is IRCodeChunk)
chunk.appendSrcPositions(subChunk.sourceLinesPositions)
chunk.instructions += sub.chunks[index].instructions
sub.chunks[index] = chunk
}
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
sub.chunks.forEach { chunk ->
chunk.instructions.withIndex().forEach { (idx, instr) ->
instr.labelSymbol?.let {
if(instr.opcode in OpcodesThatBranch) {
replaceLabels.forEach { (from, to) ->
if (it == from) {
chunk.instructions[idx] = instr.copy(labelSymbol = to)
}
else {
val actualPrefix = "$from."
if (it.startsWith(actualPrefix))
chunk.instructions[idx] = instr.copy(labelSymbol = "$to.")
}
}
}
}
}
}
}
private fun joinChunks(sub: IRSubroutine) {
@@ -188,8 +137,6 @@ 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
@@ -317,28 +264,6 @@ 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
@@ -408,9 +333,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
return false
/*
var changed = false
indexedInstructions.forEach { (idx, ins) ->
@@ -424,6 +346,5 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
// ...
}
return changed
*/
}
}
@@ -1,103 +0,0 @@
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)
pruneSymboltable(block.label)
irprog.st.removeTree(block.label)
numRemoved++
}
}
@@ -24,43 +24,21 @@ 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 { variable ->
val initValue = variable.onetimeInitializationArrayValue
if(!initValue.isNullOrEmpty()) {
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.foreachCodeChunk { chunk ->
chunk.label?.let { allLabeledChunks[it] = chunk }
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.label?.let { allLabeledChunks[it] = chunk }
}
}
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(libraryFilePrefix)) {
errors.warn("unused subroutine ${sub.label}", sub.position)
}
block.children.remove(sub)
irprog.st.removeTree(sub.label)
numRemoved++
@@ -79,7 +57,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)
@@ -119,10 +97,12 @@ class IRUnusedCodeRemover(
}
// check if asmsub is linked or called from another regular subroutine
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
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
}
}
}
@@ -143,22 +123,9 @@ 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 {
@@ -187,37 +154,29 @@ class IRUnusedCodeRemover(
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
// all chunks referenced in array initializer values are linked as well!:
irprog.st.allVariables()
.filter { !it.uninitialized }
.forEach {
it.onetimeInitializationArrayValue?.let { array ->
array.forEach {elt ->
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
linkedChunks += irprog.getChunkWithLabel(elt.addressOfSymbol!!)
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach {
if(it.branchTarget==null) {
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
} else {
linkedChunks += it.branchTarget!!
}
}
if (chunk.label == "main.start")
linkedChunks += chunk
}
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.foreachSub { sub ->
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
if (chunk !in linkedChunks) {
if (chunk === sub.chunks[0]) {
@@ -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)
-6
View File
@@ -16,8 +16,6 @@ internal object DummyMemsizer : IMemSizer {
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
@@ -45,10 +43,6 @@ 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() {
+10 -9
View File
@@ -18,7 +18,6 @@ class TestIRPeepholeOpt: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
emptyList(),
CompilationOptions.AllZeropageAllowed,
floats = false,
noSysInit = true,
compTarget = target,
@@ -37,7 +36,7 @@ class TestIRPeepholeOpt: FunSpec({
return makeIRProgram(listOf(chunk))
}
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.allSubs().flatMap { it.chunks }.toList()
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
test("remove nops") {
val irProg = makeIRProgram(listOf(
@@ -53,9 +52,9 @@ class TestIRPeepholeOpt: FunSpec({
test("remove jmp to label below") {
val c1 = IRCodeChunk("main.start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
val c2 = IRCodeChunk("label", null)
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
c2 += IRInstruction(Opcode.NOP) // removed
val c3 = IRCodeChunk("label2", null)
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
@@ -67,13 +66,15 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().size shouldBe 3
irProg.chunks().size shouldBe 4
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label2"
irProg.chunks()[2].label shouldBe "label3"
irProg.chunks()[1].label shouldBe "label"
irProg.chunks()[2].label shouldBe "label2"
irProg.chunks()[3].label shouldBe "label3"
irProg.chunks()[0].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe false
irProg.chunks()[2].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe true
irProg.chunks()[2].isEmpty() shouldBe false
irProg.chunks()[3].isEmpty() shouldBe true
val instr = irProg.chunks().flatMap { it.instructions }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.JUMP
+10 -15
View File
@@ -19,7 +19,6 @@ class TestVmCodeGen: FunSpec({
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
zpReserved = emptyList(),
zpAllowed = CompilationOptions.AllZeropageAllowed,
floats = true,
noSysInit = false,
compTarget = target,
@@ -42,7 +41,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -92,7 +91,7 @@ class TestVmCodeGen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
@@ -121,7 +120,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -184,7 +183,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -243,7 +242,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -290,7 +289,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -353,7 +352,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -412,7 +411,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -452,7 +451,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(romsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
@@ -467,11 +466,7 @@ 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
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]
val callInstr = irChunks.single().instructions.single()
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
}
+2 -2
View File
@@ -33,10 +33,10 @@ dependencies {
sourceSets {
main {
java {
srcDir "${project.projectDir}/src"
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDir "${project.projectDir}/res"
srcDirs = ["${project.projectDir}/res"]
}
}
}
@@ -0,0 +1,71 @@
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
}
@@ -2,29 +2,28 @@ 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 (but only if thing is a byte type!)
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null) {
if(addrOf.identifier.inferType(program).isBytes)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
}
return noModifications
return if(addrOf!=null)
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
else
noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
@@ -86,8 +85,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
if(expr.operator=="==" && rightconst!=null) {
val leftExpr = expr.left as? BinaryExpression
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
if(leftExpr?.constValue(program) != null) {
if(leftExpr!=null) {
val leftRightConst = leftExpr.right.constValue(program)
if(leftRightConst!=null) {
when (leftExpr.operator) {
@@ -184,12 +182,10 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
} else if (leftBinExpr.operator=="/") {
if(expr.inferType(program).istype(DataType.FLOAT)) {
// (X / C2) * rightConst --> X * (rightConst/C2) only valid for floating point
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
// (X / C2) * rightConst --> X * (rightConst/C2)
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
}
}
@@ -197,7 +193,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val c2 = leftBinExpr.right.constValue(program)
if(c2!=null && leftBinExpr.operator=="/") {
// (X / C1) / C2 --> X / (C1*C2)
// NOTE: do not optimize (X * C1) / C2 for integers, --> X * (C1/C2) because this causes precision loss on integers
// NOTE: do not optimize (X * C1) / C2 --> X * (C1/C2) because this causes precision loss on integers
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
@@ -238,47 +234,6 @@ 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
}
@@ -352,7 +307,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) ?: return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
val stepLiteral = iterableRange.step as? NumericLiteral
when(loopvar.datatype) {
@@ -388,7 +343,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> { /* nothing for floats, these are not allowed in for loops and will give an error elsewhere */ }
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
}
return noModifications
@@ -409,16 +364,6 @@ 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,
@@ -528,8 +473,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/" && subExpr.inferType(program).istype(DataType.FLOAT)) {
// division optimizations only valid for floats
else if(expr.operator=="*" && subExpr.operator=="/") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
@@ -632,4 +576,5 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return null
}
}
}
@@ -28,7 +28,6 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
declConstValue.linkParents(decl)
val cast = declConstValue.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
@@ -54,8 +53,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
}
if(to==null) {
val toType = range.to.inferType(program)
if(toType.isKnown && !range.to.inferType(program).isInteger)
if(!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)
@@ -70,124 +68,6 @@ 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
}
}
@@ -252,16 +132,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
}
if(decl.isArray && decl.type==VarDeclType.MEMORY) {
val memaddr = decl.value?.constValue(program)
if(memaddr!=null && memaddr !== decl.value) {
return listOf(IAstModification.SetExpression(
{ decl.value = it }, memaddr, decl
))
}
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
if(decl.isArray){
val arraysize = decl.arraysize
if(arraysize==null) {
@@ -286,10 +157,101 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
in ArrayDatatypes -> {
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
if(replacedArrayInitializer!=null)
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, 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))
}
}
else -> {
// nothing to do for this type
@@ -299,112 +261,4 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
if(decl.type==VarDeclType.MEMORY)
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
// 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,
options: CompilationOptions
compTarget: ICompilationTarget
): Int {
val optimizer = StatementOptimizer(this, errors, functions, options)
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
@@ -54,8 +54,8 @@ fun Program.optimizeStatements(errors: IErrorReporter,
return optimizationCount
}
fun Program.inlineSubroutines(options: CompilationOptions): Int {
val inliner = Inliner(this, options)
fun Program.inlineSubroutines(): Int {
val inliner = Inliner(this)
inliner.visit(this)
return inliner.applyModifications()
}
@@ -65,3 +65,9 @@ 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()
}
+89 -127
View File
@@ -8,9 +8,7 @@ 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
@@ -18,7 +16,7 @@ private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.va
// inliner potentially enables *ONE LINED* subroutines, wihtout to be inlined.
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
class Inliner(val program: Program): AstWalker() {
class DetermineInlineSubs(val program: Program): IAstVisitor {
private val modifications = mutableListOf<IAstModification>()
@@ -34,100 +32,85 @@ class Inliner(private val program: Program, private val options: CompilationOpti
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
if(!containsSubsOrVariables) {
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
if(subroutine !== program.entrypoint) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when (val stmt = subroutine.statements[0]) {
is Return -> {
if (stmt.value is NumericLiteral)
true
else if (stmt.value == null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if (stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size <= 1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
when (stmt.value) {
is BuiltinFunctionCall -> {
makeFullyScoped(stmt.value as BuiltinFunctionCall)
true
}
is FunctionCallExpression -> {
makeFullyScoped(stmt.value as FunctionCallExpression)
true
}
else -> false
// subroutine is possible candidate to be inlined
subroutine.inline =
when(val stmt=subroutine.statements[0]) {
is Return -> {
if(stmt.value is NumericLiteral)
true
else if(stmt.value==null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if(stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size<=1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
when (stmt.value) {
is BuiltinFunctionCall -> {
makeFullyScoped(stmt.value as BuiltinFunctionCall)
true
}
} else
false
}
is Assignment -> {
if (stmt.value.isSimple) {
val targetInline =
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if ((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if ((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else
false
}
is BuiltinFunctionCallStatement -> {
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
inline
}
is FunctionCallStatement -> {
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
inline
}
is PostIncrDecr -> {
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
}
is Jump -> true
else -> false
is FunctionCallExpression -> {
makeFullyScoped(stmt.value as FunctionCallExpression)
true
}
else -> false
}
} else
false
}
}
}
if(subroutine.inline && subroutine.statements.size>1) {
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
is Assignment -> {
if(stmt.value.isSimple) {
val targetInline =
if(stmt.target.identifier!=null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if(stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else
false
}
is BuiltinFunctionCallStatement -> {
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if(inline)
makeFullyScoped(stmt)
inline
}
is FunctionCallStatement -> {
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if(inline)
makeFullyScoped(stmt)
inline
}
is PostIncrDecr -> {
if(stmt.target.identifier!=null) {
makeFullyScoped(stmt.target.identifier!!)
true
}
else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
}
is Jump -> true
else -> false
}
}
}
}
@@ -144,20 +127,16 @@ class Inliner(private val program: Program, private val options: CompilationOpti
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
private fun makeFullyScoped(call: FunctionCallStatement) {
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
@@ -165,10 +144,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
@@ -176,10 +153,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
if(scopedArgs.any()) {
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
@@ -188,8 +163,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
when (it) {
is NumericLiteral -> it.copy()
is IdentifierReference -> {
val target = it.targetStatement(program) ?: return emptyList()
val scoped = (target as INamedStatement).scopedName
val scoped = (it.targetStatement(program)!! as INamedStatement).scopedName
IdentifierReference(scoped, it.position)
}
else -> throw InternalCompilerException("expected only number or identifier arg, otherwise too complex")
@@ -237,7 +211,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val sub = functionCallStatement.target.targetStatement(program) as? Subroutine
return if(sub==null || !canInline(sub, functionCallStatement))
return if(sub==null)
noModifications
else
possibleInlineFcallStmt(sub, functionCallStatement, parent)
@@ -245,7 +219,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
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() && canInline(sub, functionCallExpr)) {
if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
}
@@ -272,17 +246,5 @@ class Inliner(private val program: Program, private val options: CompilationOpti
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 options: CompilationOptions
private val compTarget: ICompilationTarget
) : 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) {
if(arg.arrayIndex==null) arg.identifier else null
arg.identifier
} else {
arg as? IdentifierReference
}
@@ -39,15 +39,19 @@ class StatementOptimizer(private val program: Program,
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = options.compTarget.encodeString(string.value, string.encoding)[0]
val firstCharEncoded = compTarget.encodeString(string.value, string.encoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
val stringDecl = string.parent as VarDecl
return listOf(
IAstModification.ReplaceNode(functionCallStatement, chrout, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
)
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = options.compTarget.encodeString(string.value.take(2), string.encoding)
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.encoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
@@ -58,9 +62,11 @@ class StatementOptimizer(private val program: Program,
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
functionCallStatement.void, pos
)
val stringDecl = string.parent as VarDecl
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
)
}
}
@@ -71,11 +77,6 @@ class StatementOptimizer(private val program: Program,
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
errors.warn("condition is always ${constvalue.asBooleanValue}", ifElse.condition.position)
}
// remove empty if statements
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
@@ -92,24 +93,21 @@ class StatementOptimizer(private val program: Program,
)
}
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always true", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
} else {
// always false -> keep only else-part
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always false", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
}
}
// 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
}
@@ -143,7 +141,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 = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val character = 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))
@@ -168,67 +166,13 @@ class StatementOptimizer(private val program: Program,
}
}
val loopvarDt = forLoop.loopVarDt(program)
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
if (range != null && range.from.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==1.0) {
val toBinExpr = range.to as? BinaryExpression
if(toBinExpr!=null && toBinExpr.operator=="-" && toBinExpr.right.constValue(program)?.number==1.0) {
// FOR var IN 0 TO X-1 .... ---> var=0, DO {... , var++} UNTIL var==X
val pos = forLoop.position
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", toBinExpr.left, pos)
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
if(options.compTarget.name!=VMTarget.NAME) {
// this optimization is not effective for the VM target.
val toConst = range.to.constValue(program)
if (toConst == null) {
// FOR var in 0 TO X ... ---> var=0, REPEAT { ... , IF var==X break , var++ }
val pos = forLoop.position
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
val breakCondition = IfElse(
BinaryExpression(forLoop.loopVar, "==", range.to, pos),
AnonymousScope(mutableListOf(Break(pos)), pos),
AnonymousScope(mutableListOf(), pos),
pos
)
forLoop.body.statements.add(breakCondition)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
RepeatLoop(null, forLoop.body, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
val fromExpr = range.from
if(fromExpr.constValue(program)==null) {
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
val pos = forLoop.position
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
val decOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "--", pos)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
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))
}
}
@@ -382,7 +326,7 @@ class StatementOptimizer(private val program: Program,
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
@@ -396,7 +340,7 @@ class StatementOptimizer(private val program: Program,
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && options.compTarget.name!=VMTarget.NAME) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..3.0 && compTarget.name!=VMTarget.NAME) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(rightCv.toInt()) {
@@ -442,74 +386,9 @@ class StatementOptimizer(private val program: Program,
}
override fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> {
val iterations = unrollLoop.iterations.constValue(program)?.number?.toInt()
return if(iterations!=null && iterations<1)
return if(unrollLoop.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)
}
}
}
}
}
val constantValue = whenStmt.condition.constValue(program)?.number
if(constantValue!=null) {
// when condition is a constant
var matchingChoice: WhenChoice? = null
loop@ for(choice in whenStmt.choices) {
for(value in choice.values ?: emptyList()) {
if(value.constValue(program)?.number == constantValue) {
matchingChoice = choice
break@loop
}
}
}
if(matchingChoice==null)
matchingChoice = whenStmt.choices.singleOrNull { it.values==null }
if(matchingChoice!=null) {
// get rid of the whole when-statement and just leave the matching choice
return listOf(IAstModification.ReplaceNode(whenStmt, matchingChoice.statements, parent))
}
}
return noModifications
}
}
@@ -75,9 +75,7 @@ class UnusedCodeRemover(private val program: Program,
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.warn("removing unused block '${block.name}'", block.position)
}
if(!block.statements.any { it is Subroutine && it.hasBeenInlined }) {
program.removeInternedStringsFromRemovedBlock(block)
}
program.removeInternedStringsFromRemovedBlock(block)
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
}
@@ -99,7 +97,7 @@ class UnusedCodeRemover(private val program: Program,
}
return removals
}
if(!subroutine.hasBeenInlined && subroutine.definingModule===program.toplevelModule) {
if(!subroutine.definingModule.isLibrary && !subroutine.hasBeenInlined) {
errors.warn("unused subroutine '${subroutine.name}'", subroutine.position)
}
if(!subroutine.inline) {
@@ -139,20 +137,13 @@ class UnusedCodeRemover(private val program: Program,
)
} else if(assignment.value is IFunctionCall) {
// replace the unused variable's initializer function call by a void
// but only if the vardecl immediately precedes it!
if(singleUse.parent.parent === parent) {
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
if(declIndex==singleUseIndex-1) {
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
}
}
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
} else {
errors.warn("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
}
+11 -22
View File
@@ -1,10 +1,9 @@
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 'com.peterabeles.gversion' version '1.10.2'
id "io.kotest" version "0.3.9"
}
java {
@@ -25,6 +24,8 @@ compileTestKotlin {
}
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':codeCore')
implementation project(':codeOptimizers')
@@ -33,14 +34,14 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "org.antlr:antlr4-runtime:4.12.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.5.5'
}
configurations.all {
@@ -59,10 +60,10 @@ configurations {
sourceSets {
main {
java {
srcDir "${project.projectDir}/src"
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDir "${project.projectDir}/res"
srcDirs = ["${project.projectDir}/res"]
}
}
test {
@@ -81,7 +82,7 @@ application {
shadowJar {
archiveBaseName = 'prog8compiler'
archiveVersion = version
archiveVersion = prog8version
// minimize()
}
@@ -99,16 +100,4 @@ 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
+24 -117
View File
@@ -2,48 +2,44 @@
; 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
&uword COLCRS = 85
&ubyte ROWCRS = 84
; ---- kernal routines ----
; TODO
romsub $F24A = getchar() -> ubyte @A
romsub $F2B0 = outchar(ubyte character @ A)
romsub $F2FD = waitkey() -> ubyte @A
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.
@@ -192,67 +188,6 @@ _longcopy
}}
}
inline asmsub irqsafe_set_irqd() {
%asm {{
php
sei
}}
}
inline asmsub irqsafe_clear_irqd() {
%asm {{
plp
}}
}
sub disable_caseswitch() {
; no-op
}
sub enable_caseswitch() {
; no-op
}
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
; TODO
@@ -273,7 +208,6 @@ save_SCRATCH_ZPWORD2 .word 0
}
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
@@ -379,31 +313,4 @@ 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
}}
}
}
+55 -55
View File
@@ -1,5 +1,4 @@
; Prog8 definitions for the Text I/O and Screen routines for the Atari 800XL
; Reference: https://www.atariarchives.org/mapping/appendix12.php
%import syslib
%import conv
@@ -7,8 +6,6 @@
txt {
%option no_symbol_prefixing
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 24
@@ -25,25 +22,15 @@ sub spc() {
txt.chrout(' ')
}
sub column(ubyte col) {
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
atari.COLCRS = col
; TODO
%asm {{
rts
}}
}
sub get_column() -> ubyte {
return atari.COLCRS as ubyte
}
sub row(ubyte rownum) {
; ---- set the cursor on the given row (starting with 0) on the current line
atari.ROWCRS = rownum
}
sub get_row() -> ubyte {
return atari.ROWCRS
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
asmsub fill_screen (ubyte char @ 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
@@ -53,7 +40,7 @@ asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
}}
}
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
; TODO
@@ -83,7 +70,7 @@ sub uppercase() {
; TODO
}
asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, Y) {
asmsub scroll_left (ubyte 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
@@ -94,7 +81,7 @@ asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, Y) {
}}
}
asmsub scroll_right (bool alsocolors @ Pc) clobbers(A) {
asmsub scroll_right (ubyte 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
@@ -104,7 +91,7 @@ asmsub scroll_right (bool alsocolors @ Pc) clobbers(A) {
}}
}
asmsub scroll_up (bool alsocolors @ Pc) clobbers(A) {
asmsub scroll_up (ubyte 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
@@ -114,7 +101,7 @@ asmsub scroll_up (bool alsocolors @ Pc) clobbers(A) {
}}
}
asmsub scroll_down (bool alsocolors @ Pc) clobbers(A) {
asmsub scroll_down (ubyte 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
@@ -125,20 +112,25 @@ asmsub scroll_down (bool alsocolors @ Pc) clobbers(A) {
}
asmsub chrout(ubyte character @ A) {
romsub $F2B0 = outchar(ubyte char @ A)
romsub $F2Fd = waitkey()
asmsub chrout(ubyte char @ A) {
%asm {{
sta _tmp_outchar+1
pha
txa
pha
tya
pha
_tmp_outchar
lda #0
jsr atari.outchar
jsr outchar
pla
tay
pla
tax
pla
rts
}}
}
@@ -161,9 +153,10 @@ asmsub print (str text @ AY) clobbers(A,Y) {
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
asmsub print_ub0 (ubyte value @ A) clobbers(A,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
@@ -171,13 +164,16 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
pla
jsr chrout
txa
jmp chrout
jsr chrout
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
asmsub print_ub (ubyte value @ A) clobbers(A,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
@@ -193,13 +189,16 @@ _print_byte_digits
beq _ones
jsr chrout
_ones txa
jmp chrout
jsr chrout
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
pha
cmp #0
bpl +
@@ -211,9 +210,10 @@ asmsub print_b (byte value @ A) clobbers(A,X,Y) {
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,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,13 +222,16 @@ asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
+ jsr conv.ubyte2hex
jsr chrout
tya
jmp chrout
jsr chrout
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,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 #'%'
@@ -241,11 +244,12 @@ asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
+ jsr chrout
dey
bne -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
@@ -257,7 +261,7 @@ asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
@@ -270,9 +274,10 @@ asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
asmsub print_uw0 (uword value @ AY) clobbers(A,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
@@ -280,14 +285,17 @@ asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
jsr chrout
iny
bne -
+ rts
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
asmsub print_uw (uword value @ AY) clobbers(A,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
@@ -308,7 +316,7 @@ _allzero
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
asmsub print_w (word value @ AY) clobbers(A,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
@@ -372,7 +380,7 @@ asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
}}
}
sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; TODO
%asm {{
@@ -380,20 +388,12 @@ sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
}}
}
sub plot(ubyte col, ubyte rownum) {
column(col)
row(rownum)
}
sub get_cursor(uword colptr, uword rowptr) {
@(colptr) = get_column()
@(rowptr) = get_row()
}
asmsub waitkey() -> ubyte @A {
%asm {{
jmp atari.waitkey
}}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- set cursor at specific position
; TODO
%asm {{
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
+60
View File
@@ -0,0 +1,60 @@
%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
}}
}
}
+408 -455
View File
@@ -1,9 +1,7 @@
; Prog8 definitions for the Commodore-128
; Including memory registers, I/O registers, Basic and Kernal subroutines.
cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
@@ -49,92 +47,6 @@ cbm {
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
@@ -284,11 +196,270 @@ 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
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, 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
; ---- 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.
%asm {{
txa
pha
jsr c64.STOP
beq +
pla
tax
lda #0
rts
+ pla
tax
lda #1
rts
}}
}
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
}}
}
; ---- system utility routines that are essentially the same as on the C64: -----
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 247 ; disable charset switching
lda #112
sta 808 ; disable run/stop key
rts
}}
}
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #0
sta 247 ; enable charset switching
lda #110
sta 808 ; enable run/stop key
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 c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; 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 restore_irq() clobbers(A) {
%asm {{
sei
lda #<c64.IRQDFRT
sta c64.CINV
lda #>c64.IRQDFRT
sta c64.CINV+1
lda #0
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_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 set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ;enable raster interrupt signals from vic
rts
}}
}
}
c128 {
; ---- C128 specific registers ----
%option no_symbol_prefixing
&ubyte VM1 = $0A2C ; shadow for VUC $d018 in text mode
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
@@ -298,6 +469,48 @@ c128 {
; ---- 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
@@ -325,218 +538,9 @@ asmsub disable_basic() clobbers(A) {
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
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
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 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 sys.enable_runstop_and_charsetswitch
}}
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 247 ; disable charset switching
lda #112
sta 808 ; disable run/stop key
rts
}}
}
asmsub enable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #0
sta 247 ; enable charset switching
lda #110
sta 808 ; enable run/stop key
rts
}}
}
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
sei
sta _modified+1
sty _modified+2
lda #<_irq_handler
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
rts
_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
pla
beq +
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
+ lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
pla
tay
pla
tax
pla
rti
_use_kernal .byte 0
}}
}
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
lda #>cbm.IRQDFRT
sta cbm.CINV+1
lda #0
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sei
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
_raster_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
pla
beq +
jmp cbm.IRQDFRT ; continue with kernal irq routine
+ pla
tay
pla
tax
pla
rti
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ;enable raster interrupt signals from vic
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
@@ -544,7 +548,7 @@ _setup_raster_irq
sei
lda #0
sta $ff00 ; default bank 15
jmp (cbm.RESET_VEC)
jmp (c64.RESET_VEC)
}}
}
@@ -562,9 +566,9 @@ _loop lda P8ZP_SCRATCH_W1
ldx P8ZP_SCRATCH_B1
rts
+ lda cbm.TIME_LO
+ lda c64.TIME_LO
sta P8ZP_SCRATCH_B1
- lda cbm.TIME_LO
- lda c64.TIME_LO
cmp P8ZP_SCRATCH_B1
beq -
@@ -721,40 +725,13 @@ _longcopy
}}
}
inline asmsub irqsafe_set_irqd() {
%asm {{
php
sei
}}
}
inline asmsub irqsafe_clear_irqd() {
%asm {{
plp
}}
}
inline asmsub disable_caseswitch() {
%asm {{
lda #$80
sta 247
}}
}
inline asmsub enable_caseswitch() {
%asm {{
lda #0
sta 247
}}
}
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 cbm.CLRCHN ; reset i/o channels
jsr sys.enable_runstop_and_charsetswitch
jsr c64.CLRCHN ; reset i/o channels
jsr c64.enable_runstop_and_charsetswitch
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
@@ -771,137 +748,113 @@ _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.
&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
; $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
&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
&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
&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 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 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
&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
&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 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 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
}}
}
&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
}
+111 -132
View File
@@ -1,13 +1,10 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
; these work with PETSCII encoding instead.
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
%import syslib
%import conv
txt {
%option no_symbol_prefixing
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
@@ -33,52 +30,13 @@ 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 cbm.PLOT
jsr c64.PLOT
tay
clc
jmp cbm.PLOT
jmp c64.PLOT
}}
}
asmsub get_column() -> ubyte @Y {
%asm {{
sec
jmp cbm.PLOT
}}
}
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
; ---- set the cursor on the given row (starting with 0) on the current line
%asm {{
sec
jsr cbm.PLOT
tax
clc
jmp cbm.PLOT
}}
}
asmsub get_row() -> ubyte @X {
%asm {{
sec
jmp cbm.PLOT
}}
}
sub get_cursor(uword colptr, uword rowptr) {
%asm {{
sec
jsr cbm.PLOT
tya
ldy #$00
sta (colptr),y
txa
sta (rowptr),y
}}
}
asmsub fill_screen (ubyte char @ 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)
@@ -99,10 +57,10 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; (assumes screen matrix is at the default address)
%asm {{
ldy #250
- 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
- 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
dey
bne -
rts
@@ -114,10 +72,10 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; (assumes color matrix is at the default address)
%asm {{
ldy #250
- 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
- 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
dey
bne -
rts
@@ -125,7 +83,7 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
}
sub color (ubyte txtcol) {
cbm.COLOR = txtcol
c64.COLOR = txtcol
}
sub lowercase() {
@@ -138,12 +96,13 @@ sub uppercase() {
c128.VM1 &= ~2
}
asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, X, Y) {
asmsub scroll_left (ubyte 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
%asm {{
stx P8ZP_SCRATCH_REG
bcc _scroll_screen
+ ; scroll the screen and the color memory
@@ -151,10 +110,10 @@ asmsub scroll_left (bool alsocolors @ Pc) clobbers(A, X, Y) {
ldy #38
-
.for row=0, row<=24, row+=1
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
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
.next
inx
dey
@@ -166,32 +125,34 @@ _scroll_screen ; scroll only the screen memory
ldy #38
-
.for row=0, row<=24, row+=1
lda cbm.Screen + 40*row + 1,x
sta cbm.Screen + 40*row + 0,x
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row + 0,x
.next
inx
dey
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_right (bool alsocolors @ Pc) clobbers(A,X) {
asmsub scroll_right (ubyte 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
%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 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
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
.next
dex
bpl -
@@ -201,31 +162,33 @@ _scroll_screen ; scroll only the screen memory
ldx #38
-
.for row=0, row<=24, row+=1
lda cbm.Screen + 40*row + 0,x
sta cbm.Screen + 40*row + 1,x
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_up (bool alsocolors @ Pc) clobbers(A,X) {
asmsub scroll_up (ubyte 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
%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 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
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
.next
dex
bpl -
@@ -235,31 +198,33 @@ _scroll_screen ; scroll only the screen memory
ldx #39
-
.for row=1, row<=24, row+=1
lda cbm.Screen + 40*row,x
sta cbm.Screen + 40*(row-1),x
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_down (bool alsocolors @ Pc) clobbers(A,X) {
asmsub scroll_down (ubyte 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
%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 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
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
.next
dex
bpl -
@@ -269,121 +234,134 @@ _scroll_screen ; scroll only the screen memory
ldx #39
-
.for row=23, row>=0, row-=1
lda cbm.Screen + 40*row,x
sta cbm.Screen + 40*(row+1),x
lda c64.Screen + 40*row,x
sta c64.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 cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string, in PETSCII encoding, from 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 cbm.CHROUT of that single char.
; by just one call to c64.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr cbm.CHROUT
jsr c64.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
asmsub print_ub0 (ubyte value @ A) clobbers(A,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 cbm.CHROUT
jsr c64.CHROUT
pla
jsr cbm.CHROUT
jsr c64.CHROUT
txa
jmp cbm.CHROUT
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
asmsub print_ub (ubyte value @ A) clobbers(A,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 cbm.CHROUT
jsr c64.CHROUT
pla
jsr cbm.CHROUT
jsr c64.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
jsr c64.CHROUT
_ones txa
jmp cbm.CHROUT
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,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 cbm.CHROUT
jsr c64.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
jsr c64.CHROUT
tya
jmp cbm.CHROUT
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,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 cbm.CHROUT
jsr c64.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
+ jsr c64.CHROUT
dey
bne -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
@@ -395,7 +373,7 @@ asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
@@ -408,24 +386,28 @@ asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
asmsub print_uw0 (uword value @ AY) clobbers(A,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 cbm.CHROUT
jsr c64.CHROUT
iny
bne -
+ rts
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
asmsub print_uw (uword value @ AY) clobbers(A,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
@@ -435,25 +417,25 @@ asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
bne -
_gotdigit
jsr cbm.CHROUT
jsr c64.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
jmp c64.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
asmsub print_w (word value @ AY) clobbers(A,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
jsr c64.CHROUT
tya
eor #255
tay
@@ -468,15 +450,14 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
- jsr c64.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
@@ -508,7 +489,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
_mod sta $ffff ; modified
rts
_screenrows .word cbm.Screen + range(0, 1000, 40)
_screenrows .word $0400 + range(0, 1000, 40)
}}
}
@@ -575,7 +556,7 @@ _mod lda $ffff ; modified
}}
}
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
@@ -587,13 +568,13 @@ sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
sta _colormod+2
lda setchr._screenrows,y
clc
adc col
adc column
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda character
+ lda char
_charmod sta $ffff ; modified
lda charcolor
_colormod sta $ffff ; modified
@@ -601,17 +582,22 @@ _colormod sta $ffff ; modified
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ X) {
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
stx P8ZP_SCRATCH_REG
tax
clc
jmp cbm.PLOT
jsr c64.PLOT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr cbm.SCREEN
jsr c64.SCREEN
txa
rts
}}
@@ -620,17 +606,10 @@ asmsub width() clobbers(X,Y) -> ubyte @A {
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr cbm.SCREEN
jsr c64.SCREEN
tya
rts
}}
}
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
beq -
rts
}}
}
}
+369 -99
View File
@@ -5,11 +5,13 @@ 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, X, Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_B1
@@ -17,12 +19,15 @@ ub2float .proc
jsr GIVAYF
_fac_to_mem ldx P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jmp MOVMF
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, X, Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_B1
@@ -32,7 +37,7 @@ b2float .proc
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
; clobbers X
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_W1
@@ -43,7 +48,7 @@ uw2float .proc
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
; clobbers X
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_W1
@@ -55,7 +60,7 @@ w2float .proc
cast_from_uw .proc
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
; clobbers X
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
@@ -63,7 +68,7 @@ cast_from_uw .proc
cast_from_w .proc
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
; clobbers X
stx P8ZP_SCRATCH_REG
jsr GIVAYFAY
jmp ub2float._fac_to_mem
.pend
@@ -71,7 +76,7 @@ cast_from_w .proc
cast_from_ub .proc
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
; clobbers X
stx P8ZP_SCRATCH_REG
jsr FREADUY
jmp ub2float._fac_to_mem
.pend
@@ -79,41 +84,169 @@ cast_from_ub .proc
cast_from_b .proc
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
; clobbers X
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
cast_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast float at A/Y to uword into Y/A
; 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
; clobbers X
jmp GETADR ; into Y/A
stx P8ZP_SCRATCH_REG
jsr GETADR ; into Y/A
ldx P8ZP_SCRATCH_REG
rts
.pend
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
; -- cast fac1 to word into A/Y
; clobbers X
stx P8ZP_SCRATCH_REG
jsr AYINT
ldy floats.AYINT_facmo
lda floats.AYINT_facmo+1
ldx P8ZP_SCRATCH_REG
rts
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp push_fac1._internal
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy P8ESTACK_LO,x
lda P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVAYF
jmp push_fac1._internal
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG
tay
lda #0
jsr GIVAYF
jmp push_fac1._internal
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp push_fac1._internal
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr AYINT
ldx P8ZP_SCRATCH_REG
lda floats.AYINT_facmo
sta P8ESTACK_HI,x
lda floats.AYINT_facmo+1
sta P8ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr GETADR
ldx P8ZP_SCRATCH_REG
sta P8ESTACK_HI,x
tya
sta P8ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_HI,x
dex
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_HI,x
dex
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
dex
rts
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #4
inx
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
dey
inx
lda P8ESTACK_HI,x
sta (P8ZP_SCRATCH_W1),y
dey
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
dey
inx
lda P8ESTACK_HI,x
sta (P8ZP_SCRATCH_W1),y
dey
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
@@ -139,23 +272,25 @@ 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
jmp MOVMF
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.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
@@ -164,7 +299,23 @@ dec_var_f .proc
jsr FSUB
ldx P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp MOVMF
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
@@ -172,9 +323,71 @@ 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? Result in A. Clobbers X.
; -- is the float in FAC1 < the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
beq +
lda #0
@@ -184,8 +397,10 @@ var_fac1_less_f .proc
.pend
var_fac1_lesseq_f .proc
; -- is the float in FAC1 <= the variable AY? Result in A. Clobbers X.
; -- is the float in FAC1 <= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #255
@@ -197,8 +412,10 @@ var_fac1_lesseq_f .proc
.pend
var_fac1_greater_f .proc
; -- is the float in FAC1 > the variable AY? Result in A. Clobbers X.
; -- is the float in FAC1 > the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #1
beq +
lda #0
@@ -208,8 +425,10 @@ var_fac1_greater_f .proc
.pend
var_fac1_greatereq_f .proc
; -- is the float in FAC1 >= the variable AY? Result in A. Clobbers X.
; -- is the float in FAC1 >= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #1
@@ -220,23 +439,17 @@ 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? Result in A. Clobbers X.
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
and #1
rts
.pend
vars_equal_f .proc
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical? Result in A
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
@@ -265,13 +478,51 @@ _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 ? Result in A. Clobbers X.
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
lda #1
@@ -281,11 +532,13 @@ vars_less_f .proc
.pend
vars_lesseq_f .proc
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ? Result in A. Clobbers X.
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
- lda #1
@@ -297,7 +550,7 @@ vars_lesseq_f .proc
.pend
less_f .proc
; -- is f1 < f2? Result in A. Clobbers X.
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
@@ -306,7 +559,7 @@ less_f .proc
lesseq_f .proc
; -- is f1 <= f2? Result in A. Clobbers X.
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
@@ -316,7 +569,7 @@ lesseq_f .proc
.pend
greater_f .proc
; -- is f1 > f2? Result in A. Clobbers X.
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
@@ -324,7 +577,7 @@ greater_f .proc
.pend
greatereq_f .proc
; -- is f1 >= f2? Result in A. Clobbers X.
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
@@ -333,9 +586,32 @@ 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
@@ -346,8 +622,11 @@ set_array_float_from_fac1 .proc
adc P8ZP_SCRATCH_W1
bcc +
iny
+ tax
jmp MOVMF
+ stx floats_store_reg
tax
jsr MOVMF
ldx floats_store_reg
rts
.pend
@@ -390,63 +669,54 @@ set_array_float .proc
.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
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
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
notequal_zero .proc
jsr floats.pop_float_fac1
jsr floats.SIGN
bne equal_zero._true
beq equal_zero._false
.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
+8 -24
View File
@@ -5,11 +5,9 @@
floats {
; ---- this block contains C-64 floating point related functions ----
%option no_symbol_prefixing
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and functions ----
@@ -47,13 +45,13 @@ 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. Also see parse_f()
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
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)
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
@@ -78,7 +76,6 @@ romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
@@ -161,25 +158,12 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
sub rndf() -> float {
%asm {{
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
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
}}
}
asmsub normalize(float value @FAC1) -> float @ FAC1 {
%asm {{
jmp floats.NORMAL
jsr RND ; rng into fac1
ldx P8ZP_SCRATCH_REG
rts
}}
}
+7 -10
View File
@@ -1,6 +1,13 @@
; --- 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
@@ -137,13 +144,3 @@ 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
+51 -64
View File
@@ -1,46 +1,33 @@
%import textio
; bitmap pixel graphics module for the C64
; only black/white monochrome 320x200 for now
;
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
; 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.
; assumes bitmap screen memory is $2000-$3fff
graphics {
%option no_symbol_prefixing
const uword BITMAP_ADDRESS = $2000
const uword WIDTH = 320
const ubyte HEIGHT = 200
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 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.
; 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)
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
sys.memset(CHARS_ADDRESS, 40*25, pixelcolor << 4 | bgcolor)
sys.memset(cbm.Colors, 40*25, 255)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
@@ -138,46 +125,46 @@ graphics {
}
}
sub rect(uword xx, ubyte yy, uword width, ubyte height) {
sub rect(uword x, ubyte y, uword width, ubyte height) {
if width==0 or height==0
return
horizontal_line(xx, yy, width)
horizontal_line(x, y, width)
if height==1
return
horizontal_line(xx, yy+height-1, width)
vertical_line(xx, yy+1, height-2)
horizontal_line(x, y+height-1, width)
vertical_line(x, y+1, height-2)
if width==1
return
vertical_line(xx+width-1, yy+1, height-2)
vertical_line(x+width-1, y+1, height-2)
}
sub fillrect(uword xx, ubyte yy, uword width, ubyte height) {
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
if width==0
return
repeat height {
horizontal_line(xx, yy, width)
yy++
horizontal_line(x, y, width)
y++
}
}
sub horizontal_line(uword xx, ubyte yy, uword length) {
sub horizontal_line(uword x, ubyte y, uword length) {
if length<8 {
internal_plotx=xx
internal_plotx=x
repeat lsb(length) {
internal_plot(yy)
internal_plot(y)
internal_plotx++
}
return
}
ubyte separate_pixels = lsb(xx) & 7
uword pixaddr = get_y_lookup(yy) + (xx&$fff8)
ubyte separate_pixels = lsb(x) & 7
uword addr = get_y_lookup(y) + (x&$fff8)
if separate_pixels {
%asm {{
lda pixaddr
lda addr
sta P8ZP_SCRATCH_W1
lda pixaddr+1
lda addr+1
sta P8ZP_SCRATCH_W1+1
ldy separate_pixels
lda hline_filled_right,y
@@ -186,7 +173,7 @@ graphics {
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y
}}
pixaddr += 8
addr += 8
length += separate_pixels
length -= 8
}
@@ -196,15 +183,16 @@ 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 pixaddr
lda addr
sta _modified+1
lda pixaddr+1
lda addr+1
sta _modified+2
lda length
ora length+1
@@ -220,7 +208,8 @@ _modified stx $ffff ; modified
inc _modified+2
+ dey
bne _modified
_zero
_zero ldx P8ZP_SCRATCH_REG
ldy separate_pixels
beq hline_zero2
lda _modified+1
@@ -238,17 +227,16 @@ hline_zero2
}
}
sub vertical_line(uword xx, ubyte yy, ubyte height) {
internal_plotx = xx
sub vertical_line(uword x, ubyte y, ubyte height) {
internal_plotx = x
repeat height {
internal_plot(yy)
yy++
internal_plot(y)
y++
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm.
; Midpoint algorithm
if radius==0
return
ubyte @zp ploty
@@ -277,17 +265,16 @@ hline_zero2
internal_plotx = xcenter - yy
internal_plot(ploty)
yy++
if decisionOver2>=0 {
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
radius--
decisionOver2 -= radius*$0002
decisionOver2 += (yy as word -radius)*2+1
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
if radius==0
return
@@ -300,12 +287,12 @@ hline_zero2
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2>=0 {
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
radius--
decisionOver2 -= radius*$0002
decisionOver2 += (yy as word -radius)*2+1
}
decisionOver2 += yy*$0002
decisionOver2++
}
}
@@ -313,8 +300,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 pixaddr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(pixaddr) |= ormask[lsb(px) & 7]
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(addr) |= ormask[lsb(px) & 7]
; }
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
@@ -365,7 +352,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 := BITMAP_ADDRESS + 320*(range(200)>>3) + (range(200) & 7)
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
_y_lookup_lo .byte <_plot_y_values
_y_lookup_hi .byte >_plot_y_values
@@ -373,7 +360,7 @@ _y_lookup_hi .byte >_plot_y_values
}}
}
asmsub get_y_lookup(ubyte yy @Y) -> uword @AY {
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
%asm {{
lda internal_plot._y_lookup_lo,y
pha
+292 -374
View File
@@ -1,13 +1,7 @@
; Prog8 definitions for the Commodore-64
; Including memory registers, I/O registers, Basic and Kernal subroutines.
cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
c64 {
&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
@@ -56,88 +50,7 @@ cbm {
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
@@ -287,47 +200,92 @@ c64 {
; ---- end of SID registers ----
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
}
; ---- C64 ROM kernal routines ----
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
}
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_bitmap_ptr() -> uword {
return get_vic_memory_base() + ((c64.VMCSB & %00001000) as uword << 10)
}
; ---- end of C64 ROM kernal routines ----
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
}
; ---- utilities -----
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 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
}}
}
sys {
; ------- lowlevel system routines --------
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
}}
}
%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.
@@ -335,7 +293,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.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
%asm {{
sei
cld
@@ -343,13 +301,13 @@ asmsub init_system() {
sta $00
lda #%00100111
sta $01
jsr cbm.IOINIT
jsr cbm.RESTOR
jsr cbm.CINT
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda #6
sta c64.EXTCOL
lda #7
sta cbm.COLOR
sta c64.COLOR
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
@@ -369,7 +327,7 @@ asmsub init_system_phase2() {
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
jmp sys.enable_runstop_and_charsetswitch
jmp c64.enable_runstop_and_charsetswitch
}}
}
@@ -393,86 +351,91 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
}}
}
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub set_irq(uword handler @AY) clobbers(A) {
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sei
sta _modified+1
sty _modified+2
sta _modified+1
sty _modified+2
lda #0
rol a
sta _use_kernal
sei
lda #<_irq_handler
sta cbm.CINV
sta c64.CINV
lda #>_irq_handler
sta cbm.CINV+1
sta c64.CINV+1
cli
rts
_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
pla
beq +
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
+ lda #$ff
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
}}
+ jmp c64.IRQDFRT ; 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 restore_irq() clobbers(A) {
%asm {{
sei
lda #<cbm.IRQDFRT
sta cbm.CINV
lda #>cbm.IRQDFRT
sta cbm.CINV+1
lda #<c64.IRQDFRT
sta c64.CINV
lda #>c64.IRQDFRT
sta c64.CINV+1
lda #0
sta c64.IREQMASK ; disable raster irq
lda #%10000001
@@ -482,39 +445,40 @@ asmsub restore_irq() clobbers(A) {
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sei
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
sta _modified+1
sty _modified+2
lda #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
pla
beq +
jmp cbm.IRQDFRT ; continue with kernal irq routine
+ pla
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 set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
@@ -538,14 +502,23 @@ _setup_raster_irq
}}
}
; ---- end of C64 specific system utility routines ----
asmsub reset_system() {
}
sys {
; ------- lowlevel system routines --------
const ubyte target = 64 ; 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 #14
sta $01 ; bank the kernal in
jmp (cbm.RESET_VEC)
jmp (c64.RESET_VEC)
}}
}
@@ -563,9 +536,9 @@ _loop lda P8ZP_SCRATCH_W1
ldx P8ZP_SCRATCH_B1
rts
+ lda cbm.TIME_LO
+ lda c64.TIME_LO
sta P8ZP_SCRATCH_B1
- lda cbm.TIME_LO
- lda c64.TIME_LO
cmp P8ZP_SCRATCH_B1
beq -
@@ -722,40 +695,13 @@ _longcopy
}}
}
inline asmsub irqsafe_set_irqd() {
%asm {{
php
sei
}}
}
inline asmsub irqsafe_clear_irqd() {
%asm {{
plp
}}
}
inline asmsub disable_caseswitch() {
%asm {{
lda #$80
sta 657
}}
}
inline asmsub enable_caseswitch() {
%asm {{
lda #0
sta 657
}}
}
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 cbm.CLRCHN ; reset i/o channels
jsr sys.enable_runstop_and_charsetswitch
jsr c64.CLRCHN ; reset i/o channels
jsr c64.enable_runstop_and_charsetswitch
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
@@ -773,139 +719,111 @@ _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 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
; (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
&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
&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
&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 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 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
&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
&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 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 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
}
Loaded 100 of 351 files, more files were not shown because too many files have changed in this diff. Show more