Compare commits

..
117 Commits
Author SHA1 Message Date
Irmen de Jong 13ab4166c0 new kotest library version 2023-10-19 21:57:06 +02:00
Irmen de Jong 3dc5a0e7f8 some arrays can be in BSS 2023-10-18 23:59:37 +02:00
Irmen de Jong e15c5cde53 tiny fill() optimization 2023-10-18 23:11:16 +02:00
Irmen de Jong d88c09b098 fix signed byte to word casting issue uw = 8888 + (bb as ubyte) 2023-10-17 22:54:33 +02:00
Irmen de Jong 893b383bdf fix signed byte to word sign extension in assignment 2023-10-17 03:08:37 +02:00
Irmen de Jong dd7c9d62e6 remove assigment splitter, it now caused code bloat instead of more efficient code 2023-10-16 02:07:22 +02:00
Irmen de Jong 97c5c90eff fix codegen for var1>>=var2 and var1<<=var2 when var2 is zero 2023-10-16 00:04:21 +02:00
Irmen de Jong 1fb94e7a7b monogfx and gfx2: flood fill uses optimized horizontal line drawing 2023-10-15 23:19:11 +02:00
Irmen de Jong daca87c6d0 added -breakinstr compiler option 2023-10-15 21:55:09 +02:00
Irmen de Jong 203ec5fa46 implement taking address of array var with variable index 2023-10-15 20:24:48 +02:00
Irmen de Jong 9ea69c07b8 optimize word array reads with indexvar 2023-10-14 07:30:54 +02:00
Irmen de Jong 68539d6cc9 micro tweaks adpcm.p8 2023-10-13 00:55:56 +02:00
Irmen de Jong f75fd0811e restructure play-adpcm example code, stream-wav can now play stereo adpcm wavs 2023-10-11 17:37:42 +02:00
Irmen de Jong 836bc9d456 added verafx.available() 2023-10-10 22:12:21 +02:00
Irmen de Jong a37769aafe cx16 adpcm example is now able to decode and play stereo music as well as mono. 2023-10-10 02:41:20 +02:00
Irmen de Jong 68e62e4bd2 added cx16.MCIOUT() kernal call
correct case of several other cx16 kernal calls.

corrected to upper case: cx16 kernal calls CLOSE_ALL, LKUPLA, LKUPSA, JSRFAR, PRIMM, MACPTR.
2023-10-09 22:44:36 +02:00
Irmen de Jong a5cd3728c9 3d rotation multiplications now using verafx acceleration 2023-10-05 22:36:30 +02:00
Irmen de Jong a48ce35f0b added %option verafxmuls 2023-10-05 22:06:33 +02:00
Irmen de Jong e1835b5775 removed dysfunctional c128.graphics library module 2023-10-05 21:03:47 +02:00
Irmen de Jong 433832b329 gfx2.clear_screen and monogfx.clear_screen() now have color parameter to clear the screen with
this is much faster than filling a rectangle of the full screen size with a color.
2023-10-05 21:00:39 +02:00
Irmen de Jong ee81da14d6 cx16: removed monochrome modes from gfx2 (use monogfx instead). New screen mode numbering!
programs will now be a lot smaller than before if they use gfx2 (or monogfx if they were only using monochrome drawing)
monogfx also fixes some drawing errors with small horizontal lines, and stippled vertical lines.
2023-10-05 02:12:46 +02:00
Irmen de Jong 6395d1908e cx16: added monogfx library module, replaces gfx2 for monochrome screenmodes. 2023-10-04 22:32:13 +02:00
Irmen de Jong 989a5a2f8a some notes about array alignment 2023-10-04 01:10:36 +02:00
Irmen de Jong b7a622c68e fix alignment of uninitialized arrays in aligned blocks (make them initialized with zeros so they don't end up in the BSS section)
fix alignment of uninitialized arrays in aligned blocks (make them initialized with zeros so they don't end up in the BSS section)
2023-10-04 00:12:36 +02:00
Irmen de Jong a8507b437d add verafx.transparency() 2023-10-03 01:47:52 +02:00
Irmen de Jong e505bf9ccf added "emudbg" library (cx16 only) to interface with the emulator 2023-10-02 22:23:09 +02:00
Irmen de Jong a289b32053 Revert "added -verafxmul compiler option to use vera fx multiplication routine on cx16"
This reverts commit 690782bf.
It was too risky, using vera (especially fx) transparently in multiple places especially perhaps in IRQ handlers will create havoc unless much intricate care is taken to save/restore the vera state. Better to do vera fx explicitly where the programmer has full control.
2023-10-02 21:08:52 +02:00
Irmen de Jong c3f1f09ad1 added verafx.clear() 2023-10-02 01:34:56 +02:00
Irmen de Jong 70ee2026ff fix gfx2 screen fill broken when using verafx 2023-10-02 00:12:48 +02:00
Irmen de Jong 690782bf60 added -verafxmul compiler option to use vera fx multiplication routine on cx16 2023-10-01 22:44:45 +02:00
Irmen de Jong 755cc4835e \n (newline) now also maps to Petscii $0d (return), like \r.
It used to map to $8d (shift-return)
2023-09-29 01:49:15 +02:00
Irmen de Jong a684ea46e4 fix c64 zp test and improve error for text encoding problem 2023-09-29 01:25:05 +02:00
Irmen de Jong 8fbe13f99d c64: $a5 removed from free ZP (it's actually used by kernal disk routines) 2023-09-29 00:28:04 +02:00
Irmen de Jong 452e9e275f diskio module: set correct read or write i/o channel every time f_read or f_write is called 2023-09-28 23:39:37 +02:00
Irmen de Jong cd40088636 vm: added math.mul16_last_upper() 2023-09-28 03:18:49 +02:00
Irmen de Jong 9b9e6f4af5 added math.mul16_last_upper() to fetch the upper 16 bits of the last word multiplication 2023-09-25 23:59:57 +02:00
Irmen de Jong ae6eeadf54 doc about range step value has to be a constant 2023-09-25 23:19:32 +02:00
Irmen de Jong 5268b05060 added bonkram chunk to chunkfile example 2023-09-25 22:24:40 +02:00
Irmen de Jong 390263a34e added cx16 verafx library module 2023-09-24 23:00:40 +02:00
Irmen de Jong 55646edc3e added cx16 chunkedfile example 2023-09-24 20:56:36 +02:00
Irmen de Jong 8d177beb78 fix possible register corruption when calling asmsubs that require Carry flag as a parameter 2023-09-24 14:03:31 +02:00
Irmen de Jong 1da0c59182 vm: remove BNER opcode -> CMP + BSTNE 2023-09-23 11:47:24 +02:00
Irmen de Jong 36e8f10d2b vm: remove BEQR opcode -> CMP + BSTEQ 2023-09-23 11:42:58 +02:00
Irmen de Jong cdf5a8f20f vm: remove BNE opcode -> CMPI + BSTNE 2023-09-23 11:22:33 +02:00
Irmen de Jong eb64d92333 vm: remove BEQ opcode -> CMPI + BSTEQ 2023-09-23 11:21:43 +02:00
Irmen de Jong eb55da63ef weird 2023-09-23 11:21:17 +02:00
Irmen de Jong 918302f79b ir: fix possible crash in validity check about PREPARECALL 2023-09-23 01:35:18 +02:00
Irmen de Jong 9d7131d9f6 vm: setting status bits 2023-09-22 22:50:20 +02:00
Irmen de Jong 229c1114dd vm: fixed array initialization values with address-ofs 2023-09-19 23:54:18 +02:00
Irmen de Jong 885df9156f todo 2023-09-19 00:08:17 +02:00
Irmen de Jong c319233ddc ir: added preparecall 'meta' instruction for functioncalls 2023-09-18 23:22:03 +02:00
Irmen de Jong 958b5c0780 Merge branch 'addrof-arrayelt'
# Conflicts:
#	docs/source/todo.rst
2023-09-18 04:48:45 +02:00
Irmen de Jong 880c0a5da8 allow taking address of array element 2023-09-18 04:37:41 +02:00
Irmen de Jong 237c6dc856 allow taking address of array element 2023-09-18 04:29:15 +02:00
Irmen de Jong ccf6e32bf9 adding setlsb() and setmsb() builtin functions to 6502 codegen 2023-09-17 15:16:47 +02:00
Irmen de Jong a1874f6f00 adding setlsb() and setmsb() builtin functions to 6502 codegen 2023-09-17 01:48:29 +02:00
Irmen de Jong 95e4490a8a adding setlsb() and setmsb() builtin functions 2023-09-15 02:39:16 +02:00
Irmen de Jong 31c132c2eb several optimizations and compiler error fix for @(&var) and @(&var+1) 2023-09-14 23:04:23 +02:00
Irmen de Jong 00b0ec58b4 update to Antlr 4.13.1 2023-09-14 21:11:55 +02:00
Irmen de Jong a1d0e5bb65 added list of software to docs 2023-09-13 21:51:48 +02:00
Irmen de Jong 03e0d4b2e8 reducing expression codegen complexity (no longer splitting conditional expressions, and using r9 as temp var) 2023-09-13 01:08:42 +02:00
Irmen de Jong 6afdd4e6fd preparing next version 2023-09-12 21:53:49 +02:00
Irmen de Jong b500a0d477 c64: added a couple of routines that calculate the correct memory locations for video ram and sprite pointers etc. based on current VIC-II memory setup.
the examples with sprites, now use it.
2023-09-08 21:27:38 +02:00
Irmen de Jong dd2463a440 proper fix for the previous commit. + fix for i/o channel reset in diskio.f_seek()
it wasn't the adressing mode, it was that it assumed the pointer variable was always in zeropage (which might not be)
2023-09-07 22:17:46 +02:00
Irmen de Jong 23a8bebd9e fix invalid addressing mode on 6502 cpu for bytevalue +/- bytearray[i] 2023-09-07 21:40:07 +02:00
Irmen de Jong 3caf9108ad finalizing 9.4.1 release 2023-09-06 21:18:01 +02:00
Irmen de Jong bde4be8231 fix VM indexed instructions to only use lsb part of the index 2023-09-06 02:44:04 +02:00
Irmen de Jong 0bbbb12ed2 fix bench8 examples 2023-09-05 23:40:54 +02:00
Irmen de Jong b570bdaed7 fix codegen for array[i] += float expression 2023-09-05 22:38:52 +02:00
Irmen de Jong 8c0843cc87 fix an invalid 6502 instruction on c64 in certain float assignment 2023-09-05 21:54:52 +02:00
Irmen de Jong 31458ffd81 examples cleanup and improving c64 graphics module (shift bitmap to higher ram area) 2023-09-05 20:39:12 +02:00
Irmen de Jong c15c10a94e fixed 'unroll CONSTANTEXPR' compiler errors 2023-09-05 01:03:35 +02:00
Irmen de Jong 9fca978725 optimized plasma examples even more 2023-09-05 00:23:50 +02:00
Irmen de Jong b125901717 added cx16 plasma example 2023-09-04 23:54:13 +02:00
Irmen de Jong eb018ae660 code optimization for bytearray[x] +/- bytearray[y]
use adc array,y or sbc array,y instead of tempvar
2023-09-04 23:01:53 +02:00
Irmen de Jong 7e5a9474fe improve plasma example 2023-09-04 20:35:43 +02:00
Irmen de Jong 525a9b5036 prepare parser to allow chained array indexing later 2023-09-03 19:06:47 +02:00
Irmen de Jong c3fbdf34ca fixed c64 float problem 2023-09-03 16:40:10 +02:00
Irmen de Jong 48bd51e1a5 c64 float problem 2023-09-03 16:29:01 +02:00
Irmen de Jong 10d0b03a90 use less tempvars 2023-09-03 01:32:47 +02:00
Irmen de Jong e1b3582f08 fix wordvar -= @(memory) 2023-09-03 01:12:26 +02:00
Irmen de Jong 95be1c9e22 fix optimized swapped in-place byte comparisons 2023-09-03 00:47:55 +02:00
Irmen de Jong 1ce8fe06d5 fix in-place <= for bytes 2023-09-03 00:01:11 +02:00
Irmen de Jong 15c649024e float problems on c64 2023-09-02 23:09:55 +02:00
Irmen de Jong e97303c226 fix word multiplication to not clobber r0 and r1 anymore
This was causing corruption in certain programs such as the cx16/amiga example.
The problem was introduced in 9.4 with the new multiply_words routine
2023-09-02 20:52:16 +02:00
Irmen de Jong 3b786c819d avoid using temp var even more 2023-09-01 23:47:01 +02:00
Irmen de Jong 04959dbd8b optimize asm: don't use temp var for some additions 2023-09-01 22:24:17 +02:00
Irmen de Jong 5cd4b874ea tweak sprites module 2023-09-01 21:25:19 +02:00
Irmen de Jong f14ea1b3de micro optimization to save 2 cycles: change some pha+pla into tax+txa 2023-09-01 20:37:24 +02:00
Irmen de Jong 9cc0cda0fb added sprites library module (cx16 only) 2023-09-01 17:35:07 +02:00
Irmen de Jong 09a7a4bbe5 optimize comparison against zero 2023-09-01 02:28:11 +02:00
Irmen de Jong cfea8b3745 save a cycle 2023-09-01 00:50:24 +02:00
Irmen de Jong 28bf0b61ce added math.log2() and math.log2w() 2023-09-01 00:42:15 +02:00
Irmen de Jong 2dc2429735 tweaks to the cx16 sprite example 2023-08-31 23:24:46 +02:00
Irmen de Jong 83d4592526 tweaks to the cx16 sprite example 2023-08-31 22:33:49 +02:00
Irmen de Jong 2d528c26ae added cx16 sprite demo 2023-08-31 16:56:52 +02:00
Irmen de Jong 66b3dce794 doc tweak 2023-08-30 13:16:39 +02:00
Irmen de Jong 93f77a1045 version 9.4 2023-08-29 12:27:09 +02:00
Irmen de Jong aa4d23a3d5 fix register stack saving on certain expression code that was broken on 6502 but not on 65c02 2023-08-29 11:50:35 +02:00
Irmen de Jong 2d7ebff8e9 fix shadowing warnings in asm and library code 2023-08-29 11:00:53 +02:00
Irmen de Jong bad9dd3b3b mention shadowing warnings from assembler 2023-08-28 16:55:28 +02:00
Irmen de Jong 2f4e517857 update to Kotlin 1.9.10 2023-08-28 16:45:59 +02:00
Irmen de Jong ff35ba3696 added warnshadow cli option to enable assembler warnings about symbol shadowing 2023-08-28 16:41:46 +02:00
Irmen de Jong 72768e7fad todo 2023-08-28 16:10:02 +02:00
Irmen de Jong 77f3852cdc added floats.parse_f() 2023-08-16 14:47:20 +02:00
Irmen de Jong 66857ca477 prepare parser to be more flexible with array indexed expressions 2023-08-15 13:07:01 +02:00
Irmen de Jong 75514fc7af fix some invalid instructions on 6502 (instead of 65c02) target for bit shifts 2023-08-14 21:58:26 +02:00
Irmen de Jong be06d871b6 fix code for bitwise shifts by zero 2023-08-14 21:49:13 +02:00
Irmen de Jong f98ee326b4 error when doing txt.print('@') where "@" was intended (byte for string parameter) 2023-08-14 19:25:31 +02:00
Irmen de Jong bc8126eb16 2x faster word multiplication routine 2023-08-14 18:11:30 +02:00
Irmen de Jong 4c8beefdcb slightly faster integer bytes multiplication routine 2023-08-14 17:00:16 +02:00
Irmen de Jong bbb6c53457 slightly faster sqrt() routine for integers 2023-08-14 17:00:02 +02:00
Irmen de Jong d8991894e3 added pet stubs for cbm.SETTIM,RDTIM,RDTIM16 2023-08-14 14:49:59 +02:00
Irmen de Jong c7b7dcfd03 made pet textio more compatible with the other platforms by putting the (dummy) color arguments back 2023-08-14 13:51:15 +02:00
Irmen de Jong 2c9e50873c use math.square for optimized X*X calculation (words only).
Added IR SQUARE instruction.
2023-08-14 01:05:17 +02:00
Irmen de Jong 923367296d fix reset_system() on PET, added some missing kernal routines 2023-08-13 01:46:25 +02:00
Irmen de Jong 151a206617 experimental Commodore PET target 2023-08-12 23:25:07 +02:00
180 changed files with 7567 additions and 3953 deletions

No files matched your search

+3 -3
View File
@@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.12.0">
<properties maven-id="org.antlr:antlr4:4.13.1">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.12.0/antlr4-4.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.12.0/antlr4-runtime-4.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
+6 -6
View File
@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.6.2" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.7.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/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.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-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/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.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
</CLASSES>
-24
View File
@@ -1,24 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.property.jvm" type="repository">
<properties maven-id="io.kotest:kotest-property-jvm:5.6.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.6.2/kotest-property-jvm-5.6.2.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.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-common-jvm/5.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.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$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+13 -13
View File
@@ -1,29 +1,29 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.6.2" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.7.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.6.2/kotest-runner-junit5-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.6.2/kotest-framework-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.7.2/kotest-runner-junit5-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.7.2/kotest-framework-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/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.6.2/kotest-common-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.6.2/kotest-framework-engine-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.157/classgraph-4.8.157.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.7.2/kotest-framework-engine-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.6.2/kotest-framework-discovery-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.7.2/kotest-framework-discovery-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.6.2/kotest-extensions-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.6.2/kotest-framework-concurrency-jvm-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.7.2/kotest-extensions-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.7.2/kotest-framework-concurrency-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
+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.5" />
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+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.7" />
<properties maven-id="org.slf4j:slf4j-simple:2.0.9" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.7/slf4j-simple-2.0.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.9/slf4j-simple-2.0.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+3
View File
@@ -16,6 +16,9 @@
</list>
</option>
</component>
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
+2
View File
@@ -74,6 +74,8 @@ What does Prog8 provide?
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- "pet32": Commodore PET (experimental)
- "atari": Atari 8 bit such as 800XL (experimental)
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
+2 -2
View File
@@ -32,10 +32,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
+1 -4
View File
@@ -196,13 +196,10 @@ class StStaticVariable(name: String,
}
if(onetimeInitializationNumericValue!=null) {
require(dt in NumericDatatypes)
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
}
if(onetimeInitializationArrayValue!=null) {
require(dt in ArrayDatatypes)
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
}
require(length==onetimeInitializationArrayValue.size)
}
if(onetimeInitializationStringValue!=null) {
require(dt == DataType.STR)
+7 -4
View File
@@ -74,10 +74,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
numElements = value.value.length + 1 // include the terminating 0-byte
}
is PtArray -> {
val array = makeInitialArray(value)
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
initialArray = makeInitialArray(value)
initialString = null
numElements = array.size
numElements = initialArray.size
require(node.arraySize?.toInt()==numElements)
}
else -> {
@@ -126,7 +125,11 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
return value.children.map {
when(it) {
is PtAddressOf -> StArrayElement(null, it.identifier.name)
is PtAddressOf -> {
if(it.isFromArrayElement)
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name)
}
is PtIdentifier -> StArrayElement(null, it.name)
is PtNumber -> StArrayElement(it.number, null)
else -> throw AssemblyError("invalid array element $it")
+1
View File
@@ -75,6 +75,7 @@ class PtBlock(name: String,
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
+17 -2
View File
@@ -24,7 +24,17 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
infix fun isSameAs(other: PtExpression): Boolean {
return when(this) {
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
is PtAddressOf -> {
if(other !is PtAddressOf)
return false
if (other.type!==type || !(other.identifier isSameAs identifier))
return false
if(other.children.size!=children.size)
return false
if(children.size==1)
return true
return arrayIndexExpr!! isSameAs other.arrayIndexExpr!!
}
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index && other.splitWords==splitWords
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
@@ -105,7 +115,12 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
val identifier: PtIdentifier
get() = children.single() as PtIdentifier
get() = children[0] as PtIdentifier
val arrayIndexExpr: PtExpression?
get() = if(children.size==2) children[1] as PtExpression else null
val isFromArrayElement: Boolean
get() = children.size==2
}
@@ -70,6 +70,8 @@ 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),
@@ -78,7 +80,9 @@ 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_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),
@@ -129,4 +133,4 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
@@ -13,6 +13,7 @@ class CompilationOptions(val output: OutputType,
val compTarget: ICompilationTarget,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var warnSymbolShadowing: Boolean = false,
var optimize: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
@@ -20,6 +21,7 @@ class CompilationOptions(val output: OutputType,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,
var splitWordArrays: Boolean = false,
var breakpointCpuInstruction: Boolean = false,
var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap()
) {
@@ -0,0 +1,20 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.cbm.CbmMemorySizer
import prog8.code.target.pet.PETMachineDefinition
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = PETMachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "pet32"
}
}
@@ -53,11 +53,11 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
).map{it.toUInt()})
}
if(options.zeropage!= ZeropageType.DONTUSE) {
if(options.zeropage != ZeropageType.DONTUSE) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
@@ -24,7 +24,7 @@ object PetsciiEncoding {
'\ufffe', // 0x0A -> UNDEFINED
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\r' , // 0x0D -> CARRIAGE RETURN
'\n' , // 0x0D -> LINE FEED (RETURN)
'\u000e', // 0x0E -> SHIFT OUT
'\ufffe', // 0x0F -> UNDEFINED
'\ufffe', // 0x10 -> UNDEFINED
@@ -152,7 +152,7 @@ object PetsciiEncoding {
'\uf113', //  0x8A -> FUNCTION KEY 4 (CUS)
'\uf115', //  0x8B -> FUNCTION KEY 6 (CUS)
'\uf117', //  0x8C -> FUNCTION KEY 8 (CUS)
'\n' , // 0x8D -> LINE FEED
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\u000f', //  0x8E -> SHIFT IN
'\ufffe', // 0x8F -> UNDEFINED
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
@@ -283,7 +283,7 @@ object PetsciiEncoding {
'\ufffe', // 0x0A -> UNDEFINED
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\r' , // 0x0D -> CARRIAGE RETURN
'\n' , // 0x0D -> LINE FEED (RETURN)
'\u000e', // 0x0E -> SHIFT OUT
'\ufffe', // 0x0F -> UNDEFINED
'\ufffe', // 0x10 -> UNDEFINED
@@ -411,7 +411,7 @@ object PetsciiEncoding {
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
'\n' , // 0x8D -> LINE FEED
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
'\u000f', // 0x8E -> SHIFT IN
'\ufffe', // 0x8F -> UNDEFINED
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
@@ -1061,6 +1061,7 @@ object PetsciiEncoding {
'}' -> '├'
'|' -> '│'
'\\' -> '╲'
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
else -> chr
}
@@ -1076,7 +1077,10 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
if(chr.isISOControl())
throw CharConversionException("no ${case}Petscii character for char #${chr.code}")
else
throw CharConversionException("no ${case}Petscii character for char #${chr.code} '${chr}'")
}
}
}
@@ -1119,7 +1123,10 @@ object PetsciiEncoding {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
if(chr.isISOControl())
throw CharConversionException("no ${case}Screencode character for char #${chr.code}")
else
throw CharConversionException("no ${case}Screencode character for char #${chr.code} '${chr}'")
}
}
}
@@ -0,0 +1,55 @@
package prog8.code.target.pet
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
class PETMachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val BSSHIGHRAM_START = 0xffffu
override val BSSHIGHRAM_END = 0xffffu
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The pet target only supports the main emulator (Vice).")
return
}
println("\nStarting PET emulator...")
val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString())
val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process=processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = PETZeropage(compilerOptions)
// there's no golden ram.
}
}
@@ -0,0 +1,58 @@
package prog8.code.target.pet
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
// reference: http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xb3u // temp storage for a single byte
override val SCRATCH_REG = 0xb4u // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xb6u // temp storage 1 for a word
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
init {
if (options.floats) {
throw InternalCompilerException("PET target doesn't yet support floating point routines")
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x00u..0xffu)
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x00u..0xffu)
free.removeAll(listOf(0x8du, 0x8eu, 0x8fu, 0x97u, 0x98u, 0x99u, 0x9au, 0x9bu, 0x9eu, 0xa7u, 0xa8u, 0xa9u, 0xaau)) // these are updated/used by IRQ
}
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.addAll(0xb3u..0xbau) // TODO more?
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
}
override fun allocateCx16VirtualRegisters() {
TODO("Not known if PET can put the virtual regs in ZP")
}
}
+3 -3
View File
@@ -30,16 +30,16 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
@@ -1026,6 +1026,15 @@ $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) {
@@ -2417,8 +2426,7 @@ $repeatLabel""")
cmp #<$number
bne $jumpIfFalseLabel
cpy #>$number
bne $jumpIfFalseLabel
""")
bne $jumpIfFalseLabel""")
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -2426,18 +2434,21 @@ $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)
out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel
""")
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
// assignmentAsmGen.assignAddressOf(target, name, right.arrayIndexExpr)
} else {
out("""
cmp #<$name
bne $jumpIfFalseLabel
cpy #>$name
bne $jumpIfFalseLabel""")
}
}
else -> {
if(left.isSimple()) {
@@ -2518,12 +2529,16 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
out("""
cmp #<$name
bne +
cpy #>$name
beq $jumpIfFalseLabel
+""")
}
}
else -> {
if(left.isSimple()) {
@@ -2849,8 +2864,12 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
val name = asmSymbolName(left.identifier)
code("#>$name", "#<$name")
return true
if(left.isFromArrayElement) {
TODO("address-of array element $name at ${left.position}")
} else {
code("#>$name", "#<$name")
return true
}
}
is PtIdentifier -> {
assignExpressionToRegister(right, RegisterOrPair.AY)
@@ -2898,8 +2917,12 @@ $repeatLabel""")
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
val name = asmSymbolName(right.identifier)
code("#>$name", "#<$name")
return true
if(right.isFromArrayElement) {
TODO("address-of array element $name at ${right.position}")
} else {
code("#>$name", "#<$name")
return true
}
}
is PtIdentifier -> {
assignExpressionToRegister(left, RegisterOrPair.AY)
@@ -3031,7 +3054,11 @@ $repeatLabel""")
}
internal fun popFAC1() {
out(" jsr floats.popFAC1")
out(" clc | jsr floats.popFAC")
}
internal fun popFAC2() {
out(" sec | jsr floats.popFAC")
}
internal fun needAsaveForExpr(arg: PtExpression): Boolean =
@@ -43,6 +43,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods = optimizeUnneededTempvarInAdd(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
if(mods.isNotEmpty()) {
@@ -508,4 +515,27 @@ private fun optimizeUselessPushPopStack(linesByFour: List<List<IndexedValue<Stri
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
}
@@ -22,15 +22,20 @@ internal class AssemblyProgram(
val assemblerCommand: List<String>
when (compTarget.name) {
in setOf("c64", "c128", "cx16") -> {
in setOf("c64", "c128", "cx16", "pet32") -> {
// CBM machines .prg generation.
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"-Wall", // "-Wno-strict-bool", "-Werror",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
)
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
@@ -59,10 +64,15 @@ internal class AssemblyProgram(
// TODO are these options okay for atari?
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"-Wall", // "-Werror", "-Wno-strict-bool"
"--no-monitor"
)
if(options.warnSymbolShadowing)
command.add("-Wshadow")
else
command.add("-Wno-shadow")
if(options.asmQuiet)
command.add("--quiet")
@@ -41,6 +41,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
"ror2" -> funcRor2(fcall)
"setlsb" -> funcSetLsbMsb(fcall, false)
"setmsb" -> funcSetLsbMsb(fcall, true)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
@@ -72,12 +74,31 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"prog8_lib_stringcompare" -> funcStringCompare(fcall)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD)
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}
return BuiltinFunctions.getValue(fcall.name).returnType
}
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType) {
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
when (resultType) {
DataType.UBYTE -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A)
asmgen.out(" tay | jsr math.multiply_bytes")
}
DataType.UWORD -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr math.square")
}
else -> {
throw AssemblyError("optimized square only for integer types")
}
}
}
private fun funcDivmod(fcall: PtBuiltinFunctionCall) {
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
@@ -162,7 +183,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.jsrfar
jsr cx16.JSRFAR
+ .word 0
+ .byte 0""")
// note that by convention the values in A+Y registers are now the return value of the call.
@@ -590,6 +611,85 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE)
}
private fun funcSetLsbMsb(fcall: PtBuiltinFunctionCall, msb: Boolean) {
val target: AsmAssignTarget
when(fcall.args[0]) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(fcall.args[0] as PtIdentifier) + if(msb) "+1" else ""
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, variableAsmName = varname)
}
is PtNumber -> {
val num = (fcall.args[0] as PtNumber).number + if(msb) 1 else 0
val mem = PtMemoryByte(fcall.position)
mem.add(PtNumber(DataType.UBYTE, num, fcall.position))
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
}
is PtAddressOf -> {
val mem = PtMemoryByte(fcall.position)
if(msb) {
val address = PtBinaryExpression("+", DataType.UWORD, fcall.args[0].position)
address.add(fcall.args[0])
address.add(PtNumber(address.type, 1.0, fcall.args[0].position))
mem.add(address)
} else {
mem.add(fcall.args[0])
}
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
}
is PtArrayIndexer -> {
val indexer = fcall.args[0] as PtArrayIndexer
require(!indexer.usesPointerVariable)
val elementSize: Int
val msbAdd: Int
if(indexer.splitWords) {
val arrayVariable = indexer.variable
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.ARRAY_UB, arrayVariable.position)
indexer.children[0].parent = indexer
elementSize = 1
msbAdd = 0
} else {
elementSize = 2
msbAdd = if(msb) 1 else 0
}
// double the index because of word array (if not split), add one if msb (if not split)
val constIndexNum = (indexer.index as? PtNumber)?.number
if(constIndexNum!=null) {
indexer.children[1] = PtNumber(indexer.index.type, constIndexNum*elementSize + msbAdd, indexer.position)
indexer.children[1].parent = indexer
} else {
val multipliedIndex: PtExpression
if(elementSize==1) {
multipliedIndex = indexer.index
} else {
multipliedIndex = PtBinaryExpression("<<", indexer.index.type, indexer.position)
multipliedIndex.add(indexer.index)
multipliedIndex.add(PtNumber(DataType.UBYTE, 1.0, indexer.position))
}
if(msbAdd>0) {
val msbIndex = PtBinaryExpression("+", indexer.index.type, indexer.position)
msbIndex.add(multipliedIndex)
msbIndex.add(PtNumber(DataType.UBYTE, msbAdd.toDouble(), indexer.position))
indexer.children[1] = msbIndex
msbIndex.parent = indexer
} else {
indexer.children[1] = multipliedIndex
multipliedIndex.parent=indexer
}
}
target = AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, array = indexer)
}
else -> throw AssemblyError("setlsb/setmsb on weird target ${fcall.args[0]}")
}
if(fcall.args[1].asConstInteger() == 0) {
assignAsmGen.assignConstantByte(target, 0)
} else {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false)
}
}
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
@@ -723,11 +823,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out("""
ldy #0
lda ($varname),y
pha
tax
iny
lda ($varname),y
tay
pla""")
txa""")
}
} else fallback()
}
@@ -740,11 +840,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(result.second, RegisterOrPair.Y)
asmgen.out("""
lda ($varname),y
pha
tax
iny
lda ($varname),y
tay
pla""")
txa""")
} else fallback()
}
else -> fallback()
@@ -967,17 +1067,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya")
}
RegisterOrPair.X -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" pla")
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" pla")
}
else -> throw AssemblyError("invalid reg")
@@ -1013,35 +1113,35 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.X -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.Y -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tay | cpy #0")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" ldy #0 | cmp #0")
}
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
asmgen.out(" ldx #0 | cmp #0")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
asmgen.out(" ldy #0 | cpx #0")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args.single(), resultRegister)
asmgen.assignExpressionToRegister(arg, resultRegister)
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
zero.parent=fcall
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
@@ -255,11 +255,11 @@ $loopLabel""")
sec
sbc #<${stepsize.absoluteValue}
sta $varname
pha
tax
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
pla
txa
$modifiedLabel2 cmp #0 ; modified
lda $varname+1
$modifiedLabel sbc #0 ; modified
@@ -141,6 +141,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
is PtIdentifier -> {
val sourceName = asmgen.asmVariableName(value)
// note: cannot use X register here to store A because it might be used for other arguments
asmgen.out("""
pha
clc
@@ -428,12 +428,12 @@ internal class ProgramAndVarsGen(
asmgen.out("""
lda #<${name}_init_value
ldy #>${name}_init_value
sta cx16.r0L
sty cx16.r0H
sta cx16.r0
sty cx16.r0+1
lda #<${name}
ldy #>${name}
sta cx16.r1L
sty cx16.r1H
sta cx16.r1
sty cx16.r1+1
lda #<$size
ldy #>$size
jsr sys.memcopy""")
@@ -452,7 +452,6 @@ internal class ProgramAndVarsGen(
}
asmgen.out("""+
ldx #127 ; init estack ptr (half page)
clv
clc""")
}
@@ -1,7 +1,10 @@
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
//
@@ -180,37 +183,25 @@ internal class AnyExprAsmGen(
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.operator) {
"+" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true)
if(!expr.right.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FADDT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"-" -> {
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true)
if(!expr.left.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true)
if(!expr.left.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.right, expr.left)
asmgen.out(" jsr floats.FSUBT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"*" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
if(!expr.right.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC2, true)
if(!expr.right.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FMULTT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"/" -> {
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.FAC1, true)
if(!expr.left.isSimple()) asmgen.pushFAC1()
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.FAC2, true)
if(!expr.left.isSimple()) asmgen.popFAC1()
assignFloatOperandsToFACandARG(expr.right, expr.left)
asmgen.out(" jsr floats.FDIVT")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
@@ -255,6 +246,31 @@ internal class AnyExprAsmGen(
}
}
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()
File diff suppressed because it is too large. Load diff
@@ -57,9 +57,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
// the asm-gen code can deal with situations where you want to assign a byte into a word.
// it will create the most optimized code to do this (so it type-extends for us).
// But we can't deal with writing a word into a byte - explicit typeconversion should be done
if(program.memsizer.memorySize(value.datatype) > program.memsizer.memorySize(target.datatype)) {
TODO("missing type cast: value type > target type ${target.position}")
}
if(program.memsizer.memorySize(value.datatype) > program.memsizer.memorySize(target.datatype))
throw AssemblyError("missing type cast: value type > target type ${target.position}")
fun regName(v: AsmAssignSource) = "cx16.${v.register!!.name.lowercase()}"
@@ -84,19 +83,20 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
in WordDatatypes -> {
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype)
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(target.asmVarname, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!, block)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression)
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression, block)
}
else {
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!)
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!, block)
}
}
}
@@ -163,7 +163,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
else -> {
asmgen.assignExpressionTo(memory.address, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UWORD, memory.definingISub(), target.position, register = RegisterOrPair.AY))
asmgen.saveRegisterStack(CpuRegister.A, true)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.saveRegisterStack(CpuRegister.Y, true)
asmgen.out(" jsr prog8_lib.read_byte_from_address_in_AY_into_A")
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
@@ -231,18 +231,19 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
in WordDatatypes -> {
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype)
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt(), block)
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype, block)
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype, block)
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(targetVarName, target.datatype, operator, value.memory!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!)
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!, block)
SourceStorageKind.EXPRESSION -> {
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression)
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression, block)
} else {
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!)
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!, block)
}
}
}
@@ -328,12 +329,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda ${target.array.variable.name},y")
asmgen.out(" ldx ${target.array.variable.name}+1,y")
}
val block = target.origAstTarget?.definingBlock()
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> {
val number = value.number!!.number.toInt()
if(!inplacemodificationRegisterAXwithLiteralval(operator, number)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number)
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -344,7 +346,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype)
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -355,7 +357,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
value.datatype
)) {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype)
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -366,15 +368,15 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.ARRAY -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
SourceStorageKind.EXPRESSION -> {
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
else
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!)
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
}
}
@@ -385,8 +387,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
clc
@@ -395,9 +398,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
iny
+ sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
pha ; save array ptr lsb
tya
pha ; save array ptr msb
lda #<$tempvar
ldy #>$tempvar
jsr floats.copy_float""") // copy from array into float temp var, clobbers A,Y
jsr floats.copy_float""")
// calculate on tempvar
when(value.kind) {
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
@@ -414,18 +422,17 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
// copy tempvar back into array
asmgen.out("""
lda P8ZP_SCRATCH_W1
pha
lda P8ZP_SCRATCH_W1+1
pha
lda #<$tempvar
ldy #>$tempvar
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ply
pla
jsr floats.copy_float""") // copy from array into float temp var, clobbers A,Y
pla ; restore array ptr msb
tay
pla ; restore array ptr lsb
jsr floats.copy_float""")
}
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
}
@@ -445,11 +452,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc $variable
pha
tay
txa
adc $variable+1
tax
pla""")
tya""")
true
} else {
asmgen.out("""
@@ -469,11 +476,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
sec
sbc $variable
pha
tay
txa
sbc $variable+1
tax
pla""")
tya""")
true
} else {
asmgen.out("""
@@ -508,11 +515,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc #<$number
pha
tay
txa
adc #>$number
tax
pla""")
tya""")
true
}
}
@@ -529,11 +536,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
sec
sbc #<$number
pha
tay
txa
sbc #>$number
tax
pla""")
tya""")
true
}
}
@@ -708,10 +715,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun inplacemodificationByteVariableWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
val tmpVar = if(name!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.assignExpressionToVariable(value, tmpVar, value.type)
asmgen.out(" lda $name")
inplacemodificationRegisterAwithVariable(operator, tmpVar, dt in SignedDatatypes)
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
asmgen.out(" sta $name")
}
@@ -722,15 +727,16 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
private fun inplacemodificationRegisterAwithVariable(operator: String, variable: String, signed: Boolean) {
// A = A <operator> variable
when (operator) {
"+" -> asmgen.out(" clc | adc $variable")
"-" -> asmgen.out(" sec | sbc $variable")
"*" -> asmgen.out(" ldy $variable | jsr math.multiply_bytes")
"/" -> {
if(signed)
asmgen.out(" ldy $variable | jsr math.divmod_b_asm | tya")
asmgen.out(" ldy $variable | jsr math.divmod_b_asm | tya")
else
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm | tya")
asmgen.out(" ldy $variable | jsr math.divmod_ub_asm | tya")
}
"%" -> {
if(signed)
@@ -792,11 +798,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<" -> {
if(!signed) {
asmgen.out("""
tay
cmp $variable
bcc +
lda #0
cpy $variable
rol a
eor #1""")
beq ++
+ lda #1
+""")
}
else {
// see http://www.6502.org/tutorials/compare_beyond.html
@@ -815,10 +822,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<=" -> {
if(!signed) {
asmgen.out("""
tay
cmp $variable
bcc +
beq +
lda #0
ldy $variable
rol a""")
beq ++
+ lda #1
+""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
@@ -881,6 +891,167 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplacemodificationRegisterAwithVariableWithSwappedOperands(operator: String, variable: String, signed: Boolean) {
// A = variable <operator> A
if(operator in AssociativeOperators)
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for associative operators
// now implement the non-assiciative operators...
when (operator) {
"-" -> {
// A = variable - A
val tmpVar = if(variable!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.out(" sta $tmpVar | lda $variable | sec | sbc $tmpVar")
}
"/" -> {
if(signed)
asmgen.out(" tay | lda $variable | jsr math.divmod_b_asm | tya")
else
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm | tya")
}
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" tay | lda $variable | jsr math.divmod_ub_asm")
}
"<<" -> {
asmgen.out("""
tay
lda $variable
cpy #0
beq +
- asl a
dey
bne -
+""")
}
">>" -> {
if(!signed) {
asmgen.out("""
tay
lda $variable
cpy #0
beq +
- lsr a
dey
bne -
+""")
} else {
asmgen.out("""
tay
lda $variable
cpy #0
beq +
sta P8ZP_SCRATCH_B1
- asl a
ror P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_B1
dey
bne -
+""")
}
}
"<" -> {
// variable<A --> A>variable?
if(!signed) {
asmgen.out("""
tay
lda #0
cpy $variable
beq +
rol a
+""")
}
else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
clc
sbc $variable
bvc +
eor #$80
+ bpl +
lda #0
beq ++
+ lda #1
+""")
}
}
"<=" -> {
// variable<=A --> A>=variable?
if(!signed) {
asmgen.out("""
tay
lda #0
cpy $variable
rol a""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
sec
sbc $variable
bvc +
eor #$80
+ bpl +
lda #0
beq ++
+ lda #1
+""")
}
}
">" -> {
// variable>A --> A<variable?
if(!signed) {
asmgen.out("""
cmp $variable
bcc +
lda #0
beq ++
+ lda #1
+""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
sec
sbc $variable
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
}
}
">=" -> {
// variable>=A --> A<=variable?
if(!signed) {
asmgen.out("""
cmp $variable
bcc +
beq +
lda #0
beq ++
+ lda #1
+""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
clc
sbc $variable
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
}
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
private fun inplacemodificationByteVariableWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
when (operator) {
@@ -1080,6 +1251,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
+""")
}
"-" -> {
// name -= @(memory)
asmgen.translateDirectMemReadExpressionToRegA(memread)
val tmpByte = if(name!="P8ZP_SCRATCH_B1") "P8ZP_SCRATCH_B1" else "P8ZP_SCRATCH_REG"
asmgen.out("""
@@ -1088,7 +1260,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
sec
sbc $tmpByte
sta $name
bcc +
bcs +
dec $name+1
+""")
}
@@ -1111,12 +1283,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" eor $name | sta $name")
}
else -> {
inplacemodificationWordWithValue(name, dt, operator, memread)
inplacemodificationWordWithValue(name, dt, operator, memread, memread.definingBlock())
}
}
}
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int, block: PtBlock?) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
when (operator) {
"+" -> {
@@ -1176,16 +1348,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
sta $name
sty $name+1""")
if(block?.veraFxMuls==true)
// cx16 verafx hardware mul
asmgen.out("""
lda $name
ldy $name+1
sta cx16.r0
sty cx16.r0+1
lda #<$value
ldy #>$value
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
sta $name
sty $name+1""")
else
asmgen.out("""
lda $name
sta math.multiply_words.multiplier
lda $name+1
sta math.multiply_words.multiplier+1
lda #<$value
ldy #>$value
jsr math.multiply_words
sta $name
sty $name+1""")
}
}
"/" -> {
@@ -1575,7 +1762,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType) {
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType, block: PtBlock?) {
when (valueDt) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that
@@ -1628,23 +1815,46 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
sta $name+1""")
}
"*" -> {
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
else
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
}
asmgen.out("""
if(block?.veraFxMuls==true) {
// cx16 verafx hardware muls
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta cx16.r1")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.r1+1")
else
asmgen.out(" lda #0 | sta cx16.r1+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta cx16.r1 | sty cx16.r1+1")
}
asmgen.out("""
lda $name
ldy $name+1
jsr math.multiply_words
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls
sta $name
sty $name+1""")
} else {
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta math.multiply_words.multiplier")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz math.multiply_words.multiplier+1")
else
asmgen.out(" lda #0 | sta math.multiply_words.multiplier+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta math.multiply_words.multiplier | sty math.multiply_words.multiplier+1")
}
asmgen.out("""
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1""")
}
}
"/" -> {
if(dt==DataType.UWORD) {
@@ -1692,12 +1902,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl $name
rol $name+1
dey
bne -
ldy $otherName
beq +
- asl $name
rol $name+1
dey
bne -
+""")
}
">>" -> {
@@ -1773,16 +1983,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
"-" -> asmgen.out(" lda $name | sec | sbc $otherName | sta $name | lda $name+1 | sbc $otherName+1 | sta $name+1")
"*" -> {
asmgen.out("""
lda $otherName
ldy $otherName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1""")
if(block?.veraFxMuls==true)
// cx16 verafx hardware muls
asmgen.out("""
lda $name
ldy $name+1
sta cx16.r0
sty cx16.r0+1
lda $otherName
ldy $otherName+1
sta cx16.r1
sty cx16.r1+1
jsr verafx.muls
sta $name
sty $name+1""")
else
asmgen.out("""
lda $otherName
ldy $otherName+1
sta math.multiply_words.multiplier
sty math.multiply_words.multiplier+1
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1""")
}
"/" -> {
if(dt==DataType.WORD) {
@@ -1960,27 +2185,41 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
fun multiplyVarByWordInAY() {
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1
""")
if(block?.veraFxMuls==true)
// cx16 verafx hardware muls
asmgen.out("""
sta cx16.r1
sty cx16.r1+1
lda $name
ldy $name+1
sta cx16.r0
sty cx16.r0+1
jsr verafx.muls
sta $name
sty $name+1
""")
else
asmgen.out("""
sta math.multiply_words.multiplier
sty math.multiply_words.multiplier+1
lda $name
ldy $name+1
jsr math.multiply_words
sta $name
sty $name+1
""")
}
fun divideVarByWordInAY() {
asmgen.out("""
pha
tax
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
pla""")
txa""")
if (dt == DataType.WORD)
asmgen.out(" jsr math.divmod_w_asm")
else
@@ -1992,12 +2231,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
if(dt==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out("""
pha
tax
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
pla
txa
jsr math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
@@ -2011,20 +2250,21 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
// the other variable is a BYTE type so optimize for that
when (operator) {
"+" -> {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", valueDt)
if(valueDt==DataType.UBYTE)
// name += byteexpression
if(valueDt==DataType.UBYTE) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, false)
asmgen.out("""
lda $name
clc
adc P8ZP_SCRATCH_B1
adc $name
sta $name
bcc +
inc $name+1
+""")
else
} else {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.out("""
ldy #0
lda P8ZP_SCRATCH_B1
cmp #0
bpl +
dey ; sign extend
+ clc
@@ -2033,8 +2273,10 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
tya
adc $name+1
sta $name+1""")
}
}
"-" -> {
// name -= byteexpression
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", valueDt)
if(valueDt==DataType.UBYTE)
asmgen.out("""
+2 -2
View File
@@ -44,7 +44,7 @@ class TestCodegen: FunSpec({
//}
val codegen = AsmGen6502(prefixSymbols = false)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -94,7 +94,7 @@ class TestCodegen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
+2 -2
View File
@@ -36,10 +36,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
+3 -3
View File
@@ -30,16 +30,16 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
@@ -40,17 +40,38 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"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])
@@ -92,6 +113,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
addToResult(result, left, left.resultReg, -1)
@@ -126,6 +148,7 @@ 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()
@@ -147,6 +170,7 @@ 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()
@@ -290,6 +314,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
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()
@@ -313,6 +338,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
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()
@@ -336,6 +362,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
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])
@@ -531,6 +558,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcLsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return exprGen.translateExpression(call.args.single())
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
// TODO to be more strict, maybe we *should* introduce a new result register that is of type .b?
}
private fun funcMsb(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -557,6 +585,122 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, vmDt, -1, -1)
}
private fun funcSetLsbMsb(call: PtBuiltinFunctionCall, msb: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val target = call.args[0]
val isConstZeroValue = call.args[1].asConstInteger()==0
when(target) {
is PtIdentifier -> {
if(isConstZeroValue) {
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
if (msb)
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
}
}
}
is PtArrayIndexer -> {
require(!target.usesPointerVariable)
if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
}
}
}
}
else {
val eltSize = codeGen.program.memsizer.memorySize(target.type)
val constIndex = target.index.asConstInteger()
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.nextFree()
val offset = eltSize*constIndex + if(msb) 1 else 0
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
}
}
}
}
}
else -> throw AssemblyError("weird target for setlsb/setmsb: $target")
}
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
}
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
@@ -3,10 +3,7 @@ package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PassByValueDatatypes
import prog8.code.core.SignedDatatypes
import prog8.code.core.*
import prog8.intermediate.*
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
@@ -69,10 +66,28 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val vmDt = irType(expr.type)
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val code = IRCodeChunk(null, null)
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
ExpressionCodeResult(code, vmDt, resultRegister, -1)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol), null)
if(expr.isFromArrayElement) {
val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1)
if(expr.identifier.type in SplitWordArrayTypes) {
result += IRCodeChunk(null, null).also {
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
}
} else {
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
result += IRCodeChunk(null, null).also {
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
if(eltSize>1)
it += IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=indexTr.resultReg, immediate = eltSize)
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
}
}
}
ExpressionCodeResult(result, vmDt, resultRegister, -1)
}
is PtMemoryByte -> {
val result = mutableListOf<IRCodeChunkBase>()
@@ -107,6 +122,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
when(check.iterable.type) {
DataType.STR -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
@@ -115,6 +131,7 @@ 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)
@@ -126,6 +143,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
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)
@@ -366,6 +384,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -386,8 +405,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
}
// create the call
val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
addInstr(result, call, null)
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
@@ -397,6 +415,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is StRomSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -543,14 +562,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val resultRegister = codeGen.registers.nextFree()
val valueReg = codeGen.registers.nextFree()
val label = codeGen.createLabelName()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1), null)
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
if (notEquals) {
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
} else {
addInstr(result, IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0)
it += if (notEquals)
IRInstruction(Opcode.BSTNE, labelSymbol = label)
else
IRInstruction(Opcode.BSTEQ, labelSymbol = label)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0)
}
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
result += IRCodeChunk(label, null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
@@ -415,13 +415,19 @@ class IRCodeGen(
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = endLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
}
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = choiceLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
}
}
}
}
@@ -464,7 +470,8 @@ class IRCodeGen(
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1 = tmpReg, immediate = 0, labelSymbol = endLabel)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = tmpReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
@@ -492,7 +499,8 @@ class IRCodeGen(
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
it += IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength, labelSymbol = loopLabel)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else -> {
@@ -507,7 +515,10 @@ class IRCodeGen(
}
result += translateNode(forLoop.statements)
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes, labelSymbol = loopLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
}
}
@@ -555,8 +566,11 @@ class IRCodeGen(
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol), null)
addInstr(result, IRInstruction(Opcode.BEQR, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol = labelAfterFor), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
@@ -601,8 +615,8 @@ class IRCodeGen(
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.XOR, loopvarDtIr, reg1 = 999, immediate = 111)
chunk2 += IRInstruction(Opcode.BNE, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped, labelSymbol = loopLabel)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
result += chunk2
return result
}
@@ -961,21 +975,33 @@ class IRCodeGen(
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
)
val gotoOpcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
"<=" -> Opcode.BLES
">=" -> Opcode.BGES
else -> throw AssemblyError("weird operator")
when(condition.operator) {
// TODO: the converted list of operators
"==" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTEQ)
}
"!=" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTNE)
}
else -> {
// TODO: the old list of operators, still to be converted
val gotoOpcode = when (condition.operator) {
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
"<=" -> Opcode.BLES
">=" -> Opcode.BGES
else -> throw AssemblyError("weird operator")
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.generatedLabel)
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
}
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.generatedLabel)
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
}
return result
} else {
@@ -990,6 +1016,14 @@ class IRCodeGen(
}
}
// TODO use this everywhere
private fun branchInstr(goto: PtJump, branchOpcode: Opcode) = if (goto.address != null)
IRInstruction(branchOpcode, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(branchOpcode, labelSymbol = goto.generatedLabel)
else
IRInstruction(branchOpcode, labelSymbol = goto.identifier!!.name)
private fun ifZeroIntThenJump(
result: MutableList<IRCodeChunkBase>,
ifElse: PtIfElse,
@@ -1000,21 +1034,33 @@ class IRCodeGen(
val condition = ifElse.condition as PtBinaryExpression
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val opcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
">" -> if (signed) Opcode.BGTS else Opcode.BGT
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
">=" -> if (signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
when(condition.operator) {
// TODO: converted list of operators
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
// TODO: to-be converted operators
val opcode = when (condition.operator) {
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
">" -> if (signed) Opcode.BGTS else Opcode.BGT
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
">=" -> if (signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
}
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
}
private fun ifNonZeroIntThenJump(
@@ -1028,47 +1074,60 @@ class IRCodeGen(
if(condition==null) {
val tr = expressionEval.translateExpression(ifElse.condition)
result += tr.chunks
if (goto.address != null)
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTNE)
}
} else {
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val opcode: Opcode
val number = (condition.right as? PtNumber)?.number?.toInt()
if(number!=null) {
val firstReg = leftTr.resultReg
opcode = when (condition.operator) {
"==" -> Opcode.BEQ
"!=" -> Opcode.BNE
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
">=" -> if(signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
when(condition.operator) {
// TODO: the converted operators
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
// TODO: to-be converted operators
val opcode = when (condition.operator) {
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
">=" -> if(signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
}
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
} else {
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
val firstReg: Int
val secondReg: Int
val opcode: Opcode
var useCmp = false
when (condition.operator) {
"==" -> {
opcode = Opcode.BEQR
useCmp = true
opcode = Opcode.BSTEQ
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"!=" -> {
opcode = Opcode.BNER
useCmp = true
opcode = Opcode.BSTNE
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
@@ -1096,12 +1155,20 @@ class IRCodeGen(
}
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
if(useCmp) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, irDtLeft, reg1 = firstReg, reg2 = secondReg)
it += branchInstr(goto, opcode)
}
} else {
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
else if (goto.generatedLabel != null)
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.generatedLabel), null)
else
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
}
}
}
}
@@ -1109,6 +1176,7 @@ class IRCodeGen(
private fun translateIfElseZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
val compResultReg: Int
val branchDt: IRDataType
val condition = ifElse.condition as PtBinaryExpression
@@ -1122,13 +1190,19 @@ class IRCodeGen(
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0f)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightFpReg)
}
elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> Opcode.BGES
">" -> Opcode.BLES
"<=" -> Opcode.BGTS
">=" -> Opcode.BLTS
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = Opcode.BGES
">" -> elseBranch = Opcode.BLES
"<=" -> elseBranch = Opcode.BGTS
">=" -> elseBranch = Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
} else {
@@ -1137,13 +1211,19 @@ class IRCodeGen(
val tr = expressionEval.translateExpression(condition.left)
compResultReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if (signed) Opcode.BGES else Opcode.BGE
">" -> if (signed) Opcode.BLES else Opcode.BLE
"<=" -> if (signed) Opcode.BGTS else Opcode.BGT
">=" -> if (signed) Opcode.BLTS else Opcode.BLT
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = if (signed) Opcode.BGES else Opcode.BGE
">" -> elseBranch = if (signed) Opcode.BLES else Opcode.BLE
"<=" -> elseBranch = if (signed) Opcode.BGTS else Opcode.BGT
">=" -> elseBranch = if (signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("weird operator")
}
}
@@ -1151,14 +1231,26 @@ class IRCodeGen(
if(ifElse.elseScope.children.isEmpty()) {
// just if
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
} else {
// if and else
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1169,31 +1261,39 @@ class IRCodeGen(
private fun translateIfElseNonZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseBranchOpcode: Opcode
val elseBranchFirstReg: Int
val elseBranchSecondReg: Int
val branchDt: IRDataType
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null) {
if(irDtLeft==IRDataType.FLOAT)
throw AssemblyError("condition value should not be float")
val tr = expressionEval.translateExpression(ifElse.condition)
result += tr.chunks
if(ifElse.elseScope.children.isNotEmpty()) {
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
result += IRCodeChunk(afterIfLabel, null)
} else {
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
return result
throw AssemblyError("if-else condition is not a binaryexpression, should have been converted?")
// if(irDtLeft==IRDataType.FLOAT)
// throw AssemblyError("condition value should not be float")
// val tr = expressionEval.translateExpression(ifElse.condition)
// result += tr.chunks
// if(ifElse.elseScope.children.isNotEmpty()) {
// val elseLabel = createLabelName()
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
// TODO("test this")
// }
// result += translateNode(ifElse.ifScope)
// val afterIfLabel = createLabelName()
// addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
// result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
// result += IRCodeChunk(afterIfLabel, null)
// } else {
// val afterIfLabel = createLabelName()
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel)
// TODO("test this")
// }
// result += translateNode(ifElse.ifScope)
// result += IRCodeChunk(afterIfLabel, null)
// }
// return result
} else {
if (irDtLeft == IRDataType.FLOAT) {
val leftTr = expressionEval.translateExpression(condition.left)
@@ -1201,35 +1301,35 @@ class IRCodeGen(
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val compResultReg = registers.nextFree()
addInstr(
result,
IRInstruction(
Opcode.FCOMP,
IRDataType.FLOAT,
reg1 = compResultReg,
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
),
null
)
val elseBranch = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> Opcode.BGES
">" -> Opcode.BLES
"<=" -> Opcode.BGTS
">=" -> Opcode.BLTS
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = Opcode.BGES
">" -> elseBranch = Opcode.BLES
"<=" -> elseBranch = Opcode.BGTS
">=" -> elseBranch = Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
if (ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result,
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel),
null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1237,11 +1337,13 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result,
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel),
null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
@@ -1252,26 +1354,34 @@ class IRCodeGen(
addToResult(result, leftTr, leftTr.resultReg, -1)
val number = (condition.right as? PtNumber)?.number?.toInt()
if (number!=null) {
elseBranchOpcode = when (condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if(signed) Opcode.BGES else Opcode.BGE
">" -> if(signed) Opcode.BLES else Opcode.BLE
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = if(signed) Opcode.BGES else Opcode.BGE
">" -> elseBranch = if(signed) Opcode.BLES else Opcode.BLE
"<=" -> elseBranch = if(signed) Opcode.BGTS else Opcode.BGT
">=" -> elseBranch = if(signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("invalid comparison operator")
}
if (ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = leftTr.resultReg, immediate = number,
labelSymbol = elseLabel
), null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = elseLabel), null)
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1279,52 +1389,56 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = leftTr.resultReg, immediate = number,
labelSymbol = afterIfLabel
), null
)
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
} else {
val rightTr = expressionEval.translateExpression(condition.right)
val elseBranch: Opcode
var useCmp = false
addToResult(result, rightTr, rightTr.resultReg, -1)
when (condition.operator) {
"==" -> {
elseBranchOpcode = Opcode.BNER
useCmp = true
elseBranch = Opcode.BSTNE
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"!=" -> {
elseBranchOpcode = Opcode.BEQR
useCmp = true
elseBranch = Opcode.BSTEQ
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"<" -> {
// else part when left >= right
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">" -> {
// else part when left <= right --> right >= left
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
"<=" -> {
// else part when left > right
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">=" -> {
// else part when left < right --> right > left
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
@@ -1334,13 +1448,14 @@ class IRCodeGen(
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
labelSymbol = elseLabel
), null
)
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = elseLabel), null)
}
result += translateNode(ifElse.ifScope)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
@@ -1348,13 +1463,14 @@ class IRCodeGen(
} else {
// only if part
val afterIfLabel = createLabelName()
addInstr(
result, IRInstruction(
elseBranchOpcode, branchDt,
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
labelSymbol = afterIfLabel
), null
)
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = afterIfLabel), null)
}
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
@@ -1525,12 +1641,15 @@ class IRCodeGen(
addToResult(result, countTr, countTr.resultReg, -1)
if(constIntValue(repeat.count)==null) {
// check if the counter is already zero
addInstr(result, IRInstruction(Opcode.BEQ, irDt, reg1=countTr.resultReg, immediate = 0, labelSymbol = skipRepeatLabel), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDt, reg1=countTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel)
}
}
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg)
it += IRInstruction(Opcode.BNE, irDt, reg1 = countTr.resultReg, immediate = 0, labelSymbol = repeatLabel)
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
}
result += IRCodeChunk(skipRepeatLabel, null)
return result
@@ -25,18 +25,29 @@ class IRUnusedCodeRemover(
}
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
// 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 ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
if(ins.labelSymbol == stVar.name) {
return
return // symbol occurs in an instruction
}
}
}
irprog.st.allVariables().forEach { stVar->
val initValue = stVar.onetimeInitializationArrayValue
if(initValue!=null && !initValue.isEmpty()) {
if(initValue.any {
it.addressOfSymbol?.startsWith(blockLabel)==true
})
return // symbol occurs in an initializer value (address-of this symbol)_
}
}
}
irprog.st.removeTree(blockLabel)
}
@@ -207,9 +218,7 @@ class IRUnusedCodeRemover(
return removeUnlinkedChunks(linkedChunks)
}
private fun removeUnlinkedChunks(
linkedChunks: Set<IRCodeChunkBase>
): Int {
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
var numRemoved = 0
irprog.foreachSub { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
+14 -10
View File
@@ -41,7 +41,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
@@ -91,7 +91,7 @@ class TestVmCodeGen: FunSpec({
program.add(block)
// define the "cx16.r0" virtual register
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
program.add(cx16block)
@@ -120,7 +120,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -183,7 +183,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -242,7 +242,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -289,7 +289,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -352,7 +352,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -411,7 +411,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
val if1 = PtIfElse(Position.DUMMY)
@@ -451,7 +451,7 @@ class TestVmCodeGen: FunSpec({
//}
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(romsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
@@ -466,7 +466,11 @@ class TestVmCodeGen: FunSpec({
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
val callInstr = irChunks.single().instructions.single()
irChunks[0].instructions.size shouldBe 2
val preparecallInstr = irChunks[0].instructions[0]
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
preparecallInstr.immediate shouldBe 0
val callInstr = irChunks[0].instructions[1]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
}
+2 -2
View File
@@ -33,10 +33,10 @@ dependencies {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
}
@@ -19,12 +19,13 @@ import kotlin.math.floor
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
// @( &thing ) --> thing (but only if thing is a byte type!)
val addrOf = memread.addressExpression as? AddressOf
return if(addrOf!=null)
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
else
noModifications
if(addrOf!=null) {
if(addrOf.identifier.inferType(program).isBytes)
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
}
return noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
@@ -628,5 +629,4 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return null
}
}
}
@@ -276,107 +276,10 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
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)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
in ArrayDatatypes -> {
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
if(replacedArrayInitializer!=null)
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
}
else -> {
// nothing to do for this type
@@ -386,4 +289,109 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
// convert the initializer range expression from a range or int, to an actual array.
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return null
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
}
}
else -> return null
}
return null
}
}
@@ -30,7 +30,7 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? = if(arg is AddressOf) {
arg.identifier
if(arg.arrayIndex==null) arg.identifier else null
} else {
arg as? IdentifierReference
}
@@ -384,7 +384,8 @@ class StatementOptimizer(private val program: Program,
}
override fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> {
return if(unrollLoop.iterations<1)
val iterations = unrollLoop.iterations.constValue(program)?.number?.toInt()
return if(iterations!=null && iterations<1)
listOf(IAstModification.Remove(unrollLoop, parent as IStatementContainer))
else
noModifications
+5 -5
View File
@@ -33,14 +33,14 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation "org.antlr:antlr4-runtime:4.12.0"
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
}
configurations.all {
@@ -59,10 +59,10 @@ configurations {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
resources {
srcDirs = ["${project.projectDir}/res"]
srcDir "${project.projectDir}/res"
}
}
test {
+5 -5
View File
@@ -32,7 +32,7 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
; TODO
@@ -42,7 +42,7 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
; TODO
@@ -114,10 +114,10 @@ asmsub scroll_down (bool alsocolors @ Pc) clobbers(A) {
}
romsub $F2B0 = outchar(ubyte char @ A)
romsub $F2B0 = outchar(ubyte character @ A)
romsub $F2Fd = waitkey()
asmsub chrout(ubyte char @ A) {
asmsub chrout(ubyte character @ A) {
%asm {{
sta _tmp_outchar+1
txa
@@ -364,7 +364,7 @@ asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; TODO
%asm {{
-62
View File
@@ -1,62 +0,0 @@
%import syslib
%import textio
; Bitmap pixel graphics module for the Commodore 128
; TODO c128 actually implement the graphics routines. Ideally a way to 'borrow' the code form the C64 version without just copy-pasting that here?
graphics {
%option no_symbol_prefixing
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
}}
}
}
+2 -2
View File
@@ -87,7 +87,7 @@ romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798
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 $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
@@ -335,7 +335,7 @@ asmsub init_system() {
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
; Uppercase charset is activated.
%asm {{
sei
cld
+5 -5
View File
@@ -1,4 +1,4 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
%import syslib
%import conv
@@ -466,7 +466,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
_mod sta $ffff ; modified
rts
_screenrows .word $0400 + range(0, 1000, 40)
_screenrows .word cbm.Screen + range(0, 1000, 40)
}}
}
@@ -533,7 +533,7 @@ _mod lda $ffff ; modified
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
@@ -545,13 +545,13 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sta _colormod+2
lda setchr._screenrows,y
clc
adc column
adc col
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda char
+ lda character
_charmod sta $ffff ; modified
lda charcolor
_colormod sta $ffff ; modified
+7 -3
View File
@@ -418,8 +418,9 @@ pushFAC1 .proc
rts
.pend
popFAC1 .proc
; -- pop floating point value from cpu stack into FAC1
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
@@ -437,8 +438,11 @@ popFAC1 .proc
sta floats.floats_temp_var
lda #<floats.floats_temp_var
ldy #>floats.floats_temp_var
bcs +
jsr floats.MOVFM
; re-push return address
jmp ++
+ jsr floats.CONUPK
+ ; re-push return address
lda P8ZP_SCRATCH_W2+1
pha
lda P8ZP_SCRATCH_W2
+11 -1
View File
@@ -46,7 +46,7 @@ romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length. Also see parse_f()
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
@@ -165,6 +165,16 @@ sub rndf() -> float {
}}
}
asmsub parse_f(str value @AY) -> float @FAC1 {
%asm {{
sta $22
sty $23
jsr prog8_lib.strlen
tya
jmp FREADSTR
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
+47 -39
View File
@@ -1,35 +1,43 @@
%import textio
; bitmap pixel graphics module for the C64
; only black/white monochrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
;
; Sets character matrix and bitmap screen memory at a higher memory location $5c00-$7fff
; so that the program itself can be larger without starting to overwrite the graphics memory.
graphics {
%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 text mode, erase the text screen, color white
c64.SCROLY = %00011011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00000100 ; $1000-$2fff
txt.fill_screen(' ', 1)
; enables erase the text screen, text mode
sys.memset(CHARS_ADDRESS, 40*25, sc:' ')
c64.SCROLY = %00011011 ; disable bitmap graphics mode
c64.SCROLX = %00001000 ; 40 column mode, no scrolling
c64.VMCSB = %00010100 ; screen addresses back to defaults
c64.CIA2DDRA |= %11
c64.CIA2PRA = %11 ; back to VIC bank 0.
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
sys.memset(CHARS_ADDRESS, 40*25, pixelcolor << 4 | bgcolor)
sys.memset(cbm.Colors, 40*25, 255)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
@@ -127,46 +135,46 @@ graphics {
}
}
sub rect(uword x, ubyte y, uword width, ubyte height) {
sub rect(uword xx, ubyte yy, uword width, ubyte height) {
if width==0 or height==0
return
horizontal_line(x, y, width)
horizontal_line(xx, yy, width)
if height==1
return
horizontal_line(x, y+height-1, width)
vertical_line(x, y+1, height-2)
horizontal_line(xx, yy+height-1, width)
vertical_line(xx, yy+1, height-2)
if width==1
return
vertical_line(x+width-1, y+1, height-2)
vertical_line(xx+width-1, yy+1, height-2)
}
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
sub fillrect(uword xx, ubyte yy, uword width, ubyte height) {
if width==0
return
repeat height {
horizontal_line(x, y, width)
y++
horizontal_line(xx, yy, width)
yy++
}
}
sub horizontal_line(uword x, ubyte y, uword length) {
sub horizontal_line(uword xx, ubyte yy, uword length) {
if length<8 {
internal_plotx=x
internal_plotx=xx
repeat lsb(length) {
internal_plot(y)
internal_plot(yy)
internal_plotx++
}
return
}
ubyte separate_pixels = lsb(x) & 7
uword addr = get_y_lookup(y) + (x&$fff8)
ubyte separate_pixels = lsb(xx) & 7
uword pixaddr = get_y_lookup(yy) + (xx&$fff8)
if separate_pixels {
%asm {{
lda addr
lda pixaddr
sta P8ZP_SCRATCH_W1
lda addr+1
lda pixaddr+1
sta P8ZP_SCRATCH_W1+1
ldy separate_pixels
lda hline_filled_right,y
@@ -175,7 +183,7 @@ graphics {
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y
}}
addr += 8
pixaddr += 8
length += separate_pixels
length -= 8
}
@@ -191,9 +199,9 @@ graphics {
ror length
lsr length+1
ror length
lda addr
lda pixaddr
sta _modified+1
lda addr+1
lda pixaddr+1
sta _modified+2
lda length
ora length+1
@@ -227,11 +235,11 @@ hline_zero2
}
}
sub vertical_line(uword x, ubyte y, ubyte height) {
internal_plotx = x
sub vertical_line(uword xx, ubyte yy, ubyte height) {
internal_plotx = xx
repeat height {
internal_plot(y)
y++
internal_plot(yy)
yy++
}
}
@@ -300,8 +308,8 @@ hline_zero2
; here is the non-asm code for the plot routine below:
; sub plot_nonasm(uword px, ubyte py) {
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(addr) |= ormask[lsb(px) & 7]
; uword pixaddr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(pixaddr) |= ormask[lsb(px) & 7]
; }
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
@@ -352,7 +360,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
_plot_y_values := BITMAP_ADDRESS + 320*(range(200)>>3) + (range(200) & 7)
_y_lookup_lo .byte <_plot_y_values
_y_lookup_hi .byte >_plot_y_values
@@ -360,7 +368,7 @@ _y_lookup_hi .byte >_plot_y_values
}}
}
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
asmsub get_y_lookup(ubyte yy @Y) -> uword @AY {
%asm {{
lda internal_plot._y_lookup_lo,y
pha
+37 -2
View File
@@ -91,7 +91,7 @@ romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798
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 $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
@@ -136,6 +136,8 @@ c64 {
%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
@@ -284,6 +286,39 @@ 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
}
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
}
sub get_bitmap_ptr() -> uword {
return get_vic_memory_base() + ((c64.VMCSB & %00001000) as uword << 10)
}
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
}
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)
}
}
sys {
@@ -300,7 +335,7 @@ asmsub init_system() {
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
; Uppercase charset is activated.
%asm {{
sei
cld
+7 -7
View File
@@ -39,7 +39,7 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
@@ -54,7 +54,7 @@ asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
@@ -237,7 +237,7 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
@@ -465,7 +465,7 @@ asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)
_mod sta $ffff ; modified
rts
_screenrows .word $0400 + range(0, 1000, 40)
_screenrows .word cbm.Screen + range(0, 1000, 40)
}}
}
@@ -532,7 +532,7 @@ _mod lda $ffff ; modified
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
@@ -544,13 +544,13 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sta _colormod+2
lda setchr._screenrows,y
clc
adc column
adc col
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda char
+ lda character
_charmod sta $ffff ; modified
lda charcolor
_colormod sta $ffff ; modified
+76 -65
View File
@@ -8,22 +8,33 @@
diskio {
%option no_symbol_prefixing
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
ubyte drivenumber = 8
sub set_drive(ubyte number) {
drivenumber = number
}
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
}
sub reset_write_channel() {
cbm.CHKOUT(WRITE_IO_CHANNEL)
}
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -43,12 +54,12 @@ diskio {
ubyte high = cbm.CHRIN()
txt.print_uw(mkword(high, low))
txt.spc()
ubyte @zp char
ubyte @zp character
repeat {
char = cbm.CHRIN()
if char==0
character = cbm.CHRIN()
if character==0
break
txt.chrout(char)
txt.chrout(character)
}
txt.nl()
void cbm.CHRIN() ; skip 2 bytes
@@ -61,7 +72,7 @@ diskio {
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
@@ -76,12 +87,12 @@ io_error:
sub diskname() -> uword {
; returns disk label name or 0 if error
cbm.SETNAM(3, "$")
cbm.SETLFS(12, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -106,7 +117,7 @@ io_error:
io_error:
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
return 0
return diskio.list_filename
@@ -124,7 +135,7 @@ io_error:
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
@@ -150,7 +161,7 @@ io_error:
return files_found
}
; ----- iterative file lister functions (uses io channel 12) -----
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
@@ -161,11 +172,11 @@ io_error:
iteration_in_progress = true
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -190,7 +201,7 @@ io_error:
return false
repeat {
void cbm.CHKIN(12) ; use #12 as input channel again
reset_read_channel() ; use the input io channel again
uword nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
@@ -209,12 +220,12 @@ io_error:
; read the filename
repeat {
ubyte char = cbm.CHRIN()
if char==0
ubyte character = cbm.CHRIN()
if character==0
break
if char=='\"'
if character=='\"'
break
@(nameptr) = char
@(nameptr) = character
nameptr++
}
@@ -251,13 +262,13 @@ close_end:
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file loader functions (uses io channel 12) -----
; ----- iterative file loader functions (uses the read io channel) -----
sub f_open(uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read
@@ -265,18 +276,18 @@ close_end:
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
void cbm.OPEN() ; open 12,8,12,"filename"
if_cc {
if cbm.READST()==0 {
iteration_in_progress = true
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cc {
void cbm.CHRIN() ; read first byte to test for file not found
if not cbm.READST() {
cbm.CLOSE(12) ; close file because we already consumed first byte
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
void cbm.OPEN() ; re-open the file
void cbm.CHKIN(12)
cbm.CLRCHN() ; reset default i/o channels
return true
}
}
@@ -290,26 +301,27 @@ close_end:
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress or not num_bytes
return 0
reset_read_channel()
list_blocks = 0 ; we reuse this variable for the total number of bytes read
; commander X16 supports fast block-read via macptr() kernal call
uword size
; commander X16 supports fast block-read via MACPTR() kernal call
uword readsize
while num_bytes {
size = 255
if num_bytes<size
size = num_bytes
size = cx16.macptr(lsb(size), bufferpointer, false)
readsize = 255
if num_bytes<readsize
readsize = num_bytes
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false)
if_cs
goto byte_read_loop ; macptr block read not supported, do fallback loop
list_blocks += size
bufferpointer += size
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
list_blocks += readsize
bufferpointer += readsize
if msb(bufferpointer) == $c0
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
num_bytes -= size
num_bytes -= readsize
if cbm.READST() & $40 {
f_close() ; end of file, close it
break
@@ -317,7 +329,7 @@ close_end:
}
return list_blocks ; number of bytes read
byte_read_loop: ; fallback if macptr() isn't supported on the device
byte_read_loop: ; fallback if MACPTR isn't supported on the device
%asm {{
lda bufferpointer
sta m_in_buffer+1
@@ -348,10 +360,11 @@ m_in_buffer sta $ffff
; optimized for Commander X16 to use MACPTR block read kernal call
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress
return 0
reset_read_channel()
uword total_read = 0
while not cbm.READST() {
cx16.r0 = f_read(bufferpointer, 256)
@@ -370,8 +383,7 @@ m_in_buffer sta $ffff
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #12
jsr cbm.CHKIN ; use channel 12 again for input
jsr reset_read_channel
ldy #0
_loop jsr cbm.CHRIN
sta (P8ZP_SCRATCH_W1),y
@@ -392,25 +404,23 @@ _end rts
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file writing functions (uses io channel 13) -----
; ----- iterative file writing functions (uses write io channel) -----
sub f_open_w(uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write
f_close_w()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(13, drivenumber, 1)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
void cbm.OPEN() ; open 13,8,1,"filename"
if_cc {
cbm.CHKOUT(13) ; use #13 as output channel
if_cc
return not cbm.READST()
}
f_close_w()
return false
}
@@ -418,7 +428,7 @@ _end rts
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
if num_bytes!=0 {
cbm.CHKOUT(13) ; use #13 as output channel again
reset_write_channel()
repeat num_bytes {
cbm.CHROUT(@(bufferpointer))
bufferpointer++
@@ -431,7 +441,7 @@ _end rts
sub f_close_w() {
; -- end an iterative file writing session (close channels).
cbm.CLRCHN()
cbm.CLOSE(13)
cbm.CLOSE(WRITE_IO_CHANNEL)
}
@@ -470,25 +480,25 @@ io_error:
; saves a block of memory to disk, including the default 2 byte prg header.
sub save(uword filenameptr, uword address, uword size) -> bool {
return internal_save_routine(filenameptr, address, size, false)
sub save(uword filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, false)
}
; like save() but omits the 2 byte prg header.
sub save_raw(uword filenameptr, uword address, uword size) -> bool {
return internal_save_routine(filenameptr, address, size, true)
sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
return internal_save_routine(filenameptr, startaddress, savesize, true)
}
sub internal_save_routine(uword filenameptr, uword address, uword size, bool headerless) -> bool {
sub internal_save_routine(uword filenameptr, uword startaddress, uword savesize, bool headerless) -> bool {
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(1, drivenumber, 0)
uword @shared end_address = address + size
uword @shared end_address = startaddress + savesize
cx16.r0L = 0
%asm {{
lda address
lda startaddress
sta P8ZP_SCRATCH_W1
lda address+1
lda startaddress+1
sta P8ZP_SCRATCH_W1+1
ldx end_address
ldy end_address+1
@@ -532,8 +542,8 @@ io_error:
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details.
sub load_raw(uword filenameptr, uword address) -> uword {
return internal_load_routine(filenameptr, address, true)
sub load_raw(uword filenameptr, uword startaddress) -> uword {
return internal_load_routine(filenameptr, startaddress, true)
}
@@ -606,7 +616,7 @@ io_error:
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
}
asmsub vload(str name @R0, ubyte bank @A, uword address @R1) clobbers(X, Y) -> ubyte @A {
asmsub vload(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
; -- like the basic command VLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file has to have the usual 2 byte header (which will be skipped)
@@ -645,7 +655,7 @@ internal_vload:
}}
}
asmsub vload_raw(str name @R0, ubyte bank @A, uword address @R1) clobbers(X, Y) -> ubyte @A {
asmsub vload_raw(str name @R0, ubyte bank @A, uword startaddress @R1) clobbers(X, Y) -> ubyte @A {
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file is read fully including the first two bytes.
@@ -693,11 +703,11 @@ internal_vload:
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
@(cx16.r12)=0
cbm.SETNAM(3, "$=c")
cbm.SETLFS(12, diskio.drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$=c"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -734,7 +744,7 @@ internal_vload:
io_error:
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0
return 0
if @(cx16.r12)==0 {
@@ -767,7 +777,7 @@ io_error:
sub f_seek(uword pos_hiword, uword pos_loword) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
ubyte[6] command = ['p',0,0,0,0,0]
command[1] = 12 ; f_open uses channel 12
command[1] = READ_IO_CHANNEL ; f_open uses the read io channel
command[2] = lsb(pos_loword)
command[3] = msb(pos_loword)
command[4] = lsb(pos_hiword)
@@ -777,13 +787,14 @@ io_error:
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
cbm.CLOSE(15)
reset_read_channel() ; back to the read io channel
}
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
; diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
; diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses the write io channel
; diskio.f_seek.command[2] = lsb(pos_loword)
; diskio.f_seek.command[3] = msb(pos_loword)
; diskio.f_seek.command[4] = lsb(pos_hiword)
+60
View File
@@ -0,0 +1,60 @@
; Emulator debug interface.
; Docs: https://github.com/X16Community/x16-emulator#debug-io-registers
emudbg {
const uword EMU_BASE = $9fb0
&ubyte EMU_DBG_HOTKEY_ENABLED = EMU_BASE + 0
&ubyte EMU_LOG_VIDEO = EMU_BASE + 1
&ubyte EMU_LOG_KEYBOARD = EMU_BASE + 2
&ubyte EMU_ECHO_MODE = EMU_BASE + 3
&ubyte EMU_SAVE_ON_EXIT = EMU_BASE + 4
&ubyte EMU_RECORD_GIF = EMU_BASE + 5
&ubyte EMU_RECORD_WAV = EMU_BASE + 6
&ubyte EMU_CMDKEYS_DISABLED = EMU_BASE + 7
&ubyte EMU_CPUCLK_L = EMU_BASE + 8 ; write: reset cpu clock to 0
&ubyte EMU_CPUCLK_M = EMU_BASE + 9 ; write: outputs "User debug 1: $xx"
&ubyte EMU_CPUCLK_H = EMU_BASE + 10 ; write: outputs "User debug 2: $xx"
&ubyte EMU_CPUCLK_U = EMU_BASE + 11 ; write: outputs as character to console
; 12 is unused for now
&ubyte EMU_KEYMAP = EMU_BASE + 13
&ubyte EMU_EMU_DETECT1 = EMU_BASE + 14
&ubyte EMU_EMU_DETECT2 = EMU_BASE + 15
sub is_emulator() -> bool {
; Test for emulator presence.
; It is recommended to only use the other debug routines if this returns true.
return EMU_EMU_DETECT1=='1' and EMU_EMU_DETECT2=='6'
}
asmsub console_write(str isoString @R0) clobbers(Y) {
%asm {{
ldy #0
- lda (cx16.r0),y
beq +
sta p8_EMU_CPUCLK_U
iny
bne -
+
}}
}
asmsub console_chrout(ubyte char @A) {
%asm {{
sta p8_EMU_CPUCLK_U
}}
}
asmsub console_value1(ubyte value @A) {
%asm {{
sta p8_EMU_CPUCLK_M
}}
}
asmsub console_value2(ubyte value @A) {
%asm {{
sta p8_EMU_CPUCLK_H
}}
}
}
+14 -1
View File
@@ -28,7 +28,7 @@ romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!]
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!] see parse_f() instead
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
@@ -136,6 +136,19 @@ asmsub FREADUY (ubyte value @Y) {
}}
}
asmsub parse_f(str value @AY) -> float @FAC1 {
; -- parse a string value of a number to float in FAC1
; warning: uses an internal BASIC routine that may be rom version dependent
; ($ddf2 is inside the routine for VAL at $ddef)
%asm {{
sta $a9
sty $aa
jsr prog8_lib.strlen
tya
jmp $ddf2
}}
}
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
+122 -532
View File
@@ -4,6 +4,7 @@
;
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
; Note: for identical routines for a monochrome 1 bpp screen, use the "monogfx" module instead.
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
; Note: this library implements code for various resolutions and color depths. This takes up memory.
; If you're memory constrained you should probably not use this built-in library,
@@ -12,13 +13,11 @@
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 monochrome
; mode 2 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
; mode 1 = bitmap 320 x 240 x 256c (8 bpp)
; mode 2 = bitmap 640 x 480 x 4c (2 bpp. there's not enough vram for more colors in hires mode.)
; mode 3 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
; mode 4 = bitmap 320 x 240 x 256c (like SCREEN $80 but using this api instead of kernal)
; mode 5 = bitmap 640 x 480 monochrome
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; mode 4 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
; higher color depths in highres are not supported due to lack of VRAM
gfx2 {
@@ -29,24 +28,11 @@ gfx2 {
uword width = 0
uword height = 0
ubyte bpp = 0
bool monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
sub screen_mode(ubyte mode) {
cx16.VERA_CTRL=0
when mode {
1 -> {
; lores monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
bpp = 1
}
; TODO modes 2, 3
4 -> {
; lores 256c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
@@ -58,19 +44,7 @@ gfx2 {
height = 240
bpp = 8
}
5 -> {
; highres monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 1
}
6 -> {
2 -> {
; highres 4c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
@@ -96,149 +70,59 @@ gfx2 {
active_mode = mode
if bpp
clear_screen()
clear_screen(0)
}
sub clear_screen() {
monochrome_stipple(false)
sub clear_screen(ubyte color) {
position(0, 0)
when active_mode {
1 -> {
; lores monochrome
repeat 240/2/8
cs_innerloop640()
}
; TODO modes 2, 3
4 -> {
; lores 256c
repeat 240/2
cs_innerloop640()
cs_innerloop640(color)
}
5 -> {
; highres monochrome
repeat 480/8
cs_innerloop640()
}
6 -> {
2 -> {
; highres 4c
ubyte[] colors = [%00000000, %01010101, %10101010, %11111111]
color = colors[color&3]
repeat 480/4
cs_innerloop640()
cs_innerloop640(color)
}
; modes 7 and 8 not supported due to lack of VRAM
}
position(0, 0)
}
sub monochrome_stipple(bool enable) {
monochrome_dont_stipple_flag = not enable
}
sub rect(uword x, uword y, uword rwidth, uword rheight, ubyte color) {
sub rect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
if rwidth==0 or rheight==0
return
horizontal_line(x, y, rwidth, color)
horizontal_line(xx, yy, rwidth, color)
if rheight==1
return
horizontal_line(x, y+rheight-1, rwidth, color)
vertical_line(x, y+1, rheight-2, color)
horizontal_line(xx, yy+rheight-1, rwidth, color)
vertical_line(xx, yy+1, rheight-2, color)
if rwidth==1
return
vertical_line(x+rwidth-1, y+1, rheight-2, color)
vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
}
sub fillrect(uword x, uword y, uword rwidth, uword rheight, ubyte color) {
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
if rwidth==0
return
repeat rheight {
horizontal_line(x, y, rwidth, color)
y++
horizontal_line(xx, yy, rwidth, color)
yy++
}
}
sub horizontal_line(uword x, uword y, uword length, ubyte color) {
ubyte[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111]
ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111]
sub horizontal_line(uword xx, uword yy, uword length, ubyte color) {
if length==0
return
when active_mode {
1, 5 -> {
; monochrome modes, either resolution
ubyte separate_pixels = (8-lsb(x)) & 7
if separate_pixels as uword > length
separate_pixels = lsb(length)
if separate_pixels {
if monochrome_dont_stipple_flag {
position(x,y)
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if color
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
x += separate_pixels
} else {
repeat separate_pixels {
plot(x, y, color)
x++
}
}
length -= separate_pixels
}
if length {
position(x, y)
separate_pixels = lsb(length) & 7
x += length & $fff8
%asm {{
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda color
bne +
ldy #0 ; black
bra _loop
+ lda monochrome_dont_stipple_flag
beq _stipple
ldy #255 ; don't stipple
bra _loop
_stipple lda y
and #1 ; determine stipple pattern to use
bne +
ldy #%01010101
bra _loop
+ ldy #%10101010
_loop lda length
ora length+1
beq _done
sty cx16.VERA_DATA0
lda length
bne +
dec length+1
+ dec length
bra _loop
_done
}}
if monochrome_dont_stipple_flag {
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if color
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
} else {
repeat separate_pixels {
plot(x, y, color)
x++
}
}
}
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
4 -> {
1 -> {
; lores 256c
position(x, y)
position(xx, yy)
%asm {{
lda color
ldx length+1
@@ -257,8 +141,8 @@ _done
+
}}
}
6 -> {
; highres 4c ....also mostly usable for mode 2, lores 4c?
2 -> {
; highres 4c ....also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
@@ -266,7 +150,7 @@ _done
colorbits[ii] = color
color <<= 2
}
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
lda cx16.VERA_ADDR_H
and #%00000111 ; no auto advance
@@ -279,7 +163,7 @@ _done
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
ldx x
ldx xx
}}
repeat length {
@@ -305,65 +189,12 @@ _done
}
}
sub vertical_line(uword x, uword y, uword lheight, ubyte color) {
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
when active_mode {
1, 5 -> {
; monochrome, lo-res
cx16.r15L = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if color {
if monochrome_dont_stipple_flag {
; draw continuous line.
position2(x,y,true)
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
} else {
; draw stippled line.
if x&1 {
y++
lheight--
}
position2(x,y,true)
if active_mode==1
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
else
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
repeat lheight/2 {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
} else {
position2(x,y,true)
cx16.r15 = ~cx16.r15 ; erase pixels
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
}
4 -> {
1 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
position(x,y)
position(xx,yy)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy lheight
@@ -375,22 +206,22 @@ _done
+
}}
}
6 -> {
2 -> {
; highres 4c
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
if lheight==0
return
position2(x,y,true)
position2(xx,yy,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
;; color &= 3
;; color <<= gfx2.plot.shift4c[lsb(x) & 3]
cx16.r2L = lsb(x) & 3
;; color <<= gfx2.plot.shift4c[lsb(xx) & 3]
cx16.r2L = lsb(xx) & 3
when color & 3 {
1 -> color = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
2 -> color = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
3 -> color = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
}
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
ubyte @shared mask = gfx2.plot.mask4c[lsb(xx) & 3]
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
@@ -569,8 +400,7 @@ _done
}
}
sub plot(uword @zp x, uword @zp y, ubyte @zp color) {
ubyte[8] @shared bits = [128, 64, 32, 16, 8, 4, 2, 1]
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
ubyte[4] @shared mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] @shared shift4c = [6,4,2,0]
ubyte[4] shiftedleft_4c_1 = [1<<6, 1<<4, 1<<2, 1<<0]
@@ -579,33 +409,8 @@ _done
when active_mode {
1 -> {
; lores monochrome
if color {
; solid color or perhaps stipple
%asm {{
lda x
eor y
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
mode_1_prepare()
%asm {{
tsb cx16.VERA_DATA0
}}
}
} else {
; only erase
mode_1_prepare()
%asm {{
trb cx16.VERA_DATA0
}}
}
}
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
@@ -619,34 +424,10 @@ _done
sta cx16.VERA_DATA0
}}
}
5 -> {
; highres monochrome
if color {
; solid color or perhaps stipple
%asm {{
lda x
eor y
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
mode_5_prepare()
%asm {{
tsb cx16.VERA_DATA0
}}
}
} else {
; only erase
mode_5_prepare()
%asm {{
trb cx16.VERA_DATA0
}}
}
}
6 -> {
; highres 4c ....also mostly usable for mode 2, lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(x) & 3 ; xbits
2 -> {
; highres 4c ....also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(xx) & 3 ; xbits
; color &= 3
; color <<= shift4c[cx16.r2L]
when color & 3 {
@@ -670,78 +451,13 @@ _done
}}
}
}
sub mode_1_prepare() {
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda bits,y
}}
}
sub mode_5_prepare() {
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda bits,y
}}
}
}
sub pget(uword @zp x, uword y) -> ubyte {
sub pget(uword @zp xx, uword yy) -> ubyte {
when active_mode {
1 -> {
; lores monochrome
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.bits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
@@ -753,33 +469,9 @@ _done
lda cx16.VERA_DATA0
}}
}
5 -> {
; hires monochrome
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.bits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
6 -> {
2 -> {
; hires 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
@@ -789,17 +481,26 @@ _done
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
sta cx16.r0L
pha
lda xx
and #3
tay
lda gfx2.plot.shift4c,y
tay
pla
cpy #0
beq +
- lsr a
dey
bne -
+ and #3
}}
cx16.r1L = lsb(x) & 3
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L]
return cx16.r0L & 3
}
else -> return 0
}
}
sub fill(word @zp x, word @zp y, ubyte new_color) {
sub fill(word @zp xx, word @zp yy, ubyte new_color) {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
@@ -862,85 +563,76 @@ _done
lda stack_xr_msb,y
sta x2+1
lda stack_y_lsb,y
sta y
sta yy
lda stack_y_msb,y
sta y+1
sta yy+1
ldy cx16.r12L
lda stack_dy,y
sta dy
}}
y+=dy
yy+=dy
}
cx16.r11L = pget(x as uword, y as uword) ; old_color
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
if cx16.r11L == cx16.r10L
return
if x<0 or x > width-1 or y<0 or y > height-1
if xx<0 or xx>width-1 or yy<0 or yy>height-1
return
push_stack(x, x, y, 1)
push_stack(x, x, y + 1, -1)
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L {
pop_stack()
x = x1
while x >= 0 and pget(x as uword, y as uword) == cx16.r11L {
plot(x as uword, y as uword, cx16.r10L)
x--
}
if x>= x1
xx = x1
; TODO: if mode==1 (256c) use vera autodecrement instead of pget(), but code bloat not worth it?
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L
xx--
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
else
goto skip
left = x + 1
left = xx + 1
if left < x1
push_stack(left, x1 - 1, y, -dy)
x = x1 + 1
push_stack(left, x1 - 1, yy, -dy)
xx = x1 + 1
do {
while x <= width-1 and pget(x as uword, y as uword) == cx16.r11L {
plot(x as uword, y as uword, cx16.r10L)
x++
}
push_stack(left, x - 1, y, dy)
if x > x2 + 1
push_stack(x2 + 1, x - 1, y, -dy)
cx16.r9 = xx
; TODO: if mode==1 (256c) use vera autoincrement instead of pget(), but code bloat not worth it?
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L
xx++
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
skip:
x++
while x <= x2 and pget(x as uword, y as uword) != cx16.r11L
x++
left = x
} until x>x2
xx++
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
xx++
left = xx
} until xx>x2
}
}
sub position(uword @zp x, uword y) {
sub position(uword @zp xx, uword yy) {
when active_mode {
1 -> {
; lores monochrome
cx16.r0 = y*(320/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
; TODO modes 2, 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
}
5 -> {
; highres monochrome
cx16.r0 = y*(640/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
6 -> {
2 -> {
; highres 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
}
}
cx16.r2L = cx16.r1L
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
}
sub position2(uword @zp x, uword y, bool also_port_1) {
position(x, y)
sub position2(uword @zp xx, uword yy, bool also_port_1) {
position(xx, yy)
if also_port_1
cx16.vaddr_clone(0)
}
@@ -948,7 +640,6 @@ skip:
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
%asm {{
sta cx16.VERA_DATA0
@@ -958,7 +649,6 @@ skip:
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, X, Y) {
; -- sets the next bunch of pixels from a prepared array of bytes.
; for 8 bpp screens this will plot 1 pixel per byte.
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
%asm {{
sta P8ZP_SCRATCH_W1
@@ -1011,7 +701,7 @@ skip:
cx16.screen_set_charset(charset, 0)
}
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
sub text(uword @zp xx, uword yy, ubyte color, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
uword chardataptr
@@ -1019,99 +709,14 @@ skip:
ubyte[8] @shared char_bitmap_bytes_right
when active_mode {
1, 5 -> {
; monochrome mode, either resolution
cx16.r3 = sctextptr
while @(cx16.r3) {
chardataptr = charset_addr + @(cx16.r3) * $0008
; copy the character bitmap into RAM
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
%asm {{
; pre-shift the bits
lda text.x
and #7
sta P8ZP_SCRATCH_B1
ldy #0
- lda cx16.VERA_DATA0
stz P8ZP_SCRATCH_REG
ldx P8ZP_SCRATCH_B1
cpx #0
beq +
- lsr a
ror P8ZP_SCRATCH_REG
dex
bne -
+ sta char_bitmap_bytes_left,y
lda P8ZP_SCRATCH_REG
sta char_bitmap_bytes_right,y
iny
cpy #8
bne --
}}
; left part of shifted char
position2(x, y, true)
set_autoincrs_mode1_or_5()
if color {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
; right part of shifted char
if lsb(x) & 7 {
position2(x+8, y, true)
set_autoincrs_mode1_or_5()
if color {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
}
cx16.r3++
x += 8
}
}
4 -> {
1 -> {
; lores 256c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
position(x,y)
y++
position(xx,yy)
yy++
%asm {{
ldx color
lda cx16.VERA_DATA1
@@ -1126,23 +731,23 @@ skip:
bne -
}}
}
x+=8
y-=8
xx+=8
yy-=8
sctextptr++
}
}
6 -> {
2 -> {
; hires 4c
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
cx16.r11L = color
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, true) ; for reading the chardata from Vera data channel 1
position(x, y) ; only calculated once, we update vera address in the loop instead
position(xx, yy) ; only calculated once, we update vera address in the loop instead
cx16.VERA_ADDR_H &= $0f ; no auto increment
repeat 8 {
cx16.r10L = cx16.VERA_DATA1 ; get the next 8 horizontal character bits
cx16.r7 = x
cx16.r7 = xx
repeat 8 {
cx16.r10L <<= 1
if_cs {
@@ -1189,39 +794,24 @@ skip:
sta cx16.VERA_ADDR_H
}}
}
x+=8
xx+=8
sctextptr++
}
}
}
sub set_autoincrs_mode1_or_5() {
; set autoincrements to go to next pixel row (40 or 80 increment)
if active_mode==1 {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
} else {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
}
}
}
asmsub cs_innerloop640() clobbers(Y) {
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
%asm {{
ldy #80
- stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dey
bne -
rts
+10 -10
View File
@@ -5,7 +5,7 @@
; Wraps the graphics functions that are in ROM.
; Only lo-res 320x240 256 color mode for now.
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
; For other resolutions or other color modes, use the "gfx2" module instead. (which is Cx16-specific)
; For other resolutions or other color modes, use the "gfx2" or "monogfx" module instead. (which is Cx16-specific)
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
@@ -29,7 +29,7 @@ graphics {
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
void cx16.screen_mode(0, false)
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
txt.clear_screen()
}
@@ -50,22 +50,22 @@ graphics {
cx16.GRAPH_draw_line(x1, y1, x2, y2)
}
sub fillrect(uword x, uword y, uword width, uword height) {
cx16.GRAPH_draw_rect(x, y, width, height, 0, 1)
sub fillrect(uword xx, uword yy, uword width, uword height) {
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, 1)
}
sub rect(uword x, uword y, uword width, uword height) {
cx16.GRAPH_draw_rect(x, y, width, height, 0, 0)
sub rect(uword xx, uword yy, uword width, uword height) {
cx16.GRAPH_draw_rect(xx, yy, width, height, 0, 0)
}
sub horizontal_line(uword x, uword y, uword length) {
sub horizontal_line(uword xx, uword yy, uword length) {
if length
cx16.GRAPH_draw_line(x, y, x+length-1, y)
cx16.GRAPH_draw_line(xx, yy, xx+length-1, yy)
}
sub vertical_line(uword x, uword y, uword height) {
sub vertical_line(uword xx, uword yy, uword height) {
if height
cx16.GRAPH_draw_line(x, y, x, y+height-1)
cx16.GRAPH_draw_line(xx, yy, xx, yy+height-1)
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
+791
View File
@@ -0,0 +1,791 @@
; Monochrome Bitmap pixel graphics routines for the CommanderX16
; Using the full-screen 640x480 and 320x240 screen modes, in 1 bpp mode (black/white).
;
; No text layer is currently shown, but text can be drawn as part of the bitmap itself.
; For color bitmap graphics, see the gfx2 library.
;
; NOTE: a lot of the code here is similar or the same to that in gfx2
monogfx {
%option no_symbol_prefixing
; read-only control variables:
uword width = 0
uword height = 0
bool dont_stipple_flag = true ; set to false to enable stippling mode
sub lores() {
; enable 320*240 bitmap mode
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
clear_screen(0)
}
sub hires() {
; enable 640*480 bitmap mode
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
clear_screen(0)
}
sub textmode() {
; back to normal text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
cbm.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
}
sub clear_screen(ubyte color) {
stipple(false)
position(0, 0)
when width {
320 -> {
repeat 240/2/8
cs_innerloop640(color)
}
640 -> {
repeat 480/8
cs_innerloop640(color)
}
}
position(0, 0)
}
sub stipple(bool enable) {
dont_stipple_flag = not enable
}
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
if rwidth==0 or rheight==0
return
horizontal_line(xx, yy, rwidth, draw)
if rheight==1
return
horizontal_line(xx, yy+rheight-1, rwidth, draw)
vertical_line(xx, yy+1, rheight-2, draw)
if rwidth==1
return
vertical_line(xx+rwidth-1, yy+1, rheight-2, draw)
}
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
; Draw a filled rectangle of the given size and color.
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
if rwidth==0
return
repeat rheight {
horizontal_line(xx, yy, rwidth, draw)
yy++
}
}
sub horizontal_line(uword xx, uword yy, uword length, bool draw) {
ubyte[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111]
ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111]
if length==0
return
if length<=8 {
; just use 2 byte writes with shifted mask
position2(xx,yy,true)
%asm {{
ldy length
lda masked_ends,y
sta cx16.r0L ; save left byte
stz P8ZP_SCRATCH_B1
lda xx
and #7
beq +
tay
lda cx16.r0L
- lsr a
ror P8ZP_SCRATCH_B1
dey
bne -
sta cx16.r0L ; new left byte
+
lda dont_stipple_flag
bne _dontstipple
; determine stipple pattern
lda yy
and #1
beq +
lda #%10101010
bne ++
+ lda #%01010101
+ sta P8ZP_SCRATCH_REG
lda cx16.r0L
and P8ZP_SCRATCH_REG
sta cx16.r0L
lda P8ZP_SCRATCH_B1
and P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
_dontstipple
lda draw
beq _clear
lda cx16.r0L ; left byte
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
lda P8ZP_SCRATCH_B1 ; right byte
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
rts
_clear
lda cx16.r0L ; left byte
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
lda P8ZP_SCRATCH_B1 ; right byte
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
rts
}}
}
ubyte separate_pixels = (8-lsb(xx)) & 7
if separate_pixels {
if dont_stipple_flag {
position(xx,yy)
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
xx += separate_pixels
} else {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
}
}
length -= separate_pixels
}
if length {
position(xx, yy)
separate_pixels = lsb(length) & 7
xx += length & $fff8
%asm {{
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda draw
bne +
ldy #0 ; black
bra _loop
+ lda dont_stipple_flag
beq _stipple
ldy #255 ; don't stipple
bra _loop
_stipple lda yy
and #1 ; determine stipple pattern to use
bne +
ldy #%01010101
bra _loop
+ ldy #%10101010
_loop lda length
ora length+1
beq _done
sty cx16.VERA_DATA0
lda length
bne +
dec length+1
+ dec length
bra _loop
_done
}}
if dont_stipple_flag {
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
if draw
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
else
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
} else {
repeat separate_pixels {
plot(xx, yy, draw)
xx++
}
}
}
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
cx16.r15L = monogfx.plot.maskbits[xx as ubyte & 7] ; bitmask
if draw {
if dont_stipple_flag {
; draw continuous line.
position2(xx,yy,true)
if width==320
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
} else {
; draw stippled line.
if (xx ^ yy)&1==0 {
yy++
lheight--
}
lheight++ ; because it is divided by 2 later, don't round off the last pixel
position2(xx,yy,true)
if width==320
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
else
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
repeat lheight/2 {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
} else {
position2(xx,yy,true)
cx16.r15 = ~cx16.r15 ; erase pixels
if width==320
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat lheight {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
sub set_both_strides(ubyte stride) {
stride <<= 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
}
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, bool draw) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
cx16.r0 = x1
x1 = x2
x2 = cx16.r0
cx16.r0 = y1
y1 = y2
y2 = cx16.r0
}
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy) as uword +1, draw)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx) as uword +1, draw)
return
}
word @zp d = 0
cx16.r1L = true ; 'positive_ix'
if dx < 0 {
dx = -dx
cx16.r1L = false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if cx16.r1L {
repeat {
plot(cx16.r14, y1, draw)
if cx16.r14==x2
return
cx16.r14++
d += dy2
if d > dx {
y1++
d -= dx2
}
}
} else {
repeat {
plot(cx16.r14, y1, draw)
if cx16.r14==x2
return
cx16.r14--
d += dy2
if d > dx {
y1++
d -= dx2
}
}
}
}
else {
if cx16.r1L {
repeat {
plot(cx16.r14, y1, draw)
if y1 == y2
return
y1++
d += dx2
if d > dy {
cx16.r14++
d -= dy2
}
}
} else {
repeat {
plot(cx16.r14, y1, draw)
if y1 == y2
return
y1++
d += dx2
if d > dy {
cx16.r14--
d -= dy2
}
}
}
}
}
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plot(cx16.r14, cx16.r15, draw)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, draw)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += (yy as word -xx)*2+1
}
}
}
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
ubyte[8] @shared maskbits = [128, 64, 32, 16, 8, 4, 2, 1]
if draw {
; solid color or perhaps stipple
%asm {{
lda xx
eor yy
ora dont_stipple_flag
and #1
}}
if_nz {
prepare()
%asm {{
tsb cx16.VERA_DATA0
}}
}
} else {
; only erase
prepare()
%asm {{
trb cx16.VERA_DATA0
}}
}
sub prepare() {
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
if width==320
xx += yy*(320/8)
else
xx += yy*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda maskbits,y
}}
}
}
sub pget(uword @zp xx, uword yy) -> ubyte {
%asm {{
lda xx
and #7
pha ; xbits
}}
xx /= 8
if width==320
xx += yy*(320/8)
else
xx += yy*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda xx+1
sta cx16.VERA_ADDR_M
lda xx
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.maskbits,y
and cx16.VERA_DATA0
beq +
lda #1
+ rts
}}
}
sub fill(word @zp xx, word @zp yy, bool draw) {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 48
word[MAXDEPTH] @split @shared stack_xl
word[MAXDEPTH] @split @shared stack_xr
word[MAXDEPTH] @split @shared stack_y
byte[MAXDEPTH] @shared stack_dy
cx16.r12L = 0 ; stack pointer
word x1
word x2
byte dy
cx16.r10L = draw as ubyte
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
if cx16.r12L==MAXDEPTH
return
cx16.r0s = sy+sdy
if cx16.r0s>=0 and cx16.r0s<=height-1 {
;; stack_xl[cx16.r12L] = sxl
;; stack_xr[cx16.r12L] = sxr
;; stack_y[cx16.r12L] = sy
;; stack_dy[cx16.r12L] = sdy
;; cx16.r12L++
%asm {{
ldy cx16.r12L
lda sxl
sta stack_xl_lsb,y
lda sxl+1
sta stack_xl_msb,y
lda sxr
sta stack_xr_lsb,y
lda sxr+1
sta stack_xr_msb,y
lda sy
sta stack_y_lsb,y
lda sy+1
sta stack_y_msb,y
ldy cx16.r12L
lda sdy
sta stack_dy,y
inc cx16.r12L
}}
}
}
sub pop_stack() {
;; cx16.r12L--
;; x1 = stack_xl[cx16.r12L]
;; x2 = stack_xr[cx16.r12L]
;; y = stack_y[cx16.r12L]
;; dy = stack_dy[cx16.r12L]
%asm {{
dec cx16.r12L
ldy cx16.r12L
lda stack_xl_lsb,y
sta x1
lda stack_xl_msb,y
sta x1+1
lda stack_xr_lsb,y
sta x2
lda stack_xr_msb,y
sta x2+1
lda stack_y_lsb,y
sta yy
lda stack_y_msb,y
sta yy+1
ldy cx16.r12L
lda stack_dy,y
sta dy
}}
yy+=dy
}
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
if cx16.r11L == cx16.r10L
return
if xx<0 or xx>width-1 or yy<0 or yy>height-1
return
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L {
pop_stack()
xx = x1
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L
xx--
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
else
goto skip
left = xx + 1
if left < x1
push_stack(left, x1 - 1, yy, -dy)
xx = x1 + 1
do {
cx16.r9 = xx
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L
xx++
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
skip:
xx++
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
xx++
left = xx
} until xx>x2
}
}
sub position(uword @zp xx, uword yy) {
if width==320
cx16.r0 = yy*(320/8)
else
cx16.r0 = yy*(640/8)
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
}
sub position2(uword @zp xx, uword yy, bool also_port_1) {
position(xx, yy)
if also_port_1
cx16.vaddr_clone(0)
}
const ubyte charset_bank = $1
const uword charset_addr = $f000 ; in bank 1, so $1f000
sub text_charset(ubyte charset) {
; -- select the text charset to use with the text() routine
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
cx16.screen_set_charset(charset, 0)
}
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
uword chardataptr
ubyte[8] @shared char_bitmap_bytes_left
ubyte[8] @shared char_bitmap_bytes_right
cx16.r3 = sctextptr
while @(cx16.r3) {
chardataptr = charset_addr + @(cx16.r3) * $0008
; copy the character bitmap into RAM
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
%asm {{
; pre-shift the bits
lda text.xx
and #7
sta P8ZP_SCRATCH_B1
ldy #0
- lda cx16.VERA_DATA0
stz P8ZP_SCRATCH_REG
ldx P8ZP_SCRATCH_B1
cpx #0
beq +
- lsr a
ror P8ZP_SCRATCH_REG
dex
bne -
+ sta char_bitmap_bytes_left,y
lda P8ZP_SCRATCH_REG
sta char_bitmap_bytes_right,y
iny
cpy #8
bne --
}}
; left part of shifted char
position2(xx, yy, true)
set_autoincrs()
if draw {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_left,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
; right part of shifted char
if lsb(xx) & 7 {
position2(xx+8, yy, true)
set_autoincrs()
if draw {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
ora cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
} else {
%asm {{
ldy #0
- lda char_bitmap_bytes_right,y
eor #255
and cx16.VERA_DATA1
sta cx16.VERA_DATA0
iny
cpy #8
bne -
}}
}
}
cx16.r3++
xx += 8
}
sub set_autoincrs() {
; set autoincrements to go to next pixel row (40 or 80 increment)
if width==320 {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
} else {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
}
}
}
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
%asm {{
cmp #0
beq +
lda #255
+ ldy #80
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dey
bne -
rts
}}
}
}
+5 -5
View File
@@ -16,11 +16,11 @@ psg {
const ubyte LEFT = %01000000
const ubyte RIGHT = %10000000
sub voice(ubyte voice_num, ubyte channel, ubyte volume, ubyte waveform, ubyte pulsewidth) {
sub voice(ubyte voice_num, ubyte channel, ubyte vol, ubyte waveform, ubyte pulsewidth) {
; -- Enables a 'voice' on the PSG.
; voice_num = 0-15, the voice number.
; channel = either LEFT or RIGHT or (LEFT|RIGHT). Specifies the stereo channel(s) to use.
; volume = 0-63, the starting volume for the voice
; vol = 0-63, the starting volume for the voice
; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE.
; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE.
envelope_states[voice_num] = 255
@@ -30,11 +30,11 @@ psg {
cx16.VERA_ADDR_L = lsb(cx16.r0)
cx16.VERA_ADDR_M = msb(cx16.r0)
cx16.VERA_ADDR_H = 1
cx16.VERA_DATA0 = channel | volume
cx16.VERA_DATA0 = channel | vol
cx16.VERA_ADDR_L++
cx16.VERA_DATA0 = waveform | pulsewidth
envelope_volumes[voice_num] = mkword(volume, 0)
envelope_maxvolumes[voice_num] = volume
envelope_volumes[voice_num] = mkword(vol, 0)
envelope_maxvolumes[voice_num] = vol
sys.irqsafe_clear_irqd()
}
+110
View File
@@ -0,0 +1,110 @@
; Simple routines to control sprites.
; They're not written for high performance, but for simplicity.
; That's why they control 1 sprite at a time. The exception is pos_batch().
; which is quite efficient to update sprite positions of multiple sprites in one go.
; note: sprites z-order will be in front of all layers.
; note: collision mask is not supported here yet.
sprites {
const ubyte SIZE_8 = 0
const ubyte SIZE_16 = 1
const ubyte SIZE_32 = 2
const ubyte SIZE_64 = 3
const ubyte COLORS_16 = 0
const ubyte COLORS_256 = 128
const uword VERA_SPRITEREGS = $fc00 ; $1fc00
uword @zp sprite_reg
sub init(ubyte spritenum,
ubyte databank, uword dataaddr,
ubyte width_flag, ubyte height_flag,
ubyte colors_flag, ubyte palette_offset_idx) {
hide(spritenum)
cx16.VERA_DC_VIDEO |= %01000000 ; enable sprites globally
dataaddr >>= 5
dataaddr |= (databank as uword)<<11
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(dataaddr)) ; address 12:5
cx16.vpoke(1, sprite_reg+1, colors_flag | msb(dataaddr)) ; 4 bpp + address 16:13
cx16.vpoke(1, sprite_reg+6, %00001100) ; z depth %11 = in front of both layers, no flips
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset_idx>>4) ; 64x64 pixels, palette offset
}
sub data(ubyte spritenum, ubyte bank, uword addr) {
addr >>= 5
addr |= (bank as uword)<<11
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(addr)) ; address 12:5
cx16.vpoke_mask(1, sprite_reg+1, %11110000, msb(addr)) ; address 16:13
}
sub pos(ubyte spritenum, word xpos, word ypos) {
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(xpos))
cx16.vpoke(1, sprite_reg+1, msb(xpos))
cx16.vpoke(1, sprite_reg+2, lsb(ypos))
cx16.vpoke(1, sprite_reg+3, msb(ypos))
}
sub pos_batch(ubyte first_spritenum, ubyte num_sprites, uword xpositions_ptr, uword ypositions_ptr) {
; -- note: the x and y positions word arrays must be regular arrays, they cannot be @split arrays!
sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(xpositions_ptr)
xpositions_ptr ++
cx16.VERA_DATA1 = @(xpositions_ptr)
xpositions_ptr ++
}
sprite_reg += 2
cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
repeat num_sprites {
cx16.VERA_DATA0 = @(ypositions_ptr)
ypositions_ptr ++
cx16.VERA_DATA1 = @(ypositions_ptr)
ypositions_ptr ++
}
}
sub setx(ubyte spritenum, word xpos) {
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(xpos))
cx16.vpoke(1, sprite_reg+1, msb(xpos))
}
sub sety(ubyte spritenum, word ypos) {
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
cx16.vpoke(1, sprite_reg, lsb(ypos))
cx16.vpoke(1, sprite_reg+1, msb(ypos))
}
sub getx(ubyte spritenum) -> word {
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
}
sub gety(ubyte spritenum) -> word {
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
}
sub hide(ubyte spritenum) {
pos(spritenum, -64, -64)
}
sub flipx(ubyte spritenum, bool flipped) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111110, flipped)
}
sub flipy(ubyte spritenum, bool flipped) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111101, flipped<<1)
}
sub set_palette_offset(ubyte spritenum, ubyte offset) {
sprite_reg = VERA_SPRITEREGS + 7 + spritenum*$0008
cx16.vpoke_mask(1, sprite_reg, %11110000, offset>>4)
}
}
+27 -14
View File
@@ -38,7 +38,7 @@ romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798
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 $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
@@ -267,6 +267,17 @@ cx16 {
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; experimental Vera FX registers: (depends on particular value set in VERA_CTRL!!!)
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
&ubyte VERA_FX_MULT = VERA_BASE + $000C
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000A
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000B
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000C
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009 ; (DCSEL=6)
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
@@ -320,17 +331,16 @@ cx16 {
; spelling of the names is taken from the Commander X-16 rom sources
; supported C128 additions
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) clobbers(X, Y) -> ubyte @A, bool @Pc ; note: X,Y size result is not supported, use SCREEN or get_screen_mode routine for that
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
romsub $ff6e = jsrfar() ; following word = address to call, byte after that=rom/ram bank it is in
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7d = primm()
romsub $ff7d = PRIMM()
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
@@ -346,8 +356,8 @@ romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword
romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
romsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, bool is_control @Pc) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte char @A) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_next_char(ubyte char @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte character @A) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_next_char(ubyte character @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
; framebuffer
romsub $fef6 = FB_init() clobbers(A,X,Y)
@@ -377,14 +387,15 @@ romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @
romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
romsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
romsub $fede = console_put_char(ubyte char @A, bool wrapping @Pc) clobbers(A,X,Y)
romsub $fede = console_put_char(ubyte character @A, bool wrapping @Pc) clobbers(A,X,Y)
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
@@ -395,6 +406,7 @@ romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
romsub $ff68 = mouse_config(ubyte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
@@ -403,10 +415,10 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
; Audio (rom bank 10)
romsub $C04B = psg_init() clobbers(A,X,Y)
romsub $C04B = psg_init() clobbers(A,X,Y) ; (re)init Vera PSG
romsub $C063 = ym_init() clobbers(A,X,Y) -> bool @Pc ; (re)init YM chip
romsub $C066 = ym_loaddefpatches() clobbers(A,X,Y) -> bool @Pc ; load default YM patches
romsub $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)initialize PSG and YM audio chips
romsub $C09F = audio_init() clobbers(A,X,Y) -> bool @Pc ; (re)initialize both vera PSG and YM audio chips
; TODO: add more of the audio routines?
@@ -515,7 +527,8 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
; note: inefficient when reading multiple sequential bytes!
%asm {{
pha
lda #1
lda cx16.VERA_CTRL
ora #1
sta cx16.VERA_CTRL
pla
and #1
+9 -9
View File
@@ -42,7 +42,7 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A, X) {
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A, X) {
; ---- fill the character screen with the given fill character and character color.
%asm {{
sty _ly+1
@@ -88,7 +88,7 @@ set_vera_textmatrix_addresses:
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(X, Y) {
asmsub clear_screenchars (ubyte character @ A) clobbers(X, Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
@@ -384,7 +384,7 @@ _nextline
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
@@ -674,12 +674,12 @@ asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; note: color handling is the same as on the C64: it only sets the foreground color and leaves the background color as is.
; Use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors (is faster as well).
%asm {{
lda column
lda col
asl a
tax
ldy row
@@ -691,7 +691,7 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
;clc
adc #>VERA_TEXTMATRIX_ADDR
sta cx16.VERA_ADDR_M
lda char
lda character
sta cx16.VERA_DATA0
inc cx16.VERA_ADDR_L
lda charcolor
@@ -705,12 +705,12 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
}}
}
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
sub setcc2 (ubyte col, ubyte row, ubyte character, ubyte colors) {
; ---- set char+color at the given position on the screen
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color! Is a bit faster than setcc() too.
%asm {{
lda column
lda col
asl a
tax
ldy row
@@ -722,7 +722,7 @@ sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
; clc
adc #>VERA_TEXTMATRIX_ADDR
sta cx16.VERA_ADDR_M
lda char
lda character
sta cx16.VERA_DATA0
inc cx16.VERA_ADDR_L
lda colors
+126
View File
@@ -0,0 +1,126 @@
; Experimental Vera FX support.
; Docs:
; https://github.com/X16Community/x16-docs/blob/master/VERA%20FX%20Reference.md
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg/edit
verafx {
%option no_symbol_prefixing
sub available() -> bool {
; returns true if Vera FX is available (Vera V0.3.1 or later), false if not.
cx16.r1L = 0
cx16.r0L = cx16.VERA_CTRL
cx16.VERA_CTRL = $7e
if cx16.VERA_DC_VER0 == $56 {
; Vera version number is valid.
; Vera fx is available on Vera version 0.3.1 and later,
; so no need to even check VERA_DC_VER1, which contains 0 (or higher)
cx16.r1L = mkword(cx16.VERA_DC_VER2, cx16.VERA_DC_VER3) >= $0301
}
cx16.VERA_CTRL = cx16.r0L
return cx16.r1L
}
sub clear(ubyte vbank, uword vaddr, ubyte data, uword amountof32bits) {
; use cached 4-byte write to quickly clear a portion of the video memory to a given byte value
; this routine is around 3 times faster as gfx2.clear_screen()
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = vbank | %00110000 ; 4-byte increment
cx16.VERA_ADDR_M = msb(vaddr)
cx16.VERA_ADDR_L = lsb(vaddr)
cx16.VERA_CTRL = 6<<1 ; dcsel = 6, fill the 32 bits cache
cx16.VERA_FX_CACHE_L = data
cx16.VERA_FX_CACHE_M = data
cx16.VERA_FX_CACHE_H = data
cx16.VERA_FX_CACHE_U = data
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
cx16.VERA_FX_MULT = 0
cx16.VERA_FX_CTRL = %01000000 ; cache write enable
if (amountof32bits & %1111110000000011) == 0 {
repeat lsb(amountof32bits >> 2)
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else if (amountof32bits & %1111111000000001) == 0 {
repeat lsb(amountof32bits >> 1)
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else if (lsb(amountof32bits) & 3) == 0 {
repeat amountof32bits >> 2
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else if (lsb(amountof32bits) & 1) == 0 {
repeat amountof32bits >> 1
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
}
else {
repeat amountof32bits
cx16.VERA_DATA0=0 ; write 4 bytes at a time
}
cx16.VERA_FX_CTRL = 0 ; cache write disable
cx16.VERA_CTRL = 0
}
; unsigned multiplication just passes the values as signed to muls
; if you do this yourself in your call to muls, it will save a few instructions.
sub mult(uword value1, uword value2) -> uword {
return muls(value1 as word, value2 as word) as uword
}
asmsub muls(word value1 @R0, word value2 @R1) -> word @AY {
%asm {{
lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25
stz cx16.VERA_FX_CTRL ; $9F29 (mainly to reset Addr1 Mode to 0)
lda #%00010000
sta cx16.VERA_FX_MULT ; $9F2C
lda #(6 << 1)
sta cx16.VERA_CTRL ; $9F25
lda cx16.r0
ldy cx16.r0+1
sta cx16.VERA_FX_CACHE_L ; $9F29
sty cx16.VERA_FX_CACHE_M ; $9F2A
lda cx16.r1
ldy cx16.r1+1
sta cx16.VERA_FX_CACHE_H ; $9F2B
sty cx16.VERA_FX_CACHE_U ; $9F2C
lda cx16.VERA_FX_ACCUM_RESET ; $9F29 (DCSEL=6)
; Set the ADDR0 pointer to $1f9bc and write our multiplication result there
; (these are the 4 bytes just before the PSG registers start)
lda #(2 << 1)
sta cx16.VERA_CTRL
lda #%01000000 ; Cache Write Enable
sta cx16.VERA_FX_CTRL
lda #$bc
sta cx16.VERA_ADDR_L
lda #$f9
sta cx16.VERA_ADDR_M
lda #$01
sta cx16.VERA_ADDR_H ; no increment
stz cx16.VERA_DATA0 ; multiply and write out result
lda #%00010001 ; $01 with Increment 1
sta cx16.VERA_ADDR_H ; so we can read out the result
lda cx16.VERA_DATA0
ldy cx16.VERA_DATA0
stz cx16.VERA_FX_CTRL ; Cache write disable
stz cx16.VERA_CTRL ; reset DCSEL
rts
; we skip the upper 16 bits of the result:
; lda cx16.VERA_DATA0
; sta $0402
; lda cx16.VERA_DATA0
; sta $0403
}}
}
sub transparency(bool enable) {
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
if enable
cx16.VERA_FX_CTRL |= %10000000
else
cx16.VERA_FX_CTRL &= %01111111
cx16.VERA_CTRL = 0
}
}
+53 -43
View File
@@ -7,22 +7,33 @@
diskio {
%option no_symbol_prefixing
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
ubyte drivenumber = 8
sub set_drive(ubyte number) {
drivenumber = number
}
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
}
sub reset_write_channel() {
cbm.CHKOUT(WRITE_IO_CHANNEL)
}
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -42,12 +53,12 @@ diskio {
ubyte high = cbm.CHRIN()
txt.print_uw(mkword(high, low))
txt.spc()
ubyte @zp char
ubyte @zp character
repeat {
char = cbm.CHRIN()
if char==0
character = cbm.CHRIN()
if character==0
break
txt.chrout(char)
txt.chrout(character)
}
txt.nl()
void cbm.CHRIN() ; skip 2 bytes
@@ -60,7 +71,7 @@ diskio {
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
@@ -76,12 +87,12 @@ io_error:
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte okay = false
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -108,7 +119,7 @@ io_error:
io_error:
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
if okay
return &list_filename
return 0
@@ -126,7 +137,7 @@ io_error:
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
@@ -152,7 +163,7 @@ io_error:
return files_found
}
; ----- iterative file lister functions (uses io channel 12) -----
; ----- iterative file lister functions (uses the read io channel) -----
sub lf_start_list(uword pattern_ptr) -> bool {
; -- start an iterative file listing with optional pattern matching.
@@ -163,11 +174,11 @@ io_error:
iteration_in_progress = true
cbm.SETNAM(1, "$")
cbm.SETLFS(12, drivenumber, 0)
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cs
goto io_error
@@ -192,7 +203,7 @@ io_error:
return false
repeat {
void cbm.CHKIN(12) ; use #12 as input channel again
reset_read_channel() ; use the input io channel again
uword nameptr = &list_filename
ubyte blocks_lsb = cbm.CHRIN()
@@ -211,12 +222,12 @@ io_error:
; read the filename
repeat {
ubyte char = cbm.CHRIN()
if char==0
ubyte character = cbm.CHRIN()
if character==0
break
if char=='\"'
if character=='\"'
break
@(nameptr) = char
@(nameptr) = character
nameptr++
}
@@ -253,13 +264,13 @@ close_end:
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file loader functions (uses io channel 12) -----
; ----- iterative file loader functions (uses the input io channel) -----
sub f_open(uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read
@@ -267,18 +278,18 @@ close_end:
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
void cbm.OPEN() ; open 12,8,12,"filename"
if_cc {
if cbm.READST()==0 {
iteration_in_progress = true
void cbm.CHKIN(12) ; use #12 as input channel
reset_read_channel()
if_cc {
void cbm.CHRIN() ; read first byte to test for file not found
if not cbm.READST() {
cbm.CLOSE(12) ; close file because we already consumed first byte
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
void cbm.OPEN() ; re-open the file
void cbm.CHKIN(12)
cbm.CLRCHN() ; reset default i/o channels
return true
}
}
@@ -294,6 +305,7 @@ close_end:
if not iteration_in_progress or not num_bytes
return 0
reset_read_channel()
list_blocks = 0 ; we reuse this variable for the total number of bytes read
%asm {{
@@ -328,6 +340,7 @@ m_in_buffer sta $ffff
if not iteration_in_progress
return 0
reset_read_channel()
uword total_read = 0
while not cbm.READST() {
cx16.r0 = f_read(bufferpointer, 256)
@@ -346,8 +359,7 @@ m_in_buffer sta $ffff
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #12
jsr cbm.CHKIN ; use channel 12 again for input
jsr reset_read_channel
ldy #0
_loop jsr cbm.CHRIN
sta (P8ZP_SCRATCH_W1),y
@@ -368,25 +380,23 @@ _end rts
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
cbm.CLRCHN()
cbm.CLOSE(12)
cbm.CLOSE(READ_IO_CHANNEL)
iteration_in_progress = false
}
}
; ----- iterative file writing functions (uses io channel 13) -----
; ----- iterative file writing functions (uses write io channel) -----
sub f_open_w(uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write
f_close_w()
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(13, drivenumber, 1)
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
void cbm.OPEN() ; open 13,8,1,"filename"
if_cc {
cbm.CHKOUT(13) ; use #13 as output channel
if_cc
return not cbm.READST()
}
f_close_w()
return false
}
@@ -394,7 +404,7 @@ _end rts
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
if num_bytes!=0 {
cbm.CHKOUT(13) ; use #13 as output channel again
reset_write_channel()
repeat num_bytes {
cbm.CHROUT(@(bufferpointer))
bufferpointer++
@@ -407,7 +417,7 @@ _end rts
sub f_close_w() {
; -- end an iterative file writing session (close channels).
cbm.CLRCHN()
cbm.CLOSE(13)
cbm.CLOSE(WRITE_IO_CHANNEL)
}
@@ -444,16 +454,16 @@ io_error:
goto done
}
sub save(uword filenameptr, uword address, uword size) -> bool {
sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(1, drivenumber, 0)
uword @shared end_address = address + size
uword @shared end_address = start_address + savesize
cx16.r0L = 0
%asm {{
lda address
lda start_address
sta P8ZP_SCRATCH_W1
lda address+1
lda start_address+1
sta P8ZP_SCRATCH_W1+1
lda #<P8ZP_SCRATCH_W1
ldx end_address
@@ -504,16 +514,16 @@ io_error:
; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
; No program header is assumed in the file. Everything is loaded.
; See comments on load() for more details.
sub load_raw(uword filenameptr, uword address) -> uword {
sub load_raw(uword filenameptr, uword start_address) -> uword {
; read the 2 header bytes separately to skip them
if not f_open(filenameptr)
return 0
cx16.r1 = f_read(address, 2)
cx16.r1 = f_read(start_address, 2)
f_close()
if cx16.r1!=2
return 0
address += 2
return load(filenameptr, address)
start_address += 2
return load(filenameptr, start_address)
}
sub delete(uword filenameptr) {
+191 -74
View File
@@ -4,78 +4,183 @@
; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math
; http://codebase64.org/doku.php?id=base:6502_6510_maths
;
; https://github.com/TobyLobster/multiply_test
; https://github.com/TobyLobster/sqrt_test
multiply_bytes .proc
; -- multiply 2 bytes A and Y, result as byte in A (signed or unsigned)
sta P8ZP_SCRATCH_B1 ; num1
sty P8ZP_SCRATCH_REG ; num2
lda #0
beq _enterloop
_doAdd clc
adc P8ZP_SCRATCH_B1
_loop asl P8ZP_SCRATCH_B1
_enterloop lsr P8ZP_SCRATCH_REG
bcs _doAdd
bne _loop
rts
.pend
; https://github.com/TobyLobster/multiply_test/blob/main/tests/mult29.a
_multiplicand = P8ZP_SCRATCH_B1
_multiplier = P8ZP_SCRATCH_REG
multiply_bytes_into_word .proc
; -- multiply 2 bytes A and Y, result as word in A/Y (unsigned)
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
lda #0
ldx #8
lsr P8ZP_SCRATCH_B1
- bcc +
clc
adc P8ZP_SCRATCH_REG
+ ror a
ror P8ZP_SCRATCH_B1
dex
bne -
tay
lda P8ZP_SCRATCH_B1
rts
sty _multiplicand
lsr a
sta _multiplier
lda #0
ldx #2
-
bcc +
clc
adc _multiplicand
+
ror a
ror _multiplier
bcc +
clc
adc _multiplicand
+
ror a
ror _multiplier
bcc +
clc
adc _multiplicand
+
ror a
ror _multiplier
bcc +
clc
adc _multiplicand
+
ror a
ror _multiplier
dex
bne -
; tay ; if you want 16 bits result in AY, enable this again
lda _multiplier
rts
.pend
multiply_words .proc
; -- multiply two 16-bit words into a 32-bit result (signed and unsigned)
; input: A/Y = first 16-bit number, P8ZP_SCRATCH_W1 in ZP = second 16-bit number
; output: multiply_words.result 4-bytes/32-bits product, LSB order (low-to-high) low 16 bits also in AY.
; input: A/Y = first 16-bit number, multiply_words.multiplier = second 16-bit number
; output: multiply_words.result, 4-bytes/32-bits product, LSB order (low-to-high) low 16 bits also in AY.
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
; NOTE: the result (which includes the multiplier parameter on entry) is a 4-byte array.
; this routine could be faster if we could stick that into zeropage,
; but there currently is no way to use 4 consecutive bytes in ZP (without disabling irq and saving/restoring them)...
mult16 lda #0
sta result+2 ; clear upper bits of product
sta result+3
ldx #16 ; for all 16 bits...
- lsr P8ZP_SCRATCH_W1+1 ; divide multiplier by 2
ror P8ZP_SCRATCH_W1
bcc +
lda result+2 ; get upper half of product and add multiplicand
clc
adc P8ZP_SCRATCH_W2
sta result+2
lda result+3
adc P8ZP_SCRATCH_W2+1
+ ror a ; rotate partial product
sta result+3
ror result+2
ror result+1
ror result
dex
bne -
lda result
ldy result+1
rts
; mult62.a
; from: https://github.com/TobyLobster/multiply_test/blob/main/tests/mult62.a
; based on Dr Jefyll, http://forum.6502.org/viewtopic.php?f=9&t=689&start=0#p19958
; - adjusted to use fixed zero page addresses
; - removed 'decrement to avoid clc' as this is slower on average
; - rearranged memory use to remove final memory copy and give LSB first order to result
; - removed temp zp storage bytes
; - unrolled the outer loop
; - unrolled the two inner loops once
;
; 16 bit x 16 bit unsigned multiply, 32 bit result
; Average cycles:
; 93 bytes
_multiplicand = P8ZP_SCRATCH_W2 ; 2 bytes
multiplier = result
; 16 bit x 16 bit unsigned multiply, 32 bit result
;
; On Entry:
; (multiplier, multiplier+1): two byte multiplier, four bytes needed for result
; (multiplicand, multiplicand+1): two byte multiplicand
; On Exit:
; (result, result+1, result+2, result+3): product
sta _multiplicand
sty _multiplicand+1
lda #0 ;
sta result+2 ; 16 bits of zero in A, result+2
; Note: First 8 shifts are A -> result+2 -> result
; Final 8 shifts are A -> result+2 -> result+1
; --- 1st byte ---
ldy #4 ; count for inner loop
lsr result
; inner loop (8 times)
_inner_loop
; first time
bcc +
tax ; retain A
lda result+2
clc
adc _multiplicand
sta result+2
txa ; recall A
adc _multiplicand+1
+
ror a ; shift
ror result+2
ror result
; second time
bcc +
tax ; retain A
lda result+2
clc
adc _multiplicand
sta result+2
txa ; recall A
adc _multiplicand+1
+
ror a ; shift
ror result+2
ror result
dey
bne _inner_loop ; go back for 1 more shift?
; --- 2nd byte ---
ldy #4 ; count for inner loop
lsr result+1
; inner loop (8 times)
_inner_loop2
; first time
bcc +
tax ; retain A
lda result+2
clc
adc _multiplicand
sta result+2
txa ; recall A
adc _multiplicand+1
+
ror a ; shift
ror result+2
ror result+1
; second time
bcc +
tax ; retain A
lda result+2
clc
adc _multiplicand
sta result+2
txa ; recall A
adc _multiplicand+1
+
ror a ; shift
ror result+2
ror result+1
dey
bne _inner_loop2 ; go back for 1 more shift?
sta result+3 ; ms byte of hi-word of result
lda result
ldy result+1
rts
result .byte 0,0,0,0
.pend
@@ -749,74 +854,86 @@ mul_word_640 .proc
; support for bit shifting that is too large to be unrolled:
lsr_byte_A .proc
; -- lsr signed byte in A times the value in Y (>1)
; -- lsr signed byte in A times the value in Y
cpy #0
beq +
cmp #0
bpl lsr_ubyte_A
- sec
ror a
dey
bne -
rts
+ rts
.pend
lsr_ubyte_A .proc
; -- lsr unsigned byte in A times the value in Y (>1)
; -- lsr unsigned byte in A times the value in Y
cpy #0
beq +
- lsr a
dey
bne -
rts
+ rts
.pend
asl_byte_A .proc
; -- asl any byte in A times the value in Y (>1)
; -- asl any byte in A times the value in Y
cpy #0
beq +
- asl a
dey
bne -
rts
+ rts
.pend
lsr_word_AY .proc
; -- lsr signed word in AY times the value in X (>1)
; -- lsr signed word in AY times the value in X
cpx #0
beq +
cpy #0
bpl lsr_uword_AY
sty P8ZP_SCRATCH_B1
_negative sec
- sec
ror P8ZP_SCRATCH_B1
ror a
dex
bne _negative
bne -
ldy P8ZP_SCRATCH_B1
rts
+ rts
.pend
lsr_uword_AY .proc
; -- lsr unsigned word in AY times the value in X (>1)
; -- lsr unsigned word in AY times the value in X
cpx #0
beq +
sty P8ZP_SCRATCH_B1
- lsr P8ZP_SCRATCH_B1
ror a
dex
bne -
ldy P8ZP_SCRATCH_B1
rts
+ rts
.pend
asl_word_AY .proc
; -- asl any word in AY times the value in X (>1)
; -- asl any word in AY times the value in X
cpx #0
beq +
sty P8ZP_SCRATCH_B1
- asl a
rol P8ZP_SCRATCH_B1
dex
bne -
ldy P8ZP_SCRATCH_B1
rts
+ rts
.pend
square .proc
; -- calculate square root of signed word in AY, result in AY
; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm
; using this routine is about twice as fast as doing a regular multiplication.
; -- calculate square of signed word (actually -255..255) in AY, result in AY
; routine by Lee Davison, source: http://6502.org/source/integers/square.htm
; using this routine is a lot faster as doing a regular multiplication (for words)
;
; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in
; Numberl/Numberh. The result is always in the range 0 to 65025 and is held in
+56
View File
@@ -98,6 +98,62 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}}
}
asmsub log2(ubyte value @A) -> ubyte @Y {
%asm {{
sta P8ZP_SCRATCH_B1
lda #$80
ldy #7
- bit P8ZP_SCRATCH_B1
beq +
rts
+ dey
bne +
rts
+ lsr a
bne -
}}
}
asmsub log2w(uword value @AY) -> ubyte @Y {
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$8000
sta cx16.r0
lda #>$8000
sta cx16.r0+1
ldy #15
- lda P8ZP_SCRATCH_W1
and cx16.r0
sta P8ZP_SCRATCH_B1
lda P8ZP_SCRATCH_W1+1
and cx16.r0+1
ora P8ZP_SCRATCH_B1
beq +
rts
+ dey
bne +
rts
+ lsr cx16.r0+1
ror cx16.r0
jmp -
}}
}
asmsub mul16_last_upper() -> uword @AY {
; This routine peeks into the internal 32 bits multiplication result buffer of the
; 16*16 bits multiplication routine, to fetch the upper 16 bits of the last calculation.
; Notes:
; - to avoid interference it's best to fetch and store this value immediately after the multiplication expression.
; for instance, simply printing a number may already result in new multiplication calls being performed
; - not all multiplications in the source code result in an actual multiplication call:
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
%asm {{
lda multiply_words.result+2
ldy multiply_words.result+3
rts
}}
}
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
+452
View File
@@ -0,0 +1,452 @@
; Prog8 definitions for the Commodore PET
; Including memory registers, I/O registers, Basic and Kernal subroutines.
; see: https://www.pagetable.com/?p=926 , http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
cbm {
; Commodore (CBM) common variables, vectors and kernal routines
%option no_symbol_prefixing
&ubyte TIME_HI = $8d ; software jiffy clock, hi byte
&ubyte TIME_MID = $8e ; .. mid byte
&ubyte TIME_LO = $8f ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $96 ; kernal status variable for I/O
&uword CINV = $0090 ; IRQ vector (in ram)
&uword CBINV = $0092 ; BRK vector (in ram)
&uword NMINV = $0094 ; NMI vector (in ram)
&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
; the default addresses for the character screen chars and colors
const uword Screen = $8000 ; to have this as an array[40*25] the compiler would have to support array size > 255
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
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 SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) {
; PET stub to set the software clock
%asm {{
sty TIME_HI
stx TIME_MID
sta TIME_LO
rts
}}
}
asmsub RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y {
; PET stub to read the software clock (A=lo,X=mid,Y=high)
%asm {{
ldy TIME_HI
ldx TIME_MID
lda TIME_LO
rts
}}
}
asmsub RDTIM16() clobbers(X) -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
lda TIME_LO
ldy TIME_MID
rts
}}
}
}
sys {
; ------- lowlevel system routines --------
%option no_symbol_prefixing
const ubyte target = 32 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 32=PET
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; Uppercase charset is activated.
%asm {{
sei
cld
lda #142
jsr cbm.CHROUT ; uppercase
lda #147
jsr cbm.CHROUT ; clear screen
clc
clv
cli
rts
}}
}
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the PET
}}
}
asmsub cleanup_at_exit() {
; executed when the main subroutine does rts
%asm {{
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
jmp (cbm.RESET_VEC)
}}
}
asmsub waitvsync() clobbers(A) {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; Note: on PET this simply waits until the next jiffy clock update, I don't know if a true vsync is possible there
%asm {{
lda #1
ldy #0
jmp wait
}}
}
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
%asm {{
stx P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
ldx P8ZP_SCRATCH_B1
rts
+ lda cbm.TIME_LO
sta P8ZP_SCRATCH_B1
- lda cbm.TIME_LO
cmp P8ZP_SCRATCH_B1
beq -
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
jmp _loop
}}
}
asmsub internal_stringcopy(uword source @R0, uword target @AY) clobbers (A,Y) {
; Called when the compiler wants to assign a string value to another string.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strcpy
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: only works for NON-OVERLAPPING memory regions!
; note: can't be inlined because is called from asm as well
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
rts
_longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
ldy P8ZP_SCRATCH_B1
bne _copyshort
rts
}}
}
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub irqsafe_set_irqd() {
%asm {{
php
sei
}}
}
inline asmsub irqsafe_clear_irqd() {
%asm {{
plp
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}
cx16 {
%option no_symbol_prefixing
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the PET as well but their location in memory is different
; (because there's no room for them in the zeropage)
; we select the top page of RAM (assume 32Kb)
&uword r0 = $7fe0
&uword r1 = $7fe2
&uword r2 = $7fe4
&uword r3 = $7fe6
&uword r4 = $7fe8
&uword r5 = $7fea
&uword r6 = $7fec
&uword r7 = $7fee
&uword r8 = $7ff0
&uword r9 = $7ff2
&uword r10 = $7ff4
&uword r11 = $7ff6
&uword r12 = $7ff8
&uword r13 = $7ffa
&uword r14 = $7ffc
&uword r15 = $7ffe
&word r0s = $7fe0
&word r1s = $7fe2
&word r2s = $7fe4
&word r3s = $7fe6
&word r4s = $7fe8
&word r5s = $7fea
&word r6s = $7fec
&word r7s = $7fee
&word r8s = $7ff0
&word r9s = $7ff2
&word r10s = $7ff4
&word r11s = $7ff6
&word r12s = $7ff8
&word r13s = $7ffa
&word r14s = $7ffc
&word r15s = $7ffe
&ubyte r0L = $7fe0
&ubyte r1L = $7fe2
&ubyte r2L = $7fe4
&ubyte r3L = $7fe6
&ubyte r4L = $7fe8
&ubyte r5L = $7fea
&ubyte r6L = $7fec
&ubyte r7L = $7fee
&ubyte r8L = $7ff0
&ubyte r9L = $7ff2
&ubyte r10L = $7ff4
&ubyte r11L = $7ff6
&ubyte r12L = $7ff8
&ubyte r13L = $7ffa
&ubyte r14L = $7ffc
&ubyte r15L = $7ffe
&ubyte r0H = $7fe1
&ubyte r1H = $7fe3
&ubyte r2H = $7fe5
&ubyte r3H = $7fe7
&ubyte r4H = $7fe9
&ubyte r5H = $7feb
&ubyte r6H = $7fed
&ubyte r7H = $7fef
&ubyte r8H = $7ff1
&ubyte r9H = $7ff3
&ubyte r10H = $7ff5
&ubyte r11H = $7ff7
&ubyte r12H = $7ff9
&ubyte r13H = $7ffb
&ubyte r14H = $7ffd
&ubyte r15H = $7fff
&byte r0sL = $7fe0
&byte r1sL = $7fe2
&byte r2sL = $7fe4
&byte r3sL = $7fe6
&byte r4sL = $7fe8
&byte r5sL = $7fea
&byte r6sL = $7fec
&byte r7sL = $7fee
&byte r8sL = $7ff0
&byte r9sL = $7ff2
&byte r10sL = $7ff4
&byte r11sL = $7ff6
&byte r12sL = $7ff8
&byte r13sL = $7ffa
&byte r14sL = $7ffc
&byte r15sL = $7ffe
&byte r0sH = $7fe1
&byte r1sH = $7fe3
&byte r2sH = $7fe5
&byte r3sH = $7fe7
&byte r4sH = $7fe9
&byte r5sH = $7feb
&byte r6sH = $7fed
&byte r7sH = $7fef
&byte r8sH = $7ff1
&byte r9sH = $7ff3
&byte r10sH = $7ff5
&byte r11sH = $7ff7
&byte r12sH = $7ff9
&byte r13sH = $7ffb
&byte r14sH = $7ffd
&byte r15sH = $7fff
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
}}
}
}
+456
View File
@@ -0,0 +1,456 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore PET
%import syslib
%import conv
txt {
%option no_symbol_prefixing
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character. color is ignored on PET
%asm {{
jmp clear_screenchars
}}
}
asmsub clear_screenchars (ubyte character @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character
; (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
dey
bne -
rts
}}
}
sub clear_screencolors (ubyte color) {
; --- dummy function on PET
}
sub color (ubyte txtcol) {
; --- dummy function on PET
}
sub lowercase() {
txt.chrout(14)
}
sub uppercase() {
txt.chrout(142)
}
asmsub scroll_left () clobbers(A, X, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
%asm {{
ldx #0
ldy #38
-
.for row=0, row<=24, row+=1
lda cbm.Screen + 40*row + 1,x
sta cbm.Screen + 40*row + 0,x
.next
inx
dey
bpl -
rts
}}
}
asmsub scroll_right () clobbers(A,X) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
%asm {{
ldx #38
-
.for row=0, row<=24, row+=1
lda cbm.Screen + 40*row + 0,x
sta cbm.Screen + 40*row + 1,x
.next
dex
bpl -
rts
}}
}
asmsub scroll_up () clobbers(A,X) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
%asm {{
ldx #39
-
.for row=1, row<=24, row+=1
lda cbm.Screen + 40*row,x
sta cbm.Screen + 40*(row-1),x
.next
dex
bpl -
rts
}}
}
asmsub scroll_down () clobbers(A,X) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
%asm {{
ldx #39
-
.for row=23, row>=0, row-=1
lda cbm.Screen + 40*row,x
sta cbm.Screen + 40*(row+1),x
.next
dex
bpl -
rts
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.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
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
pha
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
_ones txa
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr cbm.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- 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
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{
pha
tya
asl a
tay
lda _screenrows+1,y
sta _mod+2
txa
clc
adc _screenrows,y
sta _mod+1
bcc +
inc _mod+2
+ pla
_mod sta $ffff ; modified
rts
_screenrows .word cbm.Screen + range(0, 1000, 40)
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
pha
tya
asl a
tay
lda setchr._screenrows+1,y
sta _mod+2
pla
clc
adc setchr._screenrows,y
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
sub setclr (ubyte col, ubyte row, ubyte color) {
; --- dummy function on PET
}
sub setcc (ubyte col, ubyte row, ubyte character, ubyte charcolor) {
; ---- set char at the given position on the screen. charcolor is ignored on PET
%asm {{
lda row
asl a
tay
lda setchr._screenrows+1,y
sta _charmod+2
lda setchr._screenrows,y
clc
adc col
sta _charmod+1
bcc +
inc _charmod+2
+ lda character
_charmod sta $ffff ; modified
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ X) {
%asm {{
jsr home
cpy #0
beq +
- lda #17
jsr chrout
dey
bne -
+ cpx #0
beq +
- lda #29
jsr chrout
dex
bne -
+ rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
lda $d5
clc
adc #1
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
lda #25
rts
}}
}
}
+36 -27
View File
@@ -127,33 +127,42 @@ _possibly_zero cmp #0
func_sqrt16_into_A .proc
; integer square root from http://6502org.wikidot.com/software-math-sqrt
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #0
sta P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_REG
ldx #8
- sec
lda P8ZP_SCRATCH_W1+1
sbc #$40
tay
lda P8ZP_SCRATCH_REG
sbc P8ZP_SCRATCH_B1
bcc +
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_REG
+ rol P8ZP_SCRATCH_B1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
rol P8ZP_SCRATCH_REG
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
rol P8ZP_SCRATCH_REG
dex
bne -
lda P8ZP_SCRATCH_B1
rts
; integer square root
; http://6502org.wikidot.com/software-math-sqrt
; https://github.com/TobyLobster/sqrt_test/blob/main/sqrt/sqrt7.a
; Tweaked by TobyLobster and 0xC0DE to be smaller and faster
_numl = P8ZP_SCRATCH_W1
_numh = P8ZP_SCRATCH_W1+1
_loop_counter = P8ZP_SCRATCH_REG
_root = P8ZP_SCRATCH_B1
sta _numl
sty _numh
ldx #$ff
stx _loop_counter
inx
stx _root
sec
_loop lda _numh
sbc #$40
tay
txa
sbc _root
bcc +
sty _numh
bcs ++
+ txa
+ rol _root
asl _numl
rol _numh
rol a
asl _numl
rol _numh
rol a
tax
lsr _loop_counter
bne _loop
lda _root
rts
.pend
+8 -8
View File
@@ -225,7 +225,7 @@ _done rts
}}
}
asmsub lowerchar(ubyte char @A) -> ubyte @A {
asmsub lowerchar(ubyte character @A) -> ubyte @A {
%asm {{
and #$7f
cmp #97
@@ -237,7 +237,7 @@ _done rts
}}
}
asmsub upperchar(ubyte char @A) -> ubyte @A {
asmsub upperchar(ubyte character @A) -> ubyte @A {
%asm {{
cmp #65
bcc +
@@ -280,10 +280,10 @@ _done rts
;
; see http://6502.org/source/strings/patmatch.htm
str = P8ZP_SCRATCH_W1
strptr = P8ZP_SCRATCH_W1
sta str
sty str+1
sta strptr
sty strptr+1
lda cx16.r0
sta modify_pattern1+1
sta modify_pattern2+1
@@ -306,9 +306,9 @@ next lda $ffff,x ; look at next pattern character MODIFIED
iny ; no, let's look at the string
cmp #'?' ; is the pattern caracter a ques?
bne reg ; no, it's a regular character
lda (str),y ; yes, so it will match anything
lda (strptr),y ; yes, so it will match anything
beq fail ; except the end of string
reg cmp (str),y ; are both characters the same?
reg cmp (strptr),y ; are both characters the same?
bne fail ; no, so no match
inx ; yes, keep checking
cmp #0 ; are we at end of string?
@@ -330,7 +330,7 @@ stloop txa ; we first try to match with * = ""
tax
bcs found ; we found a match, return with c=1
iny ; no match yet, try to grow * string
lda (str),y ; are we at the end of string?
lda (strptr),y ; are we at the end of string?
bne stloop ; not yet, add a character
fail clc ; yes, no match found, return with c=0
rts
+9
View File
@@ -16,6 +16,15 @@ sub print_f(float value) {
}}
}
sub parse_f(str value) -> float {
; -- parse a string value of a number to float
%ir {{
loadm.w r65535,floats.parse_f.value
syscall 45 (r65535.w): fr0.f
returnr.f fr0
}}
}
sub pow(float value, float power) -> float {
%ir {{
loadm.f fr0,floats.pow.value
+90 -52
View File
@@ -183,60 +183,98 @@ math {
}}
}
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
ubyte angle = atan2(x1, y1, x2, y2) - 256/48
return 23-lsb(mkword(angle,0) / 2730)
}
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
; shift the points into the positive quadrant
ubyte px1
ubyte py1
ubyte px2
ubyte py2
if x1<0 or x2<0 {
px1 = x1 as ubyte + 128
px2 = x2 as ubyte + 128
} else {
px1 = x1 as ubyte
px2 = x2 as ubyte
}
if y1<0 or y2<0 {
py1 = y1 as ubyte + 128
py2 = y2 as ubyte + 128
} else {
py1 = y1 as ubyte
py2 = y2 as ubyte
sub log2(ubyte value) -> ubyte {
ubyte result = 7
ubyte compare = $80
repeat {
if value&compare
return result
result--
if_z
return 0
compare >>= 1
}
}
return direction(px1, py1, px2, py2)
}
sub log2w(uword value) -> ubyte {
ubyte result = 15
uword compare = $8000
repeat {
if value&compare
return result
result--
if_z
return 0
compare >>= 1
}
}
sub direction_qd(ubyte quadrant, ubyte xdelta, ubyte ydelta) -> ubyte {
; From a pair of X/Y deltas (both >=0), and quadrant 0-3, calculate discrete direction between 0 and 23.
when quadrant {
3 -> return direction(0, 0, xdelta, ydelta)
2 -> return direction(xdelta, 0, 0, ydelta)
1 -> return direction(0, ydelta, xdelta, 0)
else -> return direction(xdelta, ydelta, 0, 0)
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
ubyte angle = atan2(x1, y1, x2, y2) - 256/48
return 23-lsb(mkword(angle,0) / 2730)
}
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
; shift the points into the positive quadrant
ubyte px1
ubyte py1
ubyte px2
ubyte py2
if x1<0 or x2<0 {
px1 = x1 as ubyte + 128
px2 = x2 as ubyte + 128
} else {
px1 = x1 as ubyte
px2 = x2 as ubyte
}
if y1<0 or y2<0 {
py1 = y1 as ubyte + 128
py2 = y2 as ubyte + 128
} else {
py1 = y1 as ubyte
py2 = y2 as ubyte
}
return direction(px1, py1, px2, py2)
}
sub direction_qd(ubyte quadrant, ubyte xdelta, ubyte ydelta) -> ubyte {
; From a pair of X/Y deltas (both >=0), and quadrant 0-3, calculate discrete direction between 0 and 23.
when quadrant {
3 -> return direction(0, 0, xdelta, ydelta)
2 -> return direction(xdelta, 0, 0, ydelta)
1 -> return direction(0, ydelta, xdelta, 0)
else -> return direction(xdelta, ydelta, 0, 0)
}
}
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
%ir {{
loadm.b r65532,math.atan2.x1
loadm.b r65533,math.atan2.y1
loadm.b r65534,math.atan2.x2
loadm.b r65535,math.atan2.y2
syscall 44 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
returnr.b r0
}}
}
sub mul16_last_upper() -> uword {
; This routine peeks into the internal 32 bits multiplication result buffer of the
; 16*16 bits multiplication routine, to fetch the upper 16 bits of the last calculation.
; Notes:
; - to avoid interference it's best to fetch and store this value immediately after the multiplication expression.
; for instance, simply printing a number may already result in new multiplication calls being performed
; - not all multiplications in the source code result in an actual multiplication call:
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
%ir {{
syscall 46 (): r0.w
returnr.w r0
}}
}
}
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
;; Calculate the angle, in a 256-degree circle, between two points into A.
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
%ir {{
loadm.b r65532,math.atan2.x1
loadm.b r65533,math.atan2.y1
loadm.b r65534,math.atan2.x2
loadm.b r65535,math.atan2.y2
syscall 44 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
returnr.b r0
}}
}
}
+9 -3
View File
@@ -42,13 +42,15 @@ private fun compileMain(args: Array<String>): Boolean {
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val warnSymbolShadowing by cli.option(ArgType.Boolean, fullName = "warnshadow", description="show assembler warnings about symbol shadowing")
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val includeSourcelines by cli.option(ArgType.Boolean, fullName = "sourcelines", description = "include original Prog8 source lines in generated asm code")
val splitWordArrays by cli.option(ArgType.Boolean, fullName = "splitarrays", description = "treat all word arrays as tagged with @split to make them lsb/msb split in memory")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}') (required)")
val breakpointCpuInstruction by cli.option(ArgType.Boolean, fullName = "breakinstr", description = "also use a CPU instruction for %breakpoint")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${PETTarget.NAME}', '${VMTarget.NAME}') (required)")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
val varsHighBank by cli.option(ArgType.Int, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program. On the cx16 target the value specifies the HiRAM bank to use, on other systems this value is ignored.")
@@ -83,7 +85,7 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME, VMTarget.NAME)) {
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME, PETTarget.NAME, VMTarget.NAME)) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
@@ -113,6 +115,7 @@ private fun compileMain(args: Array<String>): Boolean {
filepath,
dontOptimize != true,
dontWriteAssembly != true,
warnSymbolShadowing == true,
quietAssembler == true,
asmListfile == true,
includeSourcelines == true,
@@ -120,6 +123,7 @@ private fun compileMain(args: Array<String>): Boolean {
varsHighBank,
compilationTarget!!,
splitWordArrays == true,
breakpointCpuInstruction = false,
processedSymbols,
srcdirs,
outputPath
@@ -180,6 +184,7 @@ private fun compileMain(args: Array<String>): Boolean {
filepath,
dontOptimize != true,
dontWriteAssembly != true,
warnSymbolShadowing == true,
quietAssembler == true,
asmListfile == true,
includeSourcelines == true,
@@ -187,6 +192,7 @@ private fun compileMain(args: Array<String>): Boolean {
varsHighBank,
compilationTarget!!,
splitWordArrays == true,
breakpointCpuInstruction == true,
processedSymbols,
srcdirs,
outputPath
@@ -5,11 +5,11 @@ package prog8.buildversion
*/
const val MAVEN_GROUP = "prog8"
const val MAVEN_NAME = "compiler"
const val VERSION = "9.3-SNAPSHOT"
const val GIT_REVISION = 4012
const val GIT_SHA = "9167ba499de99696aa7c6887d4867eae70eb5caf"
const val GIT_DATE = "2023-07-30T15:49:35Z"
const val VERSION = "9.5.1"
const val GIT_REVISION = 4157
const val GIT_SHA = "3dc5a0e7f8e5a94fdb4c13ff9199a4bf912268f2"
const val GIT_DATE = "2023-10-18T21:59:37Z"
const val GIT_BRANCH = "master"
const val BUILD_DATE = "2023-07-30T16:41:27Z"
const val BUILD_UNIX_TIME = 1690735287514L
const val BUILD_DATE = "2023-10-19T19:55:55Z"
const val BUILD_UNIX_TIME = 1697745355291L
const val DIRTY = 1
+10 -1
View File
@@ -30,6 +30,7 @@ class CompilationResult(val compilerAst: Program, // deprecated, use codegenAs
class CompilerArguments(val filepath: Path,
val optimize: Boolean,
val writeAssembly: Boolean,
val warnSymbolShadowing: Boolean,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val includeSourcelines: Boolean,
@@ -37,6 +38,7 @@ class CompilerArguments(val filepath: Path,
val varsHighBank: Int?,
val compilationTarget: String,
val splitWordArrays: Boolean,
val breakpointCpuInstruction: Boolean,
val symbolDefs: Map<String, String>,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
@@ -52,6 +54,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
PETTarget.NAME -> PETTarget()
AtariTarget.NAME -> AtariTarget()
VMTarget.NAME -> VMTarget()
else -> throw IllegalArgumentException("invalid compilation target")
@@ -66,11 +69,13 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
compilationOptions = options
with(compilationOptions) {
warnSymbolShadowing = args.warnSymbolShadowing
optimize = args.optimize
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
includeSourcelines = args.includeSourcelines
experimentalCodegen = args.experimentalCodegen
breakpointCpuInstruction = args.breakpointCpuInstruction
varsHighBank = args.varsHighBank
splitWordArrays = args.splitWordArrays
outputDir = args.outputDir.normalize()
@@ -106,7 +111,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
val intermediateAst = IntermediateAstMaker(program).transform()
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
@@ -251,6 +256,10 @@ fun parseMainModule(filepath: Path,
importer.importImplicitLibraryModule("math")
}
importer.importImplicitLibraryModule("prog8_lib")
if(program.allBlocks.any { it.options().any { option->option=="verafxmuls" } }) {
if(compTarget.name==Cx16Target.NAME)
importer.importImplicitLibraryModule("verafx")
}
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
@@ -7,6 +7,8 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException
@@ -76,14 +78,19 @@ internal class AstChecker(private val program: Program,
}
override fun visit(unrollLoop: UnrollLoop) {
if(unrollLoop.iterations<0 || unrollLoop.iterations>65535)
errors.err("invalid number of unrolls", unrollLoop.position)
unrollLoop.body.statements.forEach {
if(it !is InlineAssembly && it !is Assignment && it !is BuiltinFunctionCallStatement && it !is FunctionCallStatement && it !is PostIncrDecr)
errors.err("invalid statement in unroll loop", it.position)
}
if(unrollLoop.iterations * unrollLoop.body.statements.size > 256) {
errors.warn("large number of unrolls, potential code size issue", unrollLoop.position)
val iterations = unrollLoop.iterations.constValue(program)?.number?.toInt()
if(iterations==null) {
errors.err("unroll needs constant number of iterations", unrollLoop.position)
} else {
if (iterations < 0 || iterations > 65535)
errors.err("invalid number of unrolls", unrollLoop.position)
unrollLoop.body.statements.forEach {
if (it !is InlineAssembly && it !is Assignment && it !is BuiltinFunctionCallStatement && it !is FunctionCallStatement && it !is PostIncrDecr)
errors.err("invalid statement in unroll loop", it.position)
}
if (iterations * unrollLoop.body.statements.size > 256) {
errors.warn("large number of unrolls, potential code size issue", unrollLoop.position)
}
}
super.visit(unrollLoop)
}
@@ -220,7 +227,7 @@ internal class AstChecker(private val program: Program,
override fun visit(jump: Jump) {
val ident = jump.identifier
if(ident!=null) {
val targetStatement = checkFunctionOrLabelExists(ident, jump)
val targetStatement = ident.checkFunctionOrLabelExists(program, jump, errors)
if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionPlaceholder)
errors.err("can't jump to a builtin function", jump.position)
@@ -306,7 +313,7 @@ internal class AstChecker(private val program: Program,
if(subroutine.name in BuiltinFunctions)
err("cannot redefine a built-in function")
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine)
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine && !subroutine.definingBlock.isInLibrary)
errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
@@ -817,8 +824,16 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing")}.any { !it })
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing", "verafxmuls")}.any { !it })
err("invalid option directive argument(s)")
if(directive.args.any {it.name=="align_word"} && directive.args.any { it.name=="align_page"})
err("conflicting alignment options")
if(directive.parent is Block) {
if(directive.args.any {it.name !in arrayOf("align_word", "align_page", "no_symbol_prefixing", "force_output", "merge", "splitarrays", "verafxmuls")})
err("using an option that is not valid for blocks")
}
if(directive.args.any { it.name=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
err("verafx option is only valid on cx16 target")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
}
@@ -1068,7 +1083,7 @@ internal class AstChecker(private val program: Program,
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
val targetStatement = checkFunctionOrLabelExists(functionCallExpr.target, stmtOfExpression)
val targetStatement = functionCallExpr.target.checkFunctionOrLabelExists(program, stmtOfExpression, errors)
if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCallExpr.args, functionCallExpr.position)
@@ -1120,7 +1135,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)
if(targetStatement!=null) {
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
@@ -1178,8 +1193,13 @@ internal class AstChecker(private val program: Program,
if(funcName[0] in InplaceModifyingBuiltinFunctions) {
// in-place modification, can't be done on literals
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
if(funcName[0]=="setlsb" || funcName[0]=="setmsb") {
val firstArg = functionCallStatement.args[0]
if(firstArg !is IdentifierReference && firstArg !is ArrayIndexedExpression)
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
} else {
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead })
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
}
}
@@ -1421,25 +1441,6 @@ internal class AstChecker(private val program: Program,
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
when (val targetStatement = target.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
is VarDecl -> {
if(statement is Jump) {
if (targetStatement.datatype == DataType.UWORD)
return targetStatement
else
errors.err("wrong address variable datatype, expected uword", target.position)
}
else
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
}
null -> errors.undefined(target.nameInSource, target.position)
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
}
return null
}
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
return if (targetDt == DataType.STR) {
when {
@@ -5,13 +5,12 @@ import prog8.ast.Program
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDeclOrigin
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import java.io.CharConversionException
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
@@ -66,13 +65,35 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
errors.err("compilation target doesn't support this text encoding", char.position)
return noModifications
}
return listOf(IAstModification.ReplaceNode(
char,
NumericLiteral(DataType.UBYTE, target.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
parent
))
return try {
val encoded = target.encodeString(char.value.toString(), char.encoding)
listOf(IAstModification.ReplaceNode(
char,
NumericLiteral(DataType.UBYTE, encoded[0].toDouble(), char.position),
parent
))
} catch (x: CharConversionException) {
errors.err(x.message ?: "can't encode character", char.position)
noModifications
}
}
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
// this only *checks* for errors for string encoding. The actual encoding is done much later
require(string.encoding != Encoding.DEFAULT)
if(string.encoding != Encoding.DEFAULT && string.encoding !in target.supportedEncodings) {
errors.err("compilation target doesn't support this text encoding", string.position)
return noModifications
}
try {
target.encodeString(string.value, string.encoding)
} catch (x: CharConversionException) {
errors.err(x.message ?: "can't encode string", string.position)
}
return noModifications
}
}
walker.visit(this)
walker.applyModifications()
}
@@ -151,4 +172,25 @@ internal fun Subroutine.hasRtsInAsm(): Boolean {
.asSequence()
.filterIsInstance<InlineAssembly>()
.any { it.hasReturnOrRts() }
}
internal fun IdentifierReference.checkFunctionOrLabelExists(program: Program, statement: Statement, errors: IErrorReporter): Statement? {
when (val targetStatement = this.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
is VarDecl -> {
if(statement is Jump) {
if (targetStatement.datatype == DataType.UWORD)
return targetStatement
else
errors.err("wrong address variable datatype, expected uword", this.position)
}
else
errors.err("cannot call that: ${this.nameInSource.joinToString(".")}", this.position)
}
null -> {
errors.undefined(this.nameInSource, this.position)
}
else -> errors.err("cannot call that: ${this.nameInSource.joinToString(".")}", this.position)
}
return null
}
@@ -1,9 +1,12 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.findParentNode
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@@ -201,4 +204,42 @@ class AstPreprocessor(val program: Program,
return noModifications
}
override fun after(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> {
val stmtOfExpression = findParentNode<Statement>(bfc)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${bfc.position}")
checkStringParam(bfc as IFunctionCall, stmtOfExpression)
return noModifications
}
override fun after(bfcs: BuiltinFunctionCallStatement, parent: Node): Iterable<IAstModification> {
checkStringParam(bfcs as IFunctionCall, bfcs)
return noModifications
}
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
checkStringParam(functionCallExpr as IFunctionCall, stmtOfExpression)
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
checkStringParam(functionCallStatement as IFunctionCall, functionCallStatement)
return noModifications
}
private fun checkStringParam(call: IFunctionCall, stmt: Statement) {
val targetStatement = call.target.checkFunctionOrLabelExists(program, stmt, errors)
if(targetStatement!=null) {
if(targetStatement is Subroutine) {
for(arg in call.args.zip(targetStatement.parameters)) {
if(arg.first.inferType(program).isBytes && arg.second.type==DataType.STR) {
errors.err("cannot use byte value for string parameter", arg.first.position)
}
}
}
}
}
}
@@ -39,62 +39,6 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
// this triggers the more efficent augmented assignment code generation more often.
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(options.compTarget.name==VMTarget.NAME) // don't apply this optimization for Vm target
return noModifications
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null && binExpr.operator !in ComparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
// the right part of the expression contains the target variable itself.
// we can't 'split' it trivially because the variable will be changed halfway through.
if(binExpr.operator in AssociativeOperators) {
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
// use the other part of the expression to split.
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
"unknown dt"
)
}, sourceDt, implicit=true)
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.ASMGEN, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
)
}
} else {
if(binExpr.left isSameAs assignment.target)
return noModifications
val typeCast = binExpr.left as? TypecastExpression
if(typeCast!=null && typeCast.expression isSameAs assignment.target)
return noModifications
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
"unknown dt"
)
}, sourceDt, implicit=true)
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.ASMGEN, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
)
}
}
}
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
@@ -56,7 +56,7 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
listOf(
IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
AddressOf(identifier, null, typecast.position),
parent
)
)
@@ -24,6 +24,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - replace peek() and poke() by direct memory accesses.
// - repeat-forever loops replaced by label+jump.
// - pointer[word] replaced by @(pointer+word)
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
@@ -200,6 +201,41 @@ _after:
}
}
if(expr.operator=="*" && expr.inferType(program).isInteger && expr.left isSameAs expr.right) {
// replace squaring with call to builtin function to do this in a more optimized way
val function = if(expr.left.inferType(program).isBytes) "prog8_lib_square_byte" else "prog8_lib_square_word"
val squareCall = BuiltinFunctionCall(
IdentifierReference(listOf(function), expr.position),
mutableListOf(expr.left.copy()), expr.position)
return listOf(IAstModification.ReplaceNode(expr, squareCall, parent))
}
return noModifications
}
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// for word variables:
// @(&var) --> lsb(var)
// @(&var+1) --> msb(var)
val addrOf = memread.addressExpression as? AddressOf
if(addrOf?.arrayIndex!=null)
return noModifications
if(addrOf!=null && addrOf.identifier.inferType(program).isWords) {
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), memread.position), mutableListOf(addrOf.identifier), memread.position)
return listOf(IAstModification.ReplaceNode(memread, lsb, parent))
}
val expr = memread.addressExpression as? BinaryExpression
if(expr!=null && expr.operator=="+") {
val addressOf = expr.left as? AddressOf
val offset = (expr.right as? NumericLiteral)?.number?.toInt()
if(addressOf!=null && offset==1) {
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(addressOf.identifier), memread.position)
return listOf(IAstModification.ReplaceNode(memread, msb, parent))
}
}
return noModifications
}
}
@@ -19,7 +19,7 @@ import kotlin.io.path.isRegularFile
/**
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
*/
class IntermediateAstMaker(private val program: Program) {
class IntermediateAstMaker(private val program: Program, private val errors: IErrorReporter) {
fun transform(): PtProgram {
val ptProgram = PtProgram(
program.name,
@@ -161,6 +161,7 @@ class IntermediateAstMaker(private val program: Program) {
private fun transform(srcBlock: Block): PtBlock {
var alignment = PtBlock.BlockAlignment.NONE
var forceOutput = false
var veraFxMuls = false
var noSymbolPrefixing = false
val directives = srcBlock.statements.filterIsInstance<Directive>()
for (directive in directives.filter { it.directive == "%option" }) {
@@ -169,15 +170,16 @@ class IntermediateAstMaker(private val program: Program) {
"align_word" -> alignment = PtBlock.BlockAlignment.WORD
"align_page" -> alignment = PtBlock.BlockAlignment.PAGE
"no_symbol_prefixing" -> noSymbolPrefixing = true
"force_output" -> forceOutput=true
"force_output" -> forceOutput = true
"merge", "splitarrays" -> { /* ignore this one */ }
"verafxmuls" -> veraFxMuls = true
else -> throw FatalAstException("weird directive option: ${arg.name}")
}
}
}
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val src = srcBlock.definingModule.source
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, noSymbolPrefixing, alignment, src, srcBlock.position)
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, noSymbolPrefixing, veraFxMuls, alignment, src, srcBlock.position)
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements)
block.add(transformStatement(stmt))
@@ -326,7 +328,7 @@ class IntermediateAstMaker(private val program: Program) {
private fun transform(srcUnroll: UnrollLoop): PtNodeGroup {
val result = PtNodeGroup()
repeat(srcUnroll.iterations) {
repeat(srcUnroll.iterations.constValue(program)!!.number.toInt()) {
srcUnroll.body.statements.forEach {
result.add(transformStatement(it))
}
@@ -398,13 +400,26 @@ class IntermediateAstMaker(private val program: Program) {
}
private fun transform(srcVar: VarDecl): PtNode {
return when(srcVar.type) {
when(srcVar.type) {
VarDeclType.VAR -> {
val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null
PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
if(srcVar.datatype in ArrayDatatypes) {
if(value==null) {
val blockOptions = srcVar.definingBlock.options()
if("align_page" in blockOptions || "align_word" in blockOptions) {
errors.warn("converting uninitialized array to explicit zeros because of block alignment option", srcVar.position)
val zeros = PtArray(srcVar.datatype, srcVar.position)
repeat(srcVar.arraysize!!.constIndex()!!) {
zeros.children.add(PtNumber(ArrayToElementTypes.getValue(srcVar.datatype), 0.0, srcVar.position))
}
return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, zeros, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
}
}
return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
VarDeclType.CONST -> return PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> return PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
}
}
@@ -440,6 +455,8 @@ class IntermediateAstMaker(private val program: Program) {
addr.add(PtIdentifier(name+"_lsb", dt, src.identifier.position)) // NOTE: assumes _lsb is first in memory! (immediately followed by _msb)
else
addr.add(transform(src.identifier))
if(src.arrayIndex!=null)
addr.add(transformExpression(src.arrayIndex!!.indexExpr))
return addr
}
@@ -1,16 +1,17 @@
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
import prog8.ast.statements.IfElse
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.PrefixExpression
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.code.core.DataType
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.core.IntegerDatatypes
internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
@@ -99,110 +100,4 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
}
return noModifications
}
override fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
return noModifications
val binExpr = ifElse.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in ComparisonOperators)
return noModifications
// Simplify the conditional expression, introduce simple assignments if required.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
val simplify = simplifyConditionalExpression(binExpr)
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(
ifElse,
simplify.rightVarAssignment,
parent as IStatementContainer
)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(
ifElse,
simplify.leftVarAssignment,
parent as IStatementContainer
)
}
return modifications
}
private class CondExprSimplificationResult(
val leftVarAssignment: Assignment?,
val leftOperandReplacement: Expression?,
val rightVarAssignment: Assignment?,
val rightOperandReplacement: Expression?
)
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
return CondExprSimplificationResult(null, null, null, null)
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(!leftDt.isInteger || !rightDt.isInteger) {
// we can't reasonably simplify non-integer expressions
return CondExprSimplificationResult(null, null, null, null)
}
var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple
&& expr.left !is IFunctionCall
&& expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple
&& expr.right !is IFunctionCall
&& expr.right !is ContainmentCheck
if(separateLeftExpr) {
val name = getTempRegisterName(leftDt)
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.left.copy(),
AssignmentOrigin.ASMGEN, expr.position
)
}
if(separateRightExpr) {
val (tempVarName, _) = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
rightOperandReplacement = IdentifierReference(tempVarName, expr.position)
rightAssignment = Assignment(
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
expr.right.copy(),
AssignmentOrigin.ASMGEN, expr.position
)
}
return CondExprSimplificationResult(
leftAssignment, leftOperandReplacement,
rightAssignment, rightOperandReplacement
)
}
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
dt istype DataType.BOOL -> listOf("cx16", "r9L")
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
dt istype DataType.UWORD -> listOf("cx16", "r9")
dt istype DataType.WORD -> listOf("cx16", "r9s")
dt.isPassByReference -> listOf("cx16", "r9")
else -> throw FatalAstException("invalid dt $dt")
}
}
}
@@ -304,8 +304,8 @@ internal class StatementReorderer(
val eltsize = program.memsizer.memorySize(ArrayToElementTypes.getValue(sourceVar.datatype))
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
mutableListOf(
AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position),
AddressOf(sourceIdent, null, assign.position),
AddressOf(identifier, null, assign.position),
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
), false, assign.position
)
@@ -265,7 +265,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, arg.position),
AddressOf(identifier, null, arg.position),
call as Node)
}
} else if(arg is NumericLiteral) {
@@ -286,7 +286,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(arg is IdentifierReference && DataType.UWORD == param.type) {
modifications += IAstModification.ReplaceNode(
arg,
AddressOf(arg, arg.position),
AddressOf(arg, null, arg.position),
call as Node
)
}
@@ -312,7 +312,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(identifier, pair.second.position),
AddressOf(identifier, null, pair.second.position),
call as Node)
break
}
@@ -325,7 +325,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(pair.second is IdentifierReference && DataType.UWORD in pair.first.possibleDatatypes) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position),
AddressOf(pair.second as IdentifierReference, null, pair.second.position),
call as Node
)
}
+8 -5
View File
@@ -27,6 +27,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
filepath,
optimize,
writeAssembly = true,
warnSymbolShadowing = false,
quietAssembler = true,
asmListfile = false,
includeSourcelines = false,
@@ -34,6 +35,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
varsHighBank = null,
compilationTarget = target.name,
splitWordArrays = false,
breakpointCpuInstruction = false,
symbolDefs = emptyMap(),
outputDir = outputDir
)
@@ -94,9 +96,12 @@ class TestCompilerOnExamplesCx16: FunSpec({
val onlyCx16 = cartesianProduct(
listOf(
"chunkedfile/demo",
"vtui/testvtui",
"pcmaudio/play-adpcm",
"pcmaudio/stream-wav",
"sprites/dragon",
"sprites/dragons",
"amiga",
"bdmusic",
"bobs",
@@ -106,21 +111,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
"colorbars",
"cube3d",
"cxlogo",
"datetime",
"diskspeed",
"fileseek",
"highresbitmap",
"kefrenbars",
"keyboardhandler",
"mandelbrot",
"mandelbrot-gfx-colors",
"multipalette",
"plasma",
"rasterbars",
"sincos",
"snow",
"spotlight",
"tehtriz",
"testgfx2",
"testmonogfx",
),
listOf(false, true)
)
@@ -141,7 +144,6 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
listOf(
"animals",
"balls",
"bsieve",
"cube3d",
"cube3d-float",
"cube3d-gfx",
@@ -155,6 +157,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"numbergame",
"primes",
"screencodes",
"sincos",
"sorting",
"swirl",
"swirl-float",
@@ -44,6 +44,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
filepath = filePath,
optimize = false,
writeAssembly = true,
warnSymbolShadowing = false,
quietAssembler = true,
asmListfile = false,
includeSourcelines = false,
@@ -51,6 +52,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
varsHighBank = null,
compilationTarget = Cx16Target.NAME,
splitWordArrays = false,
breakpointCpuInstruction = false,
symbolDefs = emptyMap(),
sourceDirs,
outputDir
+15
View File
@@ -89,6 +89,17 @@ class TestStringEncodings: FunSpec({
PetsciiEncoding.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
}
test("testReturn") {
PetsciiEncoding.encodePetscii("\r", true) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.encodePetscii("\r", false) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.encodePetscii("\n", true) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.encodePetscii("\n", false) shouldBe Ok(listOf<UByte>(13u))
PetsciiEncoding.decodePetscii(listOf(13u), false) shouldBe Ok("\n")
PetsciiEncoding.decodePetscii(listOf(13u), true) shouldBe Ok("\n")
PetsciiEncoding.decodePetscii(listOf(0x8du), false) shouldBe Ok("\r")
PetsciiEncoding.decodePetscii(listOf(0x8du), true) shouldBe Ok("\r")
}
test("testSpecialReplacements") {
fun encodeP(c: Char, lower: Boolean) = PetsciiEncoding.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
fun encodeS(c: Char, lower: Boolean) = PetsciiEncoding.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
@@ -122,6 +133,10 @@ class TestStringEncodings: FunSpec({
encodeP('\\', true) shouldBe 205u
encodeS('\\', false) shouldBe 77u
encodeS('\\', true) shouldBe 77u
encodeP('\r', true) shouldBe 13u
encodeP('\r', false) shouldBe 13u
encodeP('\n', true) shouldBe 13u
encodeP('\n', false) shouldBe 13u
}
test("testBoxDrawingCharsEncoding") {
+24 -3
View File
@@ -82,7 +82,28 @@ class TestSymbolTable: FunSpec({
st.allMemMappedVariables.single().scopedName shouldBe "block1.sub1.v3"
st.allMemorySlabs.single().scopedName shouldBe "block1.sub1.slab1"
}
test("static vars") {
val node = PtIdentifier("dummy", DataType.UBYTE, Position.DUMMY)
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, 99.0, null, null, null, ZeropageWish.DONTCARE, node)
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, null, ZeropageWish.DONTCARE, node)
val arrayInitNonzero = listOf(StArrayElement(1.1, null), StArrayElement(2.2, null), StArrayElement(3.3, null))
val arrayInitAllzero = listOf(StArrayElement(0.0, null), StArrayElement(0.0, null), StArrayElement(0.0, null))
val stVar3 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitNonzero, 3, ZeropageWish.DONTCARE, node)
val stVar4 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitAllzero, 3, ZeropageWish.DONTCARE, node)
val stVar5 = StStaticVariable("uninitialized", DataType.ARRAY_UW, null, null, null, 3, ZeropageWish.DONTCARE, node)
stVar1.uninitialized shouldBe false
stVar1.length shouldBe null
stVar2.uninitialized shouldBe true
stVar2.length shouldBe null
stVar3.uninitialized shouldBe false
stVar3.length shouldBe 3
stVar4.uninitialized shouldBe false
stVar4.length shouldBe 3
stVar5.uninitialized shouldBe true
stVar5.length shouldBe 3
}
})
@@ -90,7 +111,7 @@ private fun makeSt(): SymbolTable {
// first build the AST
val astProgram = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val astBlock1 = PtBlock("block1", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
val astBlock1 = PtBlock("block1", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
val astConstant1 = PtConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY)
val astConstant2 = PtConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY)
astBlock1.add(astConstant1)
@@ -113,7 +134,7 @@ private fun makeSt(): SymbolTable {
astBlock1.add(astSub2)
val astBfunc = PtIdentifier("msb", DataType.UBYTE, Position.DUMMY)
astBlock1.add(astBfunc)
val astBlock2 = PtBlock("block2", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
val astBlock2 = PtBlock("block2", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
val astSub21 = PtSub("sub1", emptyList(), null, Position.DUMMY)
val astSub22 = PtSub("sub2", emptyList(), null, Position.DUMMY)
val astSub221 = PtSub("subsub", emptyList(), null, Position.DUMMY)
+3 -4
View File
@@ -58,11 +58,10 @@ class TestTypecasts: FunSpec({
val result2 = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts2 = result2.compilerAst.entrypoint.statements
stmts2.size shouldBe 6
val expr2 = (stmts2[4] as Assignment).value as BinaryExpression
stmts2.size shouldBe 5
val expr2 = (stmts2[3] as Assignment).value as BinaryExpression
expr2.operator shouldBe "&"
expr2.right shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
(expr2.left as IdentifierReference).nameInSource shouldBe listOf("bb")
}
test("bool expressions with functioncalls") {
@@ -740,7 +739,7 @@ main {
}
"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
result.compilerAst.entrypoint.statements.size shouldBe 15
result.compilerAst.entrypoint.statements.size shouldBe 14
}
test("invalid typecasts of numbers") {
+6 -6
View File
@@ -118,11 +118,11 @@ class TestC64Zeropage: FunSpec({
test("testFreeSpacesBytes") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp1.availableBytes() shouldBe 18
zp1.availableBytes() shouldBe 17
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
zp2.availableBytes() shouldBe 88
zp2.availableBytes() shouldBe 87
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
zp3.availableBytes() shouldBe 97
zp3.availableBytes() shouldBe 96
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
zp4.availableBytes() shouldBe 207
zp4.allocate("test", DataType.UBYTE, null, null, errors)
@@ -162,7 +162,7 @@ class TestC64Zeropage: FunSpec({
test("testBasicsafeAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp.availableBytes() shouldBe 18
zp.availableBytes() shouldBe 17
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
@@ -215,18 +215,18 @@ class TestC64Zeropage: FunSpec({
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
zp.availableBytes() shouldBe 18
zp.availableBytes() shouldBe 17
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xa6u
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
zp.availableBytes() shouldBe 0
}
+4 -12
View File
@@ -5,9 +5,10 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.target.C64Target
import prog8.compiler.astprocessing.IntermediateAstMaker
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestIntermediateAst: FunSpec({
@@ -26,18 +27,9 @@ class TestIntermediateAst: FunSpec({
}
"""
val target = C64Target()
val options = CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.DONTUSE,
emptyList(),
floats = false,
noSysInit = true,
compTarget = target,
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
)
val errors = ErrorReporterForTests()
val result = compileText(target, false, text, writeAssembly = false)!!
val ast = IntermediateAstMaker(result.compilerAst).transform()
val ast = IntermediateAstMaker(result.compilerAst, errors).transform()
ast.name shouldBe result.compilerAst.name
ast.allBlocks().any() shouldBe true
val entry = ast.entrypoint() ?: fail("no main.start() found")
@@ -351,5 +351,20 @@ main
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("char as str param is error") {
val src = """
main {
sub start() {
print('@')
}
sub print(str message) {
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), optimize=false, src, writeAssembly=false, errors = errors) shouldBe null
errors.errors.single() shouldContain "cannot use byte value"
}
})
@@ -52,13 +52,13 @@ class TestAsmGenSymbols: StringSpec({
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, statements, Position.DUMMY)
@@ -75,7 +75,7 @@ class TestAsmGenSymbols: StringSpec({
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
val ptProgram = IntermediateAstMaker(program).transform()
val ptProgram = IntermediateAstMaker(program, errors).transform()
val st = SymbolTableMaker(ptProgram, options).make()
return AsmGen6502Internal(ptProgram, st, options, errors)
}
@@ -9,6 +9,7 @@ import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.types.instanceOf
import prog8.code.ast.PtArrayIndexer
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtBinaryExpression
import prog8.code.ast.PtVariable
import prog8.code.core.DataType
import prog8.code.target.C64Target
@@ -93,7 +94,7 @@ main {
seed.type shouldBe DataType.ARRAY_UW
val assign = start.children[1] as PtAssignment
assign.target.identifier!!.name shouldBe "cx16.r0"
assign.value shouldBe instanceOf<PtArrayIndexer>()
assign.value shouldBe instanceOf<PtBinaryExpression>()
}
test("peek and poke argument types") {
+3 -1
View File
@@ -24,6 +24,7 @@ internal fun compileFile(
filepath,
optimize,
writeAssembly = writeAssembly,
warnSymbolShadowing = false,
quietAssembler = true,
asmListfile = false,
includeSourcelines = false,
@@ -33,7 +34,8 @@ internal fun compileFile(
symbolDefs = emptyMap(),
outputDir = outputDir,
errors = errors ?: ErrorReporterForTests(),
splitWordArrays = false
splitWordArrays = false,
breakpointCpuInstruction = false
)
return compileProgram(args)
}
+2 -2
View File
@@ -24,7 +24,7 @@ compileTestKotlin {
dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.antlr:antlr4-runtime:4.12.0"
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
implementation project(':parser')
}
@@ -36,7 +36,7 @@ configurations.all {
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
srcDir "${project.projectDir}/src"
}
}
}
@@ -429,6 +429,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
override fun visit(addressOf: AddressOf) {
output("&")
addressOf.identifier.accept(this)
addressOf.arrayIndex?.accept(this)
}
override fun visit(inlineAssembly: InlineAssembly) {
+31 -18
View File
@@ -8,6 +8,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.parser.Prog8ANTLRParser
import prog8.parser.Prog8ANTLRParser.*
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
@@ -310,12 +311,19 @@ private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter
}
private fun Prog8ANTLRParser.Assign_targetContext.toAst() : AssignTarget {
val identifier = scoped_identifier()
return when {
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
return when(this) {
is IdentifierTargetContext ->
AssignTarget(scoped_identifier().toAst(), null, null, scoped_identifier().toPosition())
is MemoryTargetContext ->
AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()), toPosition())
is ArrayindexedTargetContext -> {
val ax = arrayindexed()
val arrayvar = ax.scoped_identifier().toAst()
val index = ax.arrayindex().toAst()
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
AssignTarget(null, arrayindexed, null, toPosition())
}
else -> throw FatalAstException("weird assign target node $this")
}
}
@@ -427,6 +435,13 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
}
}
if(arrayindexed()!=null) {
val ax = arrayindexed()
val identifier = ax.scoped_identifier().toAst()
val index = ax.arrayindex().toAst()
return ArrayIndexedExpression(identifier, index, ax.toPosition())
}
if(scoped_identifier()!=null)
return scoped_identifier().toAst()
@@ -448,17 +463,21 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
if(childCount==3 && children[0].text=="(" && children[2].text==")")
return expression(0).toAst() // expression within ( )
if(arrayindexed()!=null)
return arrayindexed().toAst()
if(typecast()!=null)
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition())
if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
if(addressof()!=null) {
val addressOf = addressof()
val identifier = addressOf.scoped_identifier()
val array = addressOf.arrayindexed()
return if(identifier!=null)
AddressOf(addressof().scoped_identifier().toAst(), null, toPosition())
else
AddressOf(array.scoped_identifier().toAst(), array.arrayindex().toAst(), toPosition())
}
throw FatalAstException(text)
}
@@ -497,12 +516,6 @@ private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteral {
}
}
private fun Prog8ANTLRParser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
arrayindex().toAst(),
toPosition())
}
private fun Prog8ANTLRParser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun Prog8ANTLRParser.Scoped_identifierContext.toAst() : IdentifierReference =
@@ -577,7 +590,7 @@ private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
}
private fun Prog8ANTLRParser.UnrollloopContext.toAst(): UnrollLoop {
val iterations = integerliteral().toAst().number.toInt()
val iterations = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
Loaded 100 of 180 files, more files were not shown because too many files have changed in this diff. Show more