Compare commits

...

192 Commits
v8.6.2 ... v8.9

Author SHA1 Message Date
914f19be86 version 8.9 2023-02-12 17:38:13 +01:00
d0b18dec8e shuffle variable sorting around to attempt smaller compiled programs 2023-02-12 17:34:33 +01:00
549c598f51 variables sorted in asm 2023-02-11 14:35:56 +01:00
7b59bc8d12 avoid division by zero if host fs hyperload is used which loads instantly 2023-02-08 01:37:49 +01:00
79d0fb0b52 cx16.numbanks() now returns a word because the result can be >255 2023-02-08 00:51:34 +01:00
edf56d34f8 doc about no conditional compilation, fixes #93
also added a note to MEMTOP about 0 result
2023-02-06 23:36:19 +01:00
6733253826 added printer for Pt Ast tree 2023-02-05 16:42:06 +01:00
86210c4513 clarification 2023-02-01 20:58:40 +01:00
e08da659e5 got rid of PtScopeVarsDecls node, just insert variable nodes directly 2023-01-29 13:25:15 +01:00
e9ec310d8a upgrade to kotlin 1.8.0 2023-01-27 22:14:10 +01:00
19a2791c65 vm target can't use asmsub at all, give better error for that 2023-01-26 01:38:13 +01:00
f1a7d5ecf7 docs 2023-01-26 00:37:30 +01:00
8b05abb80d proper error when attempting to refer to parameters of asmsub by name 2023-01-25 23:41:08 +01:00
9a2df072cc tiny correction 2023-01-24 22:48:44 +01:00
224278e07a correct openjdk-11 sdk setting in project files instead of just 11 2023-01-24 01:49:38 +01:00
74b69e191e restructure keyboardhandler example due to X register bug, discussed in #94 2023-01-24 01:30:57 +01:00
8cda8a727c update vtui example to vtui 1.0 2023-01-24 01:00:21 +01:00
4403e4ed62 optimize node renames 2023-01-22 18:26:37 +01:00
6ee270d9d8 make name a var in new ast to allow cheap renames 2023-01-22 17:10:04 +01:00
44fa309d20 tweak action 2023-01-21 15:29:11 +01:00
58d88f3dd4 github action and update tool docs 2023-01-21 14:47:32 +01:00
e980c23177 github action 2023-01-21 14:25:17 +01:00
75224321bb github action 2023-01-21 14:19:01 +01:00
801af05b20 github action 2023-01-21 14:02:08 +01:00
7611dbbddc fix action 2023-01-21 13:47:09 +01:00
6d40ca15bc github action 2023-01-21 13:39:30 +01:00
32c1c19224 tweak sys.wait() routines on various targets
add warning to docs about FP usage in IRQ
2023-01-20 03:29:10 +01:00
bbf6357222 remove workaround for black cursor at boot as this was recently fixed in the kernal rom. 2023-01-17 23:27:27 +01:00
dc16629c24 todo 2023-01-04 23:57:59 +01:00
3718b9d768 less joins 2023-01-02 02:10:38 +01:00
c25eb088ec redo 8e730ef93d to avoid larger code generated 2023-01-01 23:43:33 +01:00
3feb3e52f8 optimizing scoped names in zeropage 2022-12-31 03:57:51 +01:00
8e730ef93d optimizing scoped names more and fix scoping of identifier names in arrays (pointers) in SymbolTable 2022-12-31 03:20:20 +01:00
e0913a39ab optimizing 2022-12-30 18:50:45 +01:00
7a27fbc001 add params for future changes 2022-12-30 17:43:55 +01:00
ee0dbdad35 don't reshuffle 'start' routine to the top. Fixes zsound examples. 2022-12-30 17:12:01 +01:00
9225f88f89 diskio comments 2022-12-30 15:49:53 +01:00
a04839dd6b vm: add property for custom breakpoint handler 2022-12-30 15:10:13 +01:00
002006517a rewrite bool=bool^1 into bool=not bool 2022-12-29 19:42:38 +01:00
f5b202d438 fix ast type error in float cast to bool 2022-12-28 22:18:21 +01:00
a7df094ff4 don't allow ~ on booleans, also introduce SZ and SNZ instructions in IR to complete the conditional-set instruction list. 2022-12-28 21:19:38 +01:00
1e6fa77633 ir: 4 new instructions to branch on signed <0, >0, <=0, >=0 2022-12-28 13:14:20 +01:00
eb4cff202c removed redundant branch opcodes in IR: BLT(S), BLE(S). Just use swapped BGT(S), BGE(S). 2022-12-28 12:41:05 +01:00
7ee777f405 vm/ir: for loop is now correctly skipped if loopvar>endvar
this is different still in the 6502 codegen, where it wraps around $00!
2022-12-27 18:12:41 +01:00
81bd5c784e don't remove consecutive assigns to IO space location 2022-12-24 18:01:54 +01:00
b526e132a7 better warning + don't remove non-trivial initializer expression for unused variables 2022-12-24 17:22:30 +01:00
1860f66de5 allow "x not in array" as equivalent to "not x in array"
update antlr parsing lib
2022-12-23 17:59:56 +01:00
ded9ada9bc allow "not xx in array" expression in 6502 codegen
fix compiler crash on certain bool to byte casts
2022-12-23 17:07:34 +01:00
d0e6a2eb8b fix compiler crash on hoisting certain vardecls from inner scopes 2022-12-22 18:49:53 +01:00
4e103a1963 making snow example more interesting 2022-12-22 13:04:26 +01:00
475e927178 version 8.8 2022-12-17 23:00:49 +01:00
ca7932c4f0 no longer do return value optimization with tempvar, this caused invalid code sometimes. 2022-12-14 22:33:16 +01:00
8ab47d3321 fix_autostart_square() now preserves X register correctly 2022-12-14 01:07:44 +01:00
def7e87151 fixed silly if-goto expression code in IR codegen where it used too many branching instructions 2022-12-12 22:47:15 +01:00
27568c2bef fixed silly code generated by some NOT-expressions (unused temporary) 2022-12-12 21:57:22 +01:00
0694a187d7 unsigned>0 now optimized into unsigned!=0 2022-12-12 20:37:57 +01:00
832601b36b workaround for black square issue at start 2022-12-11 11:48:41 +01:00
578969c34c optimize redundant rts/bra or rts/jmp generation in when statement 2022-12-10 17:21:15 +01:00
d1d0115aed removed unused option 'keepIR' 2022-12-09 18:44:44 +01:00
c89e6ebfab clarify 2022-12-08 22:21:45 +01:00
ca1089b881 optimized codegen for logical expressions with simple right operand (such as c64.READST() & $40 ) 2022-12-06 20:23:56 +01:00
a1d04f2aad added more $03xx vector definitions to C64/C128/CX16 syslib 2022-12-06 20:23:56 +01:00
bf0604133c fix error in IR for inline asm and BSS vars. 2022-12-04 16:48:44 +01:00
a82b2da16e Fix some FP related assignment issues in 6502 codegen. 2022-12-04 13:03:38 +01:00
f2273c0acc fix several FP rom routine addresses on cx16. 2022-12-03 19:56:54 +01:00
17bedac96c vm: memory is randomized on start instead of 0. P8ir file now has BSS segment. Vm clears BSS vars to 0. 2022-12-03 17:46:06 +01:00
4831fad27a x16 emulators are now launched with PULSE_LATENCY_MSEC=10 env setting to mitigate static noise 2022-12-03 16:19:26 +01:00
5e896cf582 preparing to add Golden RAM 2022-12-03 00:21:31 +01:00
add3491c57 fix possible vardecl issue for prefixed params 2022-11-30 22:56:54 +01:00
f470576822 it's now possible to use symbols that are the same name as 6502 instructions
because these are now prefixed internally before generating assembly.
2022-11-30 18:39:56 +01:00
10760a53a8 optimize cmp word equal/notequal 2022-11-29 20:14:35 +01:00
eee805183c don't overwrite temp vars in complex comparison expressions. Fixes #89 2022-11-29 04:13:25 +01:00
b8fb391022 - ir codegen now allows subroutine having the same name as its block
this is not possible for the 6502 codegen due to 64tass scoping limitation
2022-11-28 21:54:33 +01:00
3c698f1584 fileseek for writing not right now 2022-11-27 21:52:18 +01:00
2fad52d684 the adpcm example can now read wav files directly (so no need anymore to extract the binary frame data from them) 2022-11-27 21:37:40 +01:00
ec64a68a71 fixed compiler crash: unsigned = (-(unsigned as word) as uword) 2022-11-27 17:25:47 +01:00
db55562f6a fixed adpcm playback 2022-11-27 16:36:30 +01:00
d8409a9d2b fix compiler crash: if uwordvar > label 2022-11-26 14:39:03 +01:00
0d0ce6eec1 adpcm plays pcm 2022-11-24 21:03:50 +01:00
483f313eda ir: keep correct child node order in blocks 2022-11-24 01:19:48 +01:00
7b6c742178 fixed diskio.f_read() for small read sizes 2022-11-24 00:23:37 +01:00
d4a35ba6ff got rid of diskio.have_first_byte overhead 2022-11-23 21:53:36 +01:00
68b112837a fix cx16logo.logo() printing correct newlines 2022-11-23 02:25:20 +01:00
e2f20ebf94 fix crash on empty conditional branch statement (if_cc { } ) 2022-11-23 02:14:48 +01:00
f870e4965a added cx16diskio.f_seek() function to seek to a position in an opened file
f_open uses channel 12 now, f_open_w uses 13
2022-11-23 01:48:04 +01:00
7ebcb219d6 void func() now gives warning if func doesn't return a value 2022-11-22 22:54:40 +01:00
c21913a66b ir: keep order of children in block 2022-11-22 02:04:24 +01:00
77e956a29f API change: diskio.list_files doesn't have an internal buffer anymore, you now have to supply a buffer + size yourself. Renamed to list_filenames 2022-11-20 23:27:22 +01:00
08275c406a added chdir/mkdir/rmdir/relabel to cx16diskio 2022-11-20 22:59:44 +01:00
2931e1b87b diskio file lister routines now also put file type (prg, seq, dir) in new diskio.list_filetype variable 2022-11-20 20:22:09 +01:00
153b422496 cx16: retain display mode (composite etc) 2022-11-20 19:19:01 +01:00
0f6a6d6fea attempt to make gfx2 screen mode 0 cleanup more robust on real hardware 2022-11-18 22:53:28 +01:00
91fdb3e2d4 ir: store labels in blocks, but still useless 2022-11-17 00:37:45 +01:00
d8e87bd881 make uword xx = 1<<shift into a word shifting 2022-11-16 01:39:34 +01:00
922033c1b2 main block element order now remains the same as in source 2022-11-16 00:32:00 +01:00
df1793efbf fixed: word << 12 is suddenly an uword (with optimizer on) 2022-11-15 03:00:41 +01:00
836a2700f2 func(x>>1) no longer uses slow stack eval 2022-11-15 02:49:40 +01:00
8f3aaf77a1 fix optimizer hanging on uword xx :: xx >>= 8 / xx=msb(xx) 2022-11-15 01:40:13 +01:00
00c059e5b1 adding cx16/adpcm example 2022-11-15 01:17:28 +01:00
f4f355c74a added cx16/diskspeed example 2022-11-14 17:55:55 +01:00
b465fc5aaf fix bug in word array containment check (prog8_lib.containment_wordarray) that could hang the loop 2022-11-12 23:19:01 +01:00
2d78eaa48d fix gfx2 text color, added cx16 snow example 2022-11-12 22:08:07 +01:00
d08451bccc ir: Block can now contain inline binary 2022-11-12 20:17:23 +01:00
d8e785aed0 ir: fix too greedy chunk removal 2022-11-12 19:56:54 +01:00
267b6f49b5 IRFileReader parses the p8ir file with xml parser 2022-11-12 16:51:20 +01:00
e6688f4b9d clearer error for VM limitation cannot load label address as value 2022-11-12 13:45:02 +01:00
9d7b9771c2 p8ir file format is now valid XML 2022-11-11 23:35:52 +01:00
136a9a39de kotlin 1.7.21 2022-11-10 22:52:07 +01:00
3dcf628fdb fixed subroutine name shadow check 2022-11-10 22:51:37 +01:00
e614e9787a ir: write values as hex into p8ir file 2022-11-08 21:59:05 +01:00
e426fc0922 version 8.7 2022-11-06 22:58:39 +01:00
5d4bfffc7e float.rndseedf() now takes float seed value and is consistent for all CBM compilation targets 2022-11-06 22:53:57 +01:00
207cdaf7a4 fix kefrenbars example (use gfx2 instead of kernal routines) 2022-11-06 17:33:30 +01:00
7315b581ce added gfx2.pget(x,y) to get the pixel color value 2022-11-06 13:40:55 +01:00
38efaae7b2 ir/vm: syscall params in high base register to avoid push/pop 2022-11-06 12:52:09 +01:00
469e042216 vm: replaced prog8_lib.string_compare and others with syscalls 2022-11-04 23:12:13 +01:00
0f1a4b9d8f fixed certain type check error when passing boolean value to ubyte function parameter
fixed virtual machine string comparison syscall
2022-11-03 23:06:03 +01:00
7303c00296 vm: prog8lib.wordarray_contains() fixed 2022-11-03 22:48:47 +01:00
fc55b34d84 ir: fix asmsub multi-value return codegen 2022-11-03 22:29:41 +01:00
6f67fc0e02 ir: get rid of '_' symbol prefix 2022-11-03 21:54:53 +01:00
562d722ad5 codegen: added missing codegen for float array inplace modification 2022-11-03 20:08:46 +01:00
144c1ba3a6 ir: fix float instruction value in formatspec 2022-11-03 19:08:38 +01:00
06b032af91 refactor 2022-11-03 00:20:31 +01:00
3603140114 ir: fix unused code remover 2022-11-02 23:54:52 +01:00
e094785cbd ir: fix unused code remover 2022-11-02 23:16:51 +01:00
e7408224ac ir: remove position tracking from codechunk for now 2022-11-02 22:12:42 +01:00
e67c05c274 ir: fix asmsub contents not appearing in IR file 2022-11-02 20:50:51 +01:00
b22804efaf ir: fix inlineasm linking 2022-10-31 23:59:33 +01:00
890f55f91a fixup compiler internals diagram 2022-10-31 00:39:43 +01:00
cc5fc0b892 Merge branch 'master' into labeledchunks
# Conflicts:
#	examples/test.p8
2022-10-30 23:46:44 +01:00
5efe2b027a ir: fix chunk linkage in optimizer 2022-10-30 23:42:41 +01:00
5b6569d0f9 ir: fix overwriting chunk label 2022-10-30 19:03:02 +01:00
0eda7ac498 vm: don't crash on empty code chunks 2022-10-30 17:05:08 +01:00
a5ef353484 ir: fix memory mapped var as for loop counter 2022-10-30 14:54:47 +01:00
67a36d8d31 more robust 'return' statement checks in subroutines 2022-10-30 14:41:28 +01:00
7cc3cc3990 ir: fix non-code chunk linkage 2022-10-30 12:55:06 +01:00
dc0edc4c2b break also in for 2022-10-29 23:34:59 +02:00
71d2f091e5 Merge pull request #88 from markjreed/fix-mouse_config2
fix: don't ignore shape argument to cx16.mouse_config2
2022-10-29 23:22:14 +02:00
c2f062a391 fix: don't ignore shape argument to cx16.mouse_config2 2022-10-29 17:10:06 -04:00
224f490455 Merge branch 'master' into labeledchunks
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	examples/test.p8
2022-10-29 18:26:09 +02:00
5b35232ab4 fix "fpReg1 out of bounds" crash for vm target for in-place float array assignment. #85 2022-10-29 17:04:39 +02:00
6d6db70e42 remove type widening for bit shifts, to be consistent with other arithmetic operations. Fixes #83 2022-10-29 16:29:41 +02:00
6830e15b4e print warning when bit shifts are too large and result in 0. #83 2022-10-29 15:23:39 +02:00
3f07cad35d remove missing feature from docs 2022-10-29 14:31:40 +02:00
e951340033 BASIC, VICE, C64, zeropage spelling 2022-10-29 14:17:40 +02:00
db8912a735 Kernal spelling 2022-10-29 14:10:11 +02:00
0e297731a3 PETSCII spelling 2022-10-29 14:07:04 +02:00
f20c4f98ac Merge pull request #86 from Frosty-J/docs
Fix typos in documentation
2022-10-29 12:57:55 +02:00
05e60cc7c0 fix array type typo 2022-10-29 12:57:33 +02:00
55b4469767 Merge pull request #87 from Frosty-J/basicsafe
`%zeropage basicsafe` in Hello World
2022-10-29 12:31:28 +02:00
f15516e478 Bracket space 2022-10-29 00:25:54 +01:00
17ceadbadf %zeropage basicsafe in Hello World 2022-10-28 22:49:23 +01:00
8c25b2b316 CommanderX16 -> Commander X16 2022-10-28 22:47:14 +01:00
8b1ae404a3 Commodore-64 -> Commodore 64 2022-10-28 22:45:09 +01:00
13534cd4a9 lowlevel -> low-level 2022-10-28 22:40:36 +01:00
abfb345503 ofcourse -> of course 2022-10-28 22:39:54 +01:00
42ae935496 Various typo fixes 2022-10-28 22:39:15 +01:00
434515d957 fix: array[x] = ~array[x] no longer crashes the codegen 2022-10-27 23:56:38 +02:00
094f7803b7 fix: array[x] = -array[x] no longer crashes the codegen 2022-10-27 23:20:40 +02:00
b0c7bad391 fix: array[x] = -value no longer crashes the codegen 2022-10-27 21:58:37 +02:00
e9a4a905ef preparing to fix the array indexing compiler issue 2022-10-26 23:53:17 +02:00
7b6cd0cfbe cx16.macptr() now has additional argument in the carry flag, to reflect recent X16 kernal api change.
Also now allow bool type for status flag args and returnvalues.
2022-10-26 20:41:10 +02:00
b718b12083 ir/vm fix chunk linkage 2022-10-26 00:12:56 +02:00
cfa7258ff4 various 2022-10-25 23:18:42 +02:00
b70e0a0870 mention syntax highlighting files in the docs 2022-10-25 21:24:38 +02:00
da8eb464b8 add cx16diskio.vload_raw() to load headerless files into vram 2022-10-25 21:12:11 +02:00
8f9d1cfa30 fix regression: indexing pointer variable with word (>255) didn't work anymore since release 8.2 or so 2022-10-24 23:43:47 +02:00
585009ac5c ir: fix syscall numbers and more 2022-10-24 01:57:37 +02:00
30ee65fd14 ir: ensure that block and sub labels are also on the first chunk in said block/sub 2022-10-23 18:54:08 +02:00
76428b16f0 Merge branch 'master' into labeledchunks
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt
#	docs/source/todo.rst
#	examples/test.p8
#	virtualmachine/src/prog8/vm/VirtualMachine.kt
2022-10-23 12:19:02 +02:00
0d7b14e2d8 fix crash when assigning certain memory read to word variable. Fixes #82 2022-10-23 11:57:23 +02:00
a9d19d02b3 helpful error for programs still using the old builtin rnd() and rndw() 2022-10-22 22:36:44 +02:00
adcbe55307 replaced integer RNG with smaller and faster routine. 2022-10-22 22:01:57 +02:00
aa99a7df64 seed info 2022-10-22 17:54:24 +02:00
00afa1ce52 ir: replace RND opcode by syscalls 2022-10-22 17:20:46 +02:00
e94bf4c63c replace rnd()/rndw() builtin functions by regular routines in math module 2022-10-22 17:02:43 +02:00
ec5adffdc2 rnd()/rndf() routines can now be seeded with new rndseed()/rndseedf() routines. fixes #80 2022-10-22 13:34:22 +02:00
733c17ad3a improve docs on if syntax. fixes #81 2022-10-19 23:53:15 +02:00
53b0b562e6 fix check for routine that returns multiple values but in status bit. Fixes #79 2022-10-19 23:23:49 +02:00
fabae6e970 ir: fix handling of labeled chunks 2022-10-16 23:53:17 +02:00
a9f9c40d8a ir: fix handling of labeled chunks 2022-10-13 00:56:44 +02:00
6fc89607d3 ir: moving to labeled chunks, no more IRLabel nodes 2022-10-07 00:34:56 +02:00
2340760f53 rename 2022-10-04 22:54:14 +02:00
39d6d2857e ir: change inline binary a bit 2022-10-04 00:57:08 +02:00
7b722a0001 ir: fix count register uses 2022-10-04 00:25:55 +02:00
e7682119e0 ir: count register uses 2022-10-02 15:56:06 +02:00
af6be44676 ir: adding register usage inspections
fix compiler problems with untrimmed inlined asm, and when only a single return statement is present in a subroutine
2022-09-30 20:25:00 +02:00
5a8f97a0b6 ir: adding last missing features to be able to encode all of Prog8 2022-09-30 16:01:00 +02:00
0d4dd385b8 added '%ir' to write inline IR code, '%asm' is now only for real 6502 assembly.
(%ir is probably only used in the library modules for the virtual machine target)
2022-09-30 15:12:26 +02:00
94f0f3e966 ir: join code chunks 2022-09-30 02:47:33 +02:00
43e31765e5 kotlin 1.7.20 2022-09-29 18:41:20 +02:00
7c1bdfe713 ir: uninitialized vars remain empty, bss section classifier (unused for now as there are no segements yet) 2022-09-28 16:56:50 +02:00
191 changed files with 8734 additions and 4350 deletions

31
.github/workflows/all-ci.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Build and Test the Prog8 compiler
on:
push:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install 64tass
run: sudo apt-get update -y && sudo apt-get install -y 64tass
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 11
distribution: adopt
- name: Build and test with Gradle
run: ./gradlew build shadowJar --no-daemon
- name: Create compiler shadowJar artifact
uses: actions/upload-artifact@v3
with:
name: prog8-compiler-jar-zipped
path: compiler/build/libs/*-all.jar

View File

@ -1,17 +1,17 @@
<component name="libraryTable"> <component name="libraryTable">
<library name="antlr.antlr4" type="repository"> <library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.10.1"> <properties maven-id="org.antlr:antlr4:4.11.1">
<exclude> <exclude>
<dependency maven-id="com.ibm.icu:icu4j" /> <dependency maven-id="com.ibm.icu:icu4j" />
</exclude> </exclude>
</properties> </properties>
<CLASSES> <CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.10.1/antlr4-4.10.1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.11.1/antlr4-4.11.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.10.1/antlr4-runtime-4.10.1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.11.1/antlr4-runtime-4.11.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.3/ST4-4.3.3.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
</CLASSES> </CLASSES>
<JAVADOC /> <JAVADOC />
<SOURCES /> <SOURCES />

2
.idea/misc.xml generated
View File

@ -19,7 +19,7 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" /> <type id="Python" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

View File

@ -78,6 +78,9 @@ It's handy to have an emulator (or a real machine perhaps!) to run the programs
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target, of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target. and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
**Syntax highlighting:** for a few different editors, syntax highlighting definition files are provided.
Look in the [syntax-files](https://github.com/irmen/prog8/tree/master/syntax-files) directory in the github repository to find them.
Example code Example code
------------ ------------

View File

@ -1,8 +1,7 @@
package prog8 package prog8.code
/** /**
* By convention, the right side of an `Either` is used to hold successful values. * By convention, the right side of an `Either` is used to hold successful values.
*
*/ */
sealed class Either<out L, out R> { sealed class Either<out L, out R> {

View File

@ -8,16 +8,13 @@ import prog8.code.core.*
* (blocks, subroutines, variables (all types), memoryslabs, and labels). * (blocks, subroutines, variables (all types), memoryslabs, and labels).
*/ */
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) { class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
fun print() = printIndented(0)
override fun printProperties() { }
/** /**
* The table as a flat mapping of scoped names to the StNode. * The table as a flat mapping of scoped names to the StNode.
* This gives the fastest lookup possible (no need to traverse tree nodes) * This gives the fastest lookup possible (no need to traverse tree nodes)
*/ */
val flat: Map<List<String>, StNode> by lazy {
val result = mutableMapOf<List<String>, StNode>() val flat: Map<String, StNode> by lazy {
val result = mutableMapOf<String, StNode>()
fun flatten(node: StNode) { fun flatten(node: StNode) {
result[node.scopedName] = node result[node.scopedName] = node
node.children.values.forEach { flatten(it) } node.children.values.forEach { flatten(it) }
@ -58,7 +55,7 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null } children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
} }
override fun lookup(scopedName: List<String>) = flat[scopedName] override fun lookup(scopedName: String) = flat[scopedName]
} }
@ -85,38 +82,18 @@ open class StNode(val name: String,
lateinit var parent: StNode lateinit var parent: StNode
val scopedName: List<String> by lazy { val scopedName: String by lazy { scopedNameList.joinToString(".") }
if(type== StNodeType.GLOBAL)
emptyList()
else
parent.scopedName + name
}
fun lookup(name: String) = open fun lookup(scopedName: String) =
lookupUnqualified(name) lookup(scopedName.split('.'))
open fun lookup(scopedName: List<String>) =
if(scopedName.size>1) lookupQualified(scopedName) else lookupUnqualified(scopedName[0])
fun lookupOrElse(name: String, default: () -> StNode) =
lookupUnqualified(name) ?: default()
fun lookupOrElse(scopedName: List<String>, default: () -> StNode) =
lookup(scopedName) ?: default()
private fun lookupQualified(scopedName: List<String>): StNode? { fun lookupUnscopedOrElse(name: String, default: () -> StNode) =
// a scoped name refers to a name in another namespace, and always stars from the root. lookupUnscoped(name) ?: default()
var node = this
while(node.type!= StNodeType.GLOBAL)
node = node.parent
for(name in scopedName) { fun lookupOrElse(scopedName: String, default: () -> StNode): StNode =
if(name in node.children) lookup(scopedName.split('.')) ?: default()
node = node.children.getValue(name)
else
return null
}
return node
}
private fun lookupUnqualified(name: String): StNode? { fun lookupUnscoped(name: String): StNode? {
// first consider the builtin functions // first consider the builtin functions
var globalscope = this var globalscope = this
while(globalscope.type!= StNodeType.GLOBAL) while(globalscope.type!= StNodeType.GLOBAL)
@ -138,37 +115,37 @@ open class StNode(val name: String,
} }
} }
fun printIndented(indent: Int) {
print(" ".repeat(indent))
when(type) {
StNodeType.GLOBAL -> print("SYMBOL-TABLE:")
StNodeType.BLOCK -> print("(B) ")
StNodeType.SUBROUTINE -> print("(S) ")
StNodeType.LABEL -> print("(L) ")
StNodeType.STATICVAR -> print("(V) ")
StNodeType.MEMVAR -> print("(M) ")
StNodeType.MEMORYSLAB -> print("(MS) ")
StNodeType.CONSTANT -> print("(C) ")
StNodeType.BUILTINFUNC -> print("(F) ")
StNodeType.ROMSUB -> print("(R) ")
}
printProperties()
println()
children.forEach { (_, node) -> node.printIndented(indent+1) }
}
open fun printProperties() {
print("$name ")
}
fun add(child: StNode) { fun add(child: StNode) {
children[child.name] = child children[child.name] = child
child.parent = this child.parent = this
} }
private val scopedNameList: List<String> by lazy {
if(type== StNodeType.GLOBAL)
emptyList()
else
parent.scopedNameList + name
}
private fun lookup(scopedName: List<String>): StNode? {
// a scoped name refers to a name in another namespace, and always stars from the root.
var node = this
while(node.type!= StNodeType.GLOBAL)
node = node.parent
for(name in scopedName) {
if(name in node.children)
node = node.children.getValue(name)
else
return null
}
return node
}
} }
class StStaticVariable(name: String, class StStaticVariable(name: String,
val dt: DataType, val dt: DataType,
val bss: Boolean,
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationStringValue: StString?, val onetimeInitializationStringValue: StString?,
val onetimeInitializationArrayValue: StArray?, val onetimeInitializationArrayValue: StArray?,
@ -177,10 +154,19 @@ class StStaticVariable(name: String,
position: Position) : StNode(name, StNodeType.STATICVAR, position) { position: Position) : StNode(name, StNodeType.STATICVAR, position) {
init { init {
if(bss) {
require(onetimeInitializationNumericValue==null)
require(onetimeInitializationStringValue==null)
require(onetimeInitializationArrayValue.isNullOrEmpty())
} else {
require(onetimeInitializationNumericValue!=null ||
onetimeInitializationStringValue!=null ||
onetimeInitializationArrayValue!=null)
}
if(length!=null) { if(length!=null) {
require(onetimeInitializationNumericValue == null) require(onetimeInitializationNumericValue == null)
if(onetimeInitializationArrayValue!=null) if(onetimeInitializationArrayValue!=null)
require(length == onetimeInitializationArrayValue.size) require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
} }
if(onetimeInitializationNumericValue!=null) if(onetimeInitializationNumericValue!=null)
require(dt in NumericDatatypes) require(dt in NumericDatatypes)
@ -191,18 +177,11 @@ class StStaticVariable(name: String,
require(length == onetimeInitializationStringValue.first.length+1) require(length == onetimeInitializationStringValue.first.length+1)
} }
} }
override fun printProperties() {
print("$name dt=$dt zpw=$zpwish")
}
} }
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) : class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
StNode(name, StNodeType.CONSTANT, position) { StNode(name, StNodeType.CONSTANT, position) {
override fun printProperties() {
print("$name dt=$dt value=$value")
}
} }
@ -212,9 +191,6 @@ class StMemVar(name: String,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
position: Position) : position: Position) :
StNode(name, StNodeType.MEMVAR, position) { StNode(name, StNodeType.MEMVAR, position) {
override fun printProperties() {
print("$name dt=$dt address=${address.toHex()}")
}
} }
class StMemorySlab( class StMemorySlab(
@ -224,16 +200,10 @@ class StMemorySlab(
position: Position position: Position
): ):
StNode(name, StNodeType.MEMORYSLAB, position) { StNode(name, StNodeType.MEMORYSLAB, position) {
override fun printProperties() {
print("$name size=$size align=$align")
}
} }
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) : class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
StNode(name, StNodeType.SUBROUTINE, position) { StNode(name, StNodeType.SUBROUTINE, position) {
override fun printProperties() {
print(name)
}
} }
@ -243,15 +213,12 @@ class StRomSub(name: String,
val returns: List<RegisterOrStatusflag>, val returns: List<RegisterOrStatusflag>,
position: Position) : position: Position) :
StNode(name, StNodeType.ROMSUB, position) { StNode(name, StNodeType.ROMSUB, position) {
override fun printProperties() {
print("$name address=${address.toHex()}")
}
} }
class StSubroutineParameter(val name: String, val type: DataType) class StSubroutineParameter(val name: String, val type: DataType)
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType) class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
class StArrayElement(val number: Double?, val addressOf: List<String>?) class StArrayElement(val number: Double?, val addressOfSymbol: String?)
typealias StString = Pair<String, Encoding> typealias StString = Pair<String, Encoding>
typealias StArray = List<StArrayElement> typealias StArray = List<StArrayElement>

View File

@ -1,6 +1,8 @@
package prog8.code.ast package prog8.code.ast
import prog8.code.core.* import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.core.Position
import java.nio.file.Path import java.nio.file.Path
// New simplified AST for the code generator. // New simplified AST for the code generator.
@ -42,15 +44,17 @@ class PtNodeGroup : PtNode(Position.DUMMY) {
} }
abstract class PtNamedNode(val name: String, position: Position): PtNode(position) { sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
val scopedName: List<String> by lazy { // Note that as an exception, the 'name' is not read-only
// but a var. This is to allow for cheap node renames.
val scopedName: String by lazy {
var namedParent: PtNode = this.parent var namedParent: PtNode = this.parent
if(namedParent is PtProgram) if(namedParent is PtProgram)
listOf(name) name
else { else {
while (namedParent !is PtNamedNode) while (namedParent !is PtNamedNode)
namedParent = namedParent.parent namedParent = namedParent.parent
namedParent.scopedName + name namedParent.scopedName + "." + name
} }
} }
} }
@ -96,8 +100,13 @@ class PtBlock(name: String,
} }
class PtInlineAssembly(val assembly: String, position: Position) : PtNode(position) { class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
override fun printProperties() {} override fun printProperties() {}
init {
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
}
} }
@ -125,12 +134,6 @@ class PtNop(position: Position): PtNode(position) {
} }
class PtScopeVarsDecls(position: Position): PtNode(position) {
override fun printProperties() {}
}
// find the parent node of a specific type or interface // find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc.) // (useful to figure out in what namespace/block something is defined, etc.)
inline fun <reified T> findParentNode(node: PtNode): T? { inline fun <reified T> findParentNode(node: PtNode): T? {

View File

@ -33,7 +33,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
is PtNumber -> other is PtNumber && other.type==type && other.number==number is PtNumber -> other is PtNumber && other.type==type && other.number==number
@ -108,7 +108,7 @@ class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, posit
} }
class PtFunctionCall(val functionName: List<String>, class PtFunctionCall(val name: String,
val void: Boolean, val void: Boolean,
type: DataType, type: DataType,
position: Position) : PtExpression(type, position) { position: Position) : PtExpression(type, position) {
@ -120,14 +120,14 @@ class PtFunctionCall(val functionName: List<String>,
val args: List<PtExpression> val args: List<PtExpression>
get() = children.map { it as PtExpression } get() = children.map { it as PtExpression }
override fun printProperties() { override fun printProperties() {
print("${functionName.joinToString(".")} void=$void") print("$name void=$void")
} }
} }
class PtIdentifier(val ref: List<String>, val targetName: List<String>, type: DataType, position: Position) : PtExpression(type, position) { class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position) {
override fun printProperties() { override fun printProperties() {
print("$ref --> $targetName $type") print("$name $type")
} }
} }

View File

@ -0,0 +1,153 @@
package prog8.code.ast
import prog8.code.core.*
/**
* Produces readable text from a [PtNode] (AST node, usually starting with PtProgram as root),
* passing it as a String to the specified receiver function.
*/
fun printAst(root: PtNode, output: (text: String) -> Unit) {
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String {
return when(node) {
is PtAssignTarget -> ""
is PtAssignment -> "<assign>"
is PtBreakpoint -> "%breakpoint"
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
is PtAddressOf -> "&"
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {
val str = if(node.void) "void " else ""
str + node.name + "()"
}
is PtContainmentCheck -> "in"
is PtFunctionCall -> {
val str = if(node.void) "void " else ""
str + node.name + "()"
}
is PtIdentifier -> "${node.name} ${type(node.type)}"
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
is PtMemoryByte -> "@()"
is PtNumber -> "${node.number.toHex()} ${type(node.type)}"
is PtPrefix -> node.operator
is PtRange -> "<range>"
is PtString -> "\"${node.value.escape()}\""
is PtTypeCast -> "as ${node.type.name.lowercase()}"
is PtForLoop -> "for"
is PtIfElse -> "ifelse"
is PtIncludeBinary -> "%incbin '${node.file}', ${node.offset}, ${node.length}"
is PtInlineAssembly -> {
if(node.isIR)
"%ir {{ ...${node.assembly.length} characters... }}"
else
"%asm {{ ...${node.assembly.length} characters... }}"
}
is PtJump -> {
if(node.identifier!=null)
"goto ${node.identifier.name}"
else if(node.address!=null)
"goto ${node.address.toHex()}"
else if(node.generatedLabel!=null)
"goto ${node.generatedLabel}"
else
"???"
}
is PtAsmSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
val returns = if (node.returnTypes.isEmpty()) "" else (if (node.returnTypes.size == 1) "-> ${node.returnTypes[0].name.lowercase()}" else "-> ${node.returnTypes.map { it.name.lowercase() }}")
val str = if (node.inline) "inline " else ""
if(node.address==null) {
str + "asmsub ${node.name}($params) $clobbers $returns"
} else {
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
}
}
is PtBlock -> {
val addr = if(node.address==null) "" else "@${node.address?.toHex()}"
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
"\nblock '${node.name}' $addr $align"
}
is PtConstant -> {
val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString()
"const ${node.type.name.lowercase()} ${node.name} = $value"
}
is PtLabel -> "${node.name}:"
is PtMemMapped -> {
if(node.type in ArrayDatatypes) {
val arraysize = if(node.arraySize==null) "" else node.arraySize.toString()
val eltType = ArrayToElementTypes.getValue(node.type)
"&${eltType.name.lowercase()}[$arraysize] ${node.name} = ${node.address.toHex()}"
} else {
"&${node.type.name.lowercase()} ${node.name} = ${node.address.toHex()}"
}
}
is PtSub -> {
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
var str = if(node.inline) "inline " else ""
str += "sub ${node.name}($params) "
if(node.returntype!=null)
str += "-> ${node.returntype.name.lowercase()}"
str
}
is PtVariable -> {
val str = if(node.arraySize!=null) {
val eltType = ArrayToElementTypes.getValue(node.type)
"${eltType.name.lowercase()}[${node.arraySize}] ${node.name}"
}
else if(node.type in ArrayDatatypes) {
val eltType = ArrayToElementTypes.getValue(node.type)
"${eltType.name.lowercase()}[] ${node.name}"
}
else
"${node.type.name.lowercase()} ${node.name}"
if(node.value!=null)
str + " = " + txt(node.value!!)
else
str
}
is PtNodeGroup -> "<group>"
is PtNop -> "nop"
is PtPostIncrDecr -> "<post> ${node.operator}"
is PtProgram -> "PROGRAM ${node.name}"
is PtRepeatLoop -> "repeat"
is PtReturn -> "return"
is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}"
is PtWhen -> "when"
is PtWhenChoice -> {
if(node.isElse)
"else"
else
"->"
}
else -> throw InternalCompilerException("unrecognised ast node $node")
}
}
if(root is PtProgram) {
output(txt(root))
root.children.forEach {
walkAst(it) { node, depth ->
val txt = txt(node)
if(txt.isNotEmpty())
output(" ".repeat(depth) + txt(node))
}
}
} else {
walkAst(root) { node, depth ->
val txt = txt(node)
if(txt.isNotEmpty())
output(" ".repeat(depth) + txt(node))
}
}
}
fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Unit) {
fun recurse(node: PtNode, depth: Int) {
act(node, depth)
node.children.forEach { recurse(it, depth+1) }
}
recurse(root, 0)
}

View File

@ -66,7 +66,7 @@ class PtAssignment(position: Position) : PtNode(position) {
} }
false false
} }
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.name==source.name
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
@ -74,7 +74,7 @@ class PtAssignment(position: Position) : PtNode(position) {
is PtPrefix -> { is PtPrefix -> {
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value) (target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
|| ||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName) (target is PtIdentifier && (source.value as? PtIdentifier)?.name==target.name)
} }
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
is PtBinaryExpression -> is PtBinaryExpression ->
@ -202,7 +202,7 @@ class PtConstant(name: String, val type: DataType, val value: Double, position:
} }
class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) { class PtMemMapped(name: String, val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
override fun printProperties() { override fun printProperties() {
print("&$type $name = ${address.toHex()}") print("&$type $name = ${address.toHex()}")
} }

View File

@ -20,7 +20,6 @@ class CompilationOptions(val output: OutputType,
var asmQuiet: Boolean = false, var asmQuiet: Boolean = false,
var asmListfile: Boolean = false, var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false, var experimentalCodegen: Boolean = false,
var keepIR: Boolean = false,
var evalStackBaseAddress: UInt? = null, var evalStackBaseAddress: UInt? = null,
var outputDir: Path = Path(""), var outputDir: Path = Path(""),
var symbolDefs: Map<String, String> = emptyMap() var symbolDefs: Map<String, String> = emptyMap()

View File

@ -17,11 +17,11 @@ interface IMachineDefinition {
var ESTACK_HI: UInt var ESTACK_HI: UInt
val PROGRAM_LOAD_ADDRESS : UInt val PROGRAM_LOAD_ADDRESS : UInt
val opcodeNames: Set<String>
var zeropage: Zeropage
val cpu: CpuType val cpu: CpuType
var zeropage: Zeropage
var golden: GoldenRam
fun initializeZeropage(compilerOptions: CompilationOptions) fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): String fun getFloatAsmBytes(num: Number): String
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
@ -31,5 +31,7 @@ interface IMachineDefinition {
require(evalStackBaseAddress and 255u == 0u) require(evalStackBaseAddress and 255u == 0u)
ESTACK_LO = evalStackBaseAddress ESTACK_LO = evalStackBaseAddress
ESTACK_HI = evalStackBaseAddress + 256u ESTACK_HI = evalStackBaseAddress + 256u
require(ESTACK_LO !in golden.region && ESTACK_HI !in golden.region) { "user-set ESTACK can't be in GOLDEN ram" }
} }
} }

View File

@ -5,21 +5,31 @@ import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
class ZeropageAllocationError(message: String) : Exception(message) class MemAllocationError(message: String) : Exception(message)
abstract class Zeropage(protected val options: CompilationOptions) { abstract class MemoryAllocator(protected val options: CompilationOptions) {
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
abstract fun allocate(name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError>
}
abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
abstract val SCRATCH_B1 : UInt // temp storage for a single byte abstract val SCRATCH_B1 : UInt // temp storage for a single byte
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1 abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
data class ZpAllocation(val address: UInt, val dt: DataType, val size: Int)
// the variables allocated into Zeropage. // the variables allocated into Zeropage.
// name (scoped) ==> pair of address to (Datatype + bytesize) // name (scoped) ==> pair of address to (Datatype + bytesize)
val allocatedVariables = mutableMapOf<List<String>, ZpAllocation>() val allocatedVariables = mutableMapOf<List<String>, VarAllocation>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations. val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
@ -41,17 +51,16 @@ abstract class Zeropage(protected val options: CompilationOptions) {
return free.windowed(2).any { it[0] == it[1] - 1u } return free.windowed(2).any { it[0] == it[1] - 1u }
} }
fun allocate(name: List<String>, override fun allocate(name: List<String>,
datatype: DataType, datatype: DataType,
numElements: Int?, numElements: Int?,
position: Position?, position: Position?,
errors: IErrorReporter errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
): Result<Pair<UInt, Int>, ZeropageAllocationError> {
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"} require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
if(options.zeropage== ZeropageType.DONTUSE) if(options.zeropage== ZeropageType.DONTUSE)
return Err(ZeropageAllocationError("zero page usage has been disabled")) return Err(MemAllocationError("zero page usage has been disabled"))
val size: Int = val size: Int =
when (datatype) { when (datatype) {
@ -72,9 +81,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
else else
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY) errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
memsize memsize
} else return Err(ZeropageAllocationError("floating point option not enabled")) } else return Err(MemAllocationError("floating point option not enabled"))
} }
else -> return Err(ZeropageAllocationError("cannot put datatype $datatype in zeropage")) else -> throw MemAllocationError("weird dt")
} }
synchronized(this) { synchronized(this) {
@ -82,18 +91,18 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(size==1) { if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if(oneSeparateByteFree(candidate)) if(oneSeparateByteFree(candidate))
return Ok(Pair(makeAllocation(candidate, 1, datatype, name), 1)) return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
} }
return Ok(Pair(makeAllocation(free[0], 1, datatype, name), 1)) return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
} }
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if (sequentialFree(candidate, size)) if (sequentialFree(candidate, size))
return Ok(Pair(makeAllocation(candidate, size, datatype, name), size)) return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
} }
} }
} }
return Err(ZeropageAllocationError("no more free space in ZP to allocate $size sequential bytes")) return Err(MemAllocationError("no more free space in ZP to allocate $size sequential bytes"))
} }
private fun reserve(range: UIntRange) = free.removeAll(range) private fun reserve(range: UIntRange) = free.removeAll(range)
@ -103,9 +112,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
free.removeAll(address until address+size.toUInt()) free.removeAll(address until address+size.toUInt())
if(name.isNotEmpty()) { if(name.isNotEmpty()) {
allocatedVariables[name] = when(datatype) { allocatedVariables[name] = when(datatype) {
in NumericDatatypes -> ZpAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments in NumericDatatypes -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
DataType.STR -> ZpAllocation(address, datatype, size) DataType.STR -> VarAllocation(address, datatype, size)
in ArrayDatatypes -> ZpAllocation(address, datatype, size) in ArrayDatatypes -> VarAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt") else -> throw AssemblyError("invalid dt")
} }
} }
@ -120,3 +129,37 @@ abstract class Zeropage(protected val options: CompilationOptions) {
abstract fun allocateCx16VirtualRegisters() abstract fun allocateCx16VirtualRegisters()
} }
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
private var nextLocation: UInt = region.first
override fun allocate(
name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
val size: Int =
when (datatype) {
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
DataType.STR, in ArrayDatatypes -> {
options.compTarget.memorySize(datatype, numElements!!)
}
DataType.FLOAT -> {
if (options.floats) {
options.compTarget.memorySize(DataType.FLOAT)
} else return Err(MemAllocationError("floating point option not enabled"))
}
else -> throw MemAllocationError("weird dt")
}
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
val result = Ok(VarAllocation(nextLocation, datatype, size))
nextLocation += size.toUInt()
result
} else
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
}
}

View File

@ -1,7 +1,6 @@
package prog8.code.target.atari package prog8.code.target.atari
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import java.nio.file.Path import java.nio.file.Path
@ -19,6 +18,7 @@ class AtariMachineDefinition: IMachineDefinition {
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number") override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
@ -57,9 +57,8 @@ class AtariMachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
override fun initializeZeropage(compilerOptions: CompilationOptions) { override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = AtariZeropage(compilerOptions) zeropage = AtariZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
} }
override val opcodeNames = normal6502instructions
} }

View File

@ -1,7 +1,6 @@
package prog8.code.target.c128 package prog8.code.target.c128
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import prog8.code.target.cbm.Mflpt5 import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path import java.nio.file.Path
@ -18,8 +17,8 @@ class C128MachineDefinition: IMachineDefinition {
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@ -47,9 +46,8 @@ class C128MachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) { override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions) zeropage = C128Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
} }
override val opcodeNames = normal6502instructions
} }

View File

@ -18,8 +18,8 @@ class C64MachineDefinition: IMachineDefinition {
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@ -55,22 +55,9 @@ class C64MachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) { override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions) zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0xc000u until ESTACK_LO)
} }
override val opcodeNames = normal6502instructions
} }
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
internal val normal6502instructions = setOf(
"adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")

View File

@ -83,12 +83,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02). // The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) { for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15 allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+reg*2).toUInt()) free.remove((4+reg*2).toUInt())
free.remove((5+reg*2).toUInt()) free.remove((5+reg*2).toUInt())
} }

View File

@ -17,8 +17,8 @@ class CX16MachineDefinition: IMachineDefinition {
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> { override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
@ -50,28 +50,16 @@ class CX16MachineDefinition: IMachineDefinition {
println("\nStarting Commander X16 emulator $emulator...") println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO() val processb = ProcessBuilder(cmdline).inheritIO()
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
val process: Process = processb.start() val process: Process = processb.start()
process.waitFor() process.waitFor()
} }
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun initializeZeropage(compilerOptions: CompilationOptions) { override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions) zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, 0x0600u until 0x0800u)
} }
// 65c02 opcodes, these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "inx", "iny", "jmp", "jsr",
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
"sec", "sed", "sei",
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
"rmb", "smb", "stp", "wai")
} }

View File

@ -58,12 +58,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well. // However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
for(reg in 0..15) { for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15 allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
} }
} }
} }

View File

@ -1,9 +1,6 @@
package prog8.code.target.virtual package prog8.code.target.virtual
import prog8.code.core.CompilationOptions import prog8.code.core.*
import prog8.code.core.CpuType
import prog8.code.core.IMachineDefinition
import prog8.code.core.Zeropage
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.isReadable import kotlin.io.path.isReadable
import kotlin.io.path.name import kotlin.io.path.name
@ -20,8 +17,8 @@ class VirtualMachineDefinition: IMachineDefinition {
override var ESTACK_LO = 0u // not actually used override var ESTACK_LO = 0u // not actually used
override var ESTACK_HI = 0u // not actually used override var ESTACK_HI = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var zeropage: Zeropage // not actually used override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number") override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
@ -47,11 +44,9 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = false override fun isIOAddress(address: UInt): Boolean = false
override fun initializeZeropage(compilerOptions: CompilationOptions) {} override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
override val opcodeNames = emptySet<String>()
} }
interface IVirtualMachineRunner { interface IVirtualMachineRunner {
fun runProgram(irSource: CharSequence) fun runProgram(irSource: String)
} }

View File

@ -100,13 +100,13 @@ class AsmGen(internal val program: Program,
fun asmSymbolName(identifier: IdentifierReference) = asmSymbolName(identifier.nameInSource) fun asmSymbolName(identifier: IdentifierReference) = asmSymbolName(identifier.nameInSource)
fun asmVariableName(identifier: IdentifierReference) = asmVariableName(identifier.nameInSource) fun asmVariableName(identifier: IdentifierReference) = asmVariableName(identifier.nameInSource)
internal fun getTempVarName(dt: DataType): List<String> { internal fun getTempVarName(dt: DataType): String {
return when(dt) { return when(dt) {
DataType.UBYTE -> listOf("cx16", "r9L") DataType.UBYTE -> "cx16.r9L"
DataType.BYTE -> listOf("cx16", "r9sL") DataType.BYTE -> "cx16.r9sL"
DataType.UWORD -> listOf("cx16", "r9") DataType.UWORD -> "cx16.r9"
DataType.WORD -> listOf("cx16", "r9s") DataType.WORD -> "cx16.r9s"
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8 DataType.FLOAT -> TODO("no temporary float var available")
else -> throw FatalAstException("invalid dt $dt") else -> throw FatalAstException("invalid dt $dt")
} }
} }
@ -375,7 +375,7 @@ class AsmGen(internal val program: Program,
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5) require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
out( out(
""" """
lda $indexName lda $indexName
@ -406,7 +406,7 @@ class AsmGen(internal val program: Program,
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5) require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
out( out(
""" """
lda $indexName lda $indexName
@ -605,7 +605,7 @@ class AsmGen(internal val program: Program,
} }
private fun repeatWordCount(count: Int, stmt: RepeatLoop) { private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
require(count in 257..65535) require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
val repeatLabel = program.makeLabel("repeat") val repeatLabel = program.makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) { if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt) val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
@ -667,7 +667,7 @@ $repeatLabel lda $counterVar
} }
private fun repeatByteCount(count: Int, stmt: RepeatLoop) { private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
require(count in 2..256) require(count in 2..256) { "invalid repeat count ${stmt.position}" }
val repeatLabel = program.makeLabel("repeat") val repeatLabel = program.makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) { if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt) val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
@ -794,7 +794,7 @@ $repeatLabel lda $counterVar
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty()) if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
throw AssemblyError("only else part contains code, shoud have been switched already") throw AssemblyError("only else part contains code, shoud have been switched already")
val jump = stmt.truepart.statements.first() as? Jump val jump = stmt.truepart.statements.firstOrNull() as? Jump
if(jump!=null) { if(jump!=null) {
// branch with only a jump (goto) // branch with only a jump (goto)
val instruction = branchInstruction(stmt.condition, false) val instruction = branchInstruction(stmt.condition, false)
@ -812,11 +812,13 @@ $repeatLabel lda $counterVar
translate(stmt.elsepart) translate(stmt.elsepart)
} else { } else {
if(stmt.elsepart.isEmpty()) { if(stmt.elsepart.isEmpty()) {
val instruction = branchInstruction(stmt.condition, true) if(stmt.truepart.isNotEmpty()) {
val elseLabel = program.makeLabel("branch_else") val instruction = branchInstruction(stmt.condition, true)
out(" $instruction $elseLabel") val elseLabel = program.makeLabel("branch_else")
translate(stmt.truepart) out(" $instruction $elseLabel")
out(elseLabel) translate(stmt.truepart)
out(elseLabel)
}
} else { } else {
val instruction = branchInstruction(stmt.condition, true) val instruction = branchInstruction(stmt.condition, true)
val elseLabel = program.makeLabel("branch_else") val elseLabel = program.makeLabel("branch_else")
@ -909,8 +911,7 @@ $repeatLabel lda $counterVar
} }
private fun translate(asm: InlineAssembly) { private fun translate(asm: InlineAssembly) {
val assembly = asm.assembly.trimEnd().trimStart('\r', '\n') assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
assemblyLines.add(assembly)
} }
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair { internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
@ -1069,11 +1070,20 @@ $repeatLabel lda $counterVar
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second) val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA) if(saveA)
out(" pha") out(" pha")
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(ptrAndIndex.second.isSimple) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y) assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if(saveA) assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" pla") if(saveA)
out(" sta (P8ZP_SCRATCH_W2),y") out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
} else {
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, true)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
} }
} else { } else {
if(pointervar!=null && isZpVar(pointervar)) { if(pointervar!=null && isZpVar(pointervar)) {
@ -1081,9 +1091,16 @@ $repeatLabel lda $counterVar
out(" lda (${asmSymbolName(pointervar)}),y") out(" lda (${asmSymbolName(pointervar)}),y")
} else { } else {
// copy the pointer var to zp first // copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(ptrAndIndex.second.isSimple) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y) assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
out(" lda (P8ZP_SCRATCH_W2),y") assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
} else {
pushCpuStack(DataType.UBYTE, ptrAndIndex.second)
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
out(" lda (P8ZP_SCRATCH_W2),y")
}
} }
} }
return true return true
@ -1577,8 +1594,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -1612,8 +1635,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -1649,8 +1678,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code)) if(wordJumpForSimpleRightOperands(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.AY) assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel") return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel")
} }
@ -1688,8 +1724,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code)) if(wordJumpForSimpleRightOperands(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.AY) assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel") return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
} }
@ -1728,8 +1771,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -1765,8 +1814,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -1808,8 +1863,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code)) if(wordJumpForSimpleRightOperands(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.AY) assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2") return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2")
} }
@ -1852,8 +1914,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleLeftOperand(left, right, ::code)) if(wordJumpForSimpleLeftOperand(left, right, ::code))
return return
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(right.isSimple) {
assignExpressionToRegister(right, RegisterOrPair.AY) assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel") return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
} }
@ -1893,8 +1962,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -1930,8 +2005,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -1975,8 +2056,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code)) if(wordJumpForSimpleRightOperands(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.AY) assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel") return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
} }
@ -2023,8 +2111,15 @@ $repeatLabel lda $counterVar
return code(asmVariableName(left)) return code(asmVariableName(left))
} }
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(right.isSimple) {
assignExpressionToRegister(right, RegisterOrPair.AY) assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel") return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
} }
@ -2060,8 +2155,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -2094,8 +2195,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE, null)
out(" pla")
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -2130,8 +2237,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code)) if(wordJumpForSimpleRightOperands(left, right, ::code))
return return
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(right.isSimple) {
assignExpressionToRegister(right, RegisterOrPair.AY) assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel") return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
} }
@ -2169,8 +2283,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code)) if(wordJumpForSimpleRightOperands(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.AY) assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel") return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
} }
@ -2205,8 +2326,17 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(right, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
restoreRegisterStack(CpuRegister.A, false)
}
out(" cmp P8ZP_SCRATCH_B1 | bne $jumpIfFalseLabel") out(" cmp P8ZP_SCRATCH_B1 | bne $jumpIfFalseLabel")
} }
@ -2242,8 +2372,17 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code)) if(byteJumpForSimpleRightOperand(left, right, ::code))
return return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.A) assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
} else if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(right, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
restoreRegisterStack(CpuRegister.A, false)
}
return code("P8ZP_SCRATCH_B1") return code("P8ZP_SCRATCH_B1")
} }
@ -2309,8 +2448,18 @@ $repeatLabel lda $counterVar
""") """)
} }
else -> { else -> {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.AY) assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else if(right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
out(""" out("""
cmp P8ZP_SCRATCH_W2 cmp P8ZP_SCRATCH_W2
bne $jumpIfFalseLabel bne $jumpIfFalseLabel
@ -2385,8 +2534,18 @@ $repeatLabel lda $counterVar
+""") +""")
} }
else -> { else -> {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null) if(left.isSimple) {
assignExpressionToRegister(left, RegisterOrPair.AY) assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else if (right.isSimple) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
out(""" out("""
cmp P8ZP_SCRATCH_W2 cmp P8ZP_SCRATCH_W2
bne + bne +
@ -2781,7 +2940,7 @@ $repeatLabel lda $counterVar
val parameter = target.subroutineParameter val parameter = target.subroutineParameter
if(parameter!=null) { if(parameter!=null) {
val sub = parameter.definingSubroutine!! val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs" } require(sub.isAsmSubroutine) { "push/pop arg passing only supported on asmsubs ${sub.position}" }
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY } val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)] val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) { if(reg.statusflag!=null) {
@ -2867,17 +3026,27 @@ $repeatLabel lda $counterVar
} }
} }
internal fun popCpuStack(dt: DataType, targetAsmVarName: String) {
when(dt) {
in ByteDatatypes -> out(" pla | sta $targetAsmVarName")
in WordDatatypes -> out(" pla | sta $targetAsmVarName+1 | pla | sta $targetAsmVarName")
else -> throw AssemblyError("can't pop $dt")
}
}
internal fun pushCpuStack(dt: DataType, value: Expression) { internal fun pushCpuStack(dt: DataType, value: Expression) {
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD) val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(dt in ByteDatatypes) { if(dt in ByteDatatypes) {
assignExpressionToRegister(value, RegisterOrPair.A, signed) assignExpressionToRegister(value, RegisterOrPair.A, signed)
out(" pha") out(" pha")
} else { } else if(dt in WordDatatypes) {
assignExpressionToRegister(value, RegisterOrPair.AY, signed) assignExpressionToRegister(value, RegisterOrPair.AY, signed)
if (isTargetCpu(CpuType.CPU65c02)) if (isTargetCpu(CpuType.CPU65c02))
out(" pha | phy") out(" pha | phy")
else else
out(" pha | tya | pha") out(" pha | tya | pha")
} else {
throw AssemblyError("can't push $dt")
} }
} }

View File

@ -44,7 +44,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++ numberOfOptimizations++
} }
mods= optimizeJsrRts(linesByFour) mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
if(mods.isNotEmpty()) { if(mods.isNotEmpty()) {
apply(mods, lines) apply(mods, lines)
linesByFour = getLinesBy(lines, 4) linesByFour = getLinesBy(lines, 4)
@ -450,7 +450,7 @@ private fun getAddressArg(line: String, program: Program): UInt? {
val identMatch = identifierRegex.find(loadArg) val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) { if(identMatch!=null) {
val identifier = identMatch.value val identifier = identMatch.value
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl val decl = program.toplevelModule.lookup(identifier.split('.')) as? VarDecl
if(decl!=null) { if(decl!=null) {
when(decl.type){ when(decl.type){
VarDeclType.VAR -> null VarDeclType.VAR -> null
@ -486,8 +486,11 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
return mods return mods
} }
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { private fun optimizeJsrRtsAndOtherCombinations(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub // jsr Sub + rts -> jmp Sub
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
for (lines in linesByFour) { for (lines in linesByFour) {
val first = lines[0].value val first = lines[0].value
@ -496,6 +499,28 @@ private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp")) mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null) mods += Modification(lines[1].index, true, null)
} }
else if (" rts" in first || "\trts" in first) {
if (" jmp" in second || "\tjmp" in second)
mods += Modification(lines[1].index, true, null)
else if (" bra" in second || "\tbra" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcc" in second || "\tbcc" in second)
mods += Modification(lines[1].index, true, null)
else if (" bcs" in second || "\tbcs" in second)
mods += Modification(lines[1].index, true, null)
else if (" beq" in second || "\tbeq" in second)
mods += Modification(lines[1].index, true, null)
else if (" bne" in second || "\tbne" in second)
mods += Modification(lines[1].index, true, null)
else if (" bmi" in second || "\tbmi" in second)
mods += Modification(lines[1].index, true, null)
else if (" bpl" in second || "\tbpl" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvs" in second || "\tbvs" in second)
mods += Modification(lines[1].index, true, null)
else if (" bvc" in second || "\tbvc" in second)
mods += Modification(lines[1].index, true, null)
}
} }
return mods return mods
} }

View File

@ -124,7 +124,7 @@ internal class AssemblyProgram(
breakpoints.add("break \$" + match.groupValues[1]) breakpoints.add("break \$" + match.groupValues[1])
} }
val num = breakpoints.size val num = breakpoints.size
breakpoints.add(0, "; vice monitor breakpoint list now follows") breakpoints.add(0, "; breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined") breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del") breakpoints.add(2, "del")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n") viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")

View File

@ -43,7 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope) "abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope) "any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope) "sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope) "sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall) "rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall) "rol2" -> funcRol2(fcall)
@ -255,15 +254,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}") asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
} else { } else {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine) if(arg1.isSimple) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.out(" cmp P8ZP_SCRATCH_B1") asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
} }
} }
else -> { else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine) if(arg1.isSimple) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.out(" cmp P8ZP_SCRATCH_B1") asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
} }
} }
} else } else
@ -289,13 +300,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+""") +""")
} }
else -> { else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine) if(arg1.isSimple) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY) asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.out(""" asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
cpy P8ZP_SCRATCH_W1+1 asmgen.out("""
bne + cpy P8ZP_SCRATCH_W1+1
cmp P8ZP_SCRATCH_W1 bne +
+""") cmp P8ZP_SCRATCH_W1
+""")
} else {
asmgen.pushCpuStack(DataType.UWORD, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
}
} }
} }
} else } else
@ -307,7 +330,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
if(discardResult || fcall !is BuiltinFunctionCall) if(discardResult || fcall !is BuiltinFunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall") throw AssemblyError("should not discard result of memory allocation at $fcall")
val name = (fcall.args[0] as StringLiteral).value val name = (fcall.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"} require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position) val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
slabname.linkParents(fcall) slabname.linkParents(fcall)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position)) val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
@ -687,28 +710,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
} }
} }
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
when(func.name) {
"rnd" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rnd_stack")
else {
asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
}
}
"rndw" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rndw_stack")
else {
asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, asmgen), RegisterOrPair.AY)
}
}
else -> throw AssemblyError("wrong func")
}
}
private fun funcPokeW(fcall: IFunctionCall) { private fun funcPokeW(fcall: IFunctionCall) {
when(val addrExpr = fcall.args[0]) { when(val addrExpr = fcall.args[0]) {
is NumericLiteral -> { is NumericLiteral -> {

View File

@ -53,7 +53,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes) (sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes) || (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
// Output only the code to set up the parameters and perform the actual call // Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values! // NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!! // NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
@ -124,7 +124,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// this is called when one or more of the arguments are 'complex' and // this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers. // cannot be assigned to a register easily or risk clobbering other registers.
require(callee.isAsmSubroutine) require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
if(callee.parameters.isEmpty()) if(callee.parameters.isEmpty())
return return

View File

@ -202,16 +202,10 @@ internal class ProgramAndVarsGen(
asmsubs2asm(block.statements) asmsubs2asm(block.statements)
asmgen.out("") asmgen.out("")
asmgen.out("; subroutines in this block")
// First translate regular statements, and then put the subroutines at the end.
// (regular statements = everything except the initialization assignments;
// these will be part of the prog8_init_vars init routine generated below)
val initializers = blockVariableInitializers.getValue(block) val initializers = blockVariableInitializers.getValue(block)
val statements = block.statements.filterNot { it in initializers } val notInitializers = block.statements.filterNot { it in initializers }
val (subroutine, stmts) = statements.partition { it is Subroutine } notInitializers.forEach { asmgen.translate(it) }
stmts.forEach { asmgen.translate(it) }
subroutine.forEach { asmgen.translate(it) }
if(!options.dontReinitGlobals) { if(!options.dontReinitGlobals) {
// generate subroutine to initialize block-level (global) variables // generate subroutine to initialize block-level (global) variables
@ -229,7 +223,7 @@ internal class ProgramAndVarsGen(
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) } scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
private fun createBlockVariables(block: Block) { private fun createBlockVariables(block: Block) {
val scope = symboltable.lookupOrElse(block.name) { throw AssemblyError("lookup") } val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.BLOCK) require(scope.type==StNodeType.BLOCK)
val varsInBlock = getVars(scope) val varsInBlock = getVars(scope)
@ -248,7 +242,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables // normal statically allocated variables
val variables = varsInBlock val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) } .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
.map { it.value as StStaticVariable } .map { it.value as StStaticVariable }
nonZpVariables2asm(variables) nonZpVariables2asm(variables)
} }
@ -292,7 +286,7 @@ internal class ProgramAndVarsGen(
// regular subroutine // regular subroutine
asmgen.out("${sub.name}\t$asmStartScope") asmgen.out("${sub.name}\t$asmStartScope")
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") } val scope = symboltable.lookupOrElse(sub.scopedName.joinToString(".")) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.SUBROUTINE) require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope) val varsInSubroutine = getVars(scope)
@ -363,7 +357,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables // normal statically allocated variables
val variables = varsInSubroutine val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) } .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName.split('.')) }
.map { it.value as StStaticVariable } .map { it.value as StStaticVariable }
nonZpVariables2asm(variables) nonZpVariables2asm(variables)
@ -434,14 +428,14 @@ internal class ProgramAndVarsGen(
} }
private class ZpStringWithInitial( private class ZpStringWithInitial(
val name: List<String>, val name: String,
val alloc: Zeropage.ZpAllocation, val alloc: MemoryAllocator.VarAllocation,
val value: Pair<String, Encoding> val value: Pair<String, Encoding>
) )
private class ZpArrayWithInitial( private class ZpArrayWithInitial(
val name: List<String>, val name: String,
val alloc: Zeropage.ZpAllocation, val alloc: MemoryAllocator.VarAllocation,
val value: StArray val value: StArray
) )
@ -449,9 +443,10 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpStringWithInitial>() val result = mutableListOf<ZpStringWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR } val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
for (variable in vars) { for (variable in vars) {
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable val scopedName = variable.key.joinToString(".")
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationStringValue!=null) if(svar.onetimeInitializationStringValue!=null)
result.add(ZpStringWithInitial(variable.key, variable.value, svar.onetimeInitializationStringValue!!)) result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
} }
return result return result
} }
@ -460,15 +455,17 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpArrayWithInitial>() val result = mutableListOf<ZpArrayWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes } val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
for (variable in vars) { for (variable in vars) {
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable val scopedName = variable.key.joinToString(".")
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
if(svar.onetimeInitializationArrayValue!=null) if(svar.onetimeInitializationArrayValue!=null)
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.onetimeInitializationArrayValue!!)) result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
} }
return result return result
} }
private fun zeropagevars2asm(varNames: Set<List<String>>) { private fun zeropagevars2asm(varNames: Set<String>) {
val zpVariables = allocator.zeropageVars.filter { it.key in varNames } val namesLists = varNames.map { it.split('.') }.toSet()
val zpVariables = allocator.zeropageVars.filter { it.key in namesLists }.toList().sortedBy { it.second.address }
for ((scopedName, zpvar) in zpVariables) { for ((scopedName, zpvar) in zpVariables) {
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit()) if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
@ -479,7 +476,7 @@ internal class ProgramAndVarsGen(
private fun nonZpVariables2asm(variables: List<StStaticVariable>) { private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
asmgen.out("") asmgen.out("")
asmgen.out("; non-zeropage variables") asmgen.out("; non-zeropage variables")
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR } val (stringvars, othervars) = variables.sortedBy { it.name }.partition { it.dt==DataType.STR }
stringvars.forEach { stringvars.forEach {
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first) outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
} }
@ -585,10 +582,10 @@ internal class ProgramAndVarsGen(
} }
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) { private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
memvars.forEach { memvars.sortedBy { it.address }.forEach {
asmgen.out(" ${it.name} = ${it.address.toHex()}") asmgen.out(" ${it.name} = ${it.address.toHex()}")
} }
consts.forEach { consts.sortedBy { it.name }.forEach {
if(it.dt==DataType.FLOAT) if(it.dt==DataType.FLOAT)
asmgen.out(" ${it.name} = ${it.value}") asmgen.out(" ${it.name} = ${it.value}")
else else
@ -626,8 +623,8 @@ internal class ProgramAndVarsGen(
if(it.number!=null) { if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0') "$" + it.number!!.toInt().toString(16).padStart(4, '0')
} }
else if(it.addressOf!=null) { else if(it.addressOfSymbol!=null) {
asmgen.asmSymbolName(it.addressOf!!) asmgen.asmSymbolName(it.addressOfSymbol!!)
} }
else else
throw AssemblyError("weird array elt") throw AssemblyError("weird array elt")

View File

@ -16,13 +16,13 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
private val zeropage = options.compTarget.machine.zeropage private val zeropage = options.compTarget.machine.zeropage
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname) internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
init { init {
allocateZeropageVariables() allocateZeropageVariables()
} }
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables internal fun isZpVar(scopedName: List<String>) = scopedName in zeropageVars
internal fun getFloatAsmConst(number: Double): String { internal fun getFloatAsmConst(number: Double): String {
val asmName = globalFloatConsts[number] val asmName = globalFloatConsts[number]
@ -56,7 +56,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable -> varsRequiringZp.forEach { variable ->
val result = zeropage.allocate( val result = zeropage.allocate(
variable.scopedName, variable.scopedName.split('.'),
variable.dt, variable.dt,
variable.length, variable.length,
variable.position, variable.position,
@ -75,7 +75,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) { if(errors.noErrors()) {
varsPreferringZp.forEach { variable -> varsPreferringZp.forEach { variable ->
val result = zeropage.allocate( val result = zeropage.allocate(
variable.scopedName, variable.scopedName.split('.'),
variable.dt, variable.dt,
variable.length, variable.length,
variable.position, variable.position,
@ -88,13 +88,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
// try to allocate any other interger variables into the zeropage until it is full. // try to allocate any other interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? // TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
if(errors.noErrors()) { if(errors.noErrors()) {
for (variable in varsDontCare.sortedBy { it.scopedName.size }) { val sortedList = varsDontCare.sortedByDescending { it.scopedName }
for (variable in sortedList) {
if(variable.dt in IntegerDatatypes) { if(variable.dt in IntegerDatatypes) {
if(zeropage.free.isEmpty()) { if(zeropage.free.isEmpty()) {
break break
} else { } else {
val result = zeropage.allocate( val result = zeropage.allocate(
variable.scopedName, variable.scopedName.split('.'),
variable.dt, variable.dt,
variable.length, variable.length,
variable.position, variable.position,
@ -124,6 +125,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
} }
} }
collect(st) collect(st)
return vars return vars.sortedBy { it.dt }
} }
} }

View File

@ -207,7 +207,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
init { init {
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" } require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) { require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source dt size must be less or equal to target dt size at $position" "source dt size must be less or equal to target dt size at $position"
} }

View File

@ -27,13 +27,6 @@ internal class AssignmentAsmGen(private val program: Program,
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, origtarget.datatype, origtarget.scope,
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
} else origtarget
}
fun translateNormalAssignment(assign: AsmAssignment) { fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) { if(assign.isAugmentable) {
augmentableAsmGen.translate(assign) augmentableAsmGen.translate(assign)
@ -287,18 +280,25 @@ internal class AssignmentAsmGen(private val program: Program,
} }
} }
is PrefixExpression -> { is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target. if(assign.target.array==null) {
translateNormalAssignment(AsmAssignment( // First assign the value to the target then apply the operator in place on the target.
AsmAssignSource.fromAstSource(value.expression, program, asmgen), // This saves a temporary variable
assign.target, translateNormalAssignment(
false, program.memsizer, assign.position AsmAssignment(
)) AsmAssignSource.fromAstSource(value.expression, program, asmgen),
val target = virtualRegsToVariables(assign.target) assign.target,
when(value.operator) { false, program.memsizer, assign.position
"+" -> {} )
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype) )
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype) when (value.operator) {
else -> throw AssemblyError("invalid prefix operator") "+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign, true)
"~" -> augmentableAsmGen.inplaceInvert(assign)
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
} else {
assignPrefixedExpressionToArrayElt(assign)
} }
} }
is ContainmentCheck -> { is ContainmentCheck -> {
@ -317,6 +317,28 @@ internal class AssignmentAsmGen(private val program: Program,
} }
} }
internal fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment) {
require(assign.source.expression is PrefixExpression)
if(assign.source.datatype==DataType.FLOAT) {
// floatarray[x] = -value ... just use FAC1 to calculate the expression into and then store that back into the array.
assignExpressionToRegister(assign.source.expression, RegisterOrPair.FAC1, true)
assignFAC1float(assign.target)
} else {
// array[x] = -value ... use a tempvar then store that back into the array.
val tempvar = asmgen.getTempVarName(assign.target.datatype)
val assignToTempvar = AsmAssignment(assign.source,
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, assign.target.datatype, assign.target.scope, variableAsmName=tempvar, origAstTarget = assign.target.origAstTarget),
false, program.memsizer, assign.position)
asmgen.translateNormalAssignment(assignToTempvar)
when(assign.target.datatype) {
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
DataType.FLOAT -> assignVariableFloat(assign.target, tempvar)
else -> throw AssemblyError("weird dt")
}
}
}
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) { private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
when(target.datatype) { when(target.datatype) {
in ByteDatatypes -> { in ByteDatatypes -> {
@ -357,48 +379,60 @@ internal class AssignmentAsmGen(private val program: Program,
if(!expr.inferType(program).isInteger) if(!expr.inferType(program).isInteger)
return false return false
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) { fun simpleLogicalBytesExpr() {
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes && // both left and right expression operands are simple.
expr.left.isSimple && expr.right.isSimple) { if (expr.right is NumericLiteral || expr.right is IdentifierReference)
if(expr.right is NumericLiteral || expr.right is IdentifierReference) assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right) else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
else if(expr.left is NumericLiteral || expr.left is IdentifierReference) assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left) else {
else { assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
assignExpressionToRegister(expr.left, RegisterOrPair.A, false) asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false) assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine) asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.restoreRegisterStack(CpuRegister.A, false) when (expr.operator) {
when (expr.operator) { "&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1") "|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1") "^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1") else -> throw AssemblyError("invalid operator")
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(assign.target, CpuRegister.A)
} }
return true assignRegisterByte(assign.target, CpuRegister.A)
} }
if(expr.left.inferType(program).isWords && expr.right.inferType(program).isWords && }
expr.left.isSimple && expr.right.isSimple) {
if(expr.right is NumericLiteral || expr.right is IdentifierReference) fun simpleLogicalWordsExpr() {
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right) // both left and right expression operands are simple.
else if(expr.left is NumericLiteral || expr.left is IdentifierReference) if (expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left) assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
else { else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false) assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
asmgen.saveRegisterStack(CpuRegister.A, false) else {
asmgen.saveRegisterStack(CpuRegister.Y, false) assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine) asmgen.saveRegisterStack(CpuRegister.A, false)
when (expr.operator) { asmgen.saveRegisterStack(CpuRegister.Y, false)
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1") assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1") when (expr.operator) {
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1") "&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
else -> throw AssemblyError("invalid operator") "|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
} "^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
assignRegisterpairWord(assign.target, RegisterOrPair.AY) else -> throw AssemblyError("invalid operator")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
}
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
if (expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes) {
if (expr.right.isSimple) {
simpleLogicalBytesExpr()
return true
}
}
if (expr.left.inferType(program).isWords && expr.right.inferType(program).isWords) {
if (expr.right.isSimple) {
simpleLogicalWordsExpr()
return true
} }
return true
} }
return false return false
} }
@ -594,6 +628,58 @@ internal class AssignmentAsmGen(private val program: Program,
} }
} }
} }
else if(expr.operator=="<<" || expr.operator==">>") {
val shifts = expr.right.constValue(program)?.number?.toInt()
if(shifts!=null) {
val dt = expr.left.inferType(program)
if(dt.isBytes && shifts in 0..7) {
val signed = dt istype DataType.BYTE
assignExpressionToRegister(expr.left, RegisterOrPair.A, signed)
if(expr.operator=="<<") {
repeat(shifts) {
asmgen.out(" asl a")
}
} else {
if(signed && shifts>0) {
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
} else {
repeat(shifts) {
asmgen.out(" lsr a")
}
}
}
assignRegisterByte(assign.target, CpuRegister.A)
return true
} else if(dt.isWords && shifts in 0..7) {
val signed = dt istype DataType.WORD
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
if(expr.operator=="<<") {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
repeat(shifts) {
asmgen.out(" asl a | rol P8ZP_SCRATCH_B1")
}
asmgen.out(" ldy P8ZP_SCRATCH_B1")
}
} else {
if(signed) {
// TODO("shift AY >> $shifts signed")
return false
} else {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
repeat(shifts) {
asmgen.out(" lsr P8ZP_SCRATCH_B1 | ror a")
}
asmgen.out(" ldy P8ZP_SCRATCH_B1")
}
}
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
}
}
return false return false
} }
@ -729,7 +815,7 @@ internal class AssignmentAsmGen(private val program: Program,
if(variable.origin!=VarDeclOrigin.USERCODE) { if(variable.origin!=VarDeclOrigin.USERCODE) {
when(variable.datatype) { when(variable.datatype) {
DataType.STR -> { DataType.STR -> {
require(elementDt.isBytes) require(elementDt.isBytes) { "must be byte string ${variable.position}" }
val stringVal = variable.value as StringLiteral val stringVal = variable.value as StringLiteral
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference) val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE) assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
@ -745,7 +831,7 @@ internal class AssignmentAsmGen(private val program: Program,
throw AssemblyError("containment check of floats not supported") throw AssemblyError("containment check of floats not supported")
} }
in ArrayDatatypes -> { in ArrayDatatypes -> {
require(elementDt.isInteger) require(elementDt.isInteger) { "must be integer array ${variable.position}" }
val arrayVal = variable.value as ArrayLiteral val arrayVal = variable.value as ArrayLiteral
val dt = elementDt.getOr(DataType.UNDEFINED) val dt = elementDt.getOr(DataType.UNDEFINED)
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference) val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
@ -788,20 +874,20 @@ internal class AssignmentAsmGen(private val program: Program,
throw AssemblyError("containment check of floats not supported") throw AssemblyError("containment check of floats not supported")
} }
DataType.ARRAY_B, DataType.ARRAY_UB -> { DataType.ARRAY_B, DataType.ARRAY_UB -> {
val arrayVal = variable.value as ArrayLiteral val numElements = variable.arraysize!!.constIndex()!!
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE) assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname) assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
asmgen.restoreRegisterLocal(CpuRegister.A) asmgen.restoreRegisterLocal(CpuRegister.A)
asmgen.out(" ldy #${arrayVal.value.size}") asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_bytearray") asmgen.out(" jsr prog8_lib.containment_bytearray")
return return
} }
DataType.ARRAY_W, DataType.ARRAY_UW -> { DataType.ARRAY_W, DataType.ARRAY_UW -> {
val arrayVal = variable.value as ArrayLiteral val numElements = variable.arraysize!!.constIndex()!!
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine) assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname) assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
asmgen.out(" ldy #${arrayVal.value.size}") asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_wordarray") asmgen.out(" jsr prog8_lib.containment_wordarray")
return return
} }
@ -852,18 +938,17 @@ internal class AssignmentAsmGen(private val program: Program,
fun assignViaExprEval(addressExpression: Expression) { fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null) asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2") asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(target, CpuRegister.A) asmgen.out(" ldy #0")
assignRegisterpairWord(target, RegisterOrPair.AY)
} }
when (value.addressExpression) { when (value.addressExpression) {
is NumericLiteral -> { is NumericLiteral -> {
val address = (value.addressExpression as NumericLiteral).number.toUInt() val address = (value.addressExpression as NumericLiteral).number.toUInt()
assignMemoryByteIntoWord(target, address, null) assignMemoryByteIntoWord(target, address, null)
return
} }
is IdentifierReference -> { is IdentifierReference -> {
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference) assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
return
} }
is BinaryExpression -> { is BinaryExpression -> {
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) { if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
@ -877,6 +962,7 @@ internal class AssignmentAsmGen(private val program: Program,
assignViaExprEval(value.addressExpression) assignViaExprEval(value.addressExpression)
} }
} }
return
} }
} }
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away") is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
@ -885,7 +971,7 @@ internal class AssignmentAsmGen(private val program: Program,
// special case optimizations // special case optimizations
if(target.kind== TargetStorageKind.VARIABLE) { if(target.kind == TargetStorageKind.VARIABLE) {
if(value is IdentifierReference && valueDt != DataType.UNDEFINED) if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt) return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
@ -920,7 +1006,7 @@ internal class AssignmentAsmGen(private val program: Program,
} }
} }
if(valueDt==DataType.UBYTE) { if(valueDt==DataType.UBYTE || valueDt==DataType.BOOL) {
when(target.register) { when(target.register) {
RegisterOrPair.A, RegisterOrPair.A,
RegisterOrPair.X, RegisterOrPair.X,
@ -963,11 +1049,12 @@ internal class AssignmentAsmGen(private val program: Program,
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes) assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype) assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes) assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
return
} else { } else {
if(!(valueDt isAssignableTo targetDt)) { if(!(valueDt isAssignableTo targetDt)) {
if(valueDt in WordDatatypes && targetDt in ByteDatatypes) { return if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
// word to byte, just take the lsb // word to byte, just take the lsb
return assignCastViaLsbFunc(value, target) assignCastViaLsbFunc(value, target)
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) { } else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
// word to word, just assign // word to word, just assign
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD) assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
@ -977,44 +1064,87 @@ internal class AssignmentAsmGen(private val program: Program,
} else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) { } else if(valueDt in ByteDatatypes && targetDt in WordDatatypes) {
// byte to word, just assign // byte to word, just assign
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD) assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
} } else
else
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker") throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
} }
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
} }
return }
if(targetDt in IntegerDatatypes && valueDt in IntegerDatatypes && valueDt!=targetDt && valueDt.isAssignableTo(targetDt)) {
require(targetDt in WordDatatypes && valueDt in ByteDatatypes) {
"should be byte to word assignment ${origTypeCastExpression.position}"
}
when(target.kind) {
// TargetStorageKind.VARIABLE -> {
// This has been handled already earlier on line 961.
// // byte to word, just assign to registers first, then assign to variable
// assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
// assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, targetDt)
// return
// }
TargetStorageKind.ARRAY -> {
// byte to word, just assign to registers first, then assign into array
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
TargetStorageKind.REGISTER -> {
// byte to word, just assign to registers
assignExpressionToRegister(value, target.register!!, targetDt==DataType.WORD)
return
}
TargetStorageKind.STACK -> {
// byte to word, just assign to registers first, then push onto stack
assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==DataType.WORD)
asmgen.out("""
sta P8ESTACK_LO,x
tya
sta P8ESTACK_HI,x
dex""")
return
}
else -> throw AssemblyError("weird target")
}
} }
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) { if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
when(valueDt) { when(valueDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
assignExpressionToRegister(value, RegisterOrPair.Y, false) assignExpressionToRegister(value, RegisterOrPair.Y, false)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.FREADUY") asmgen.out(" jsr floats.FREADUY")
asmgen.restoreRegisterLocal(CpuRegister.X)
} }
DataType.BYTE -> { DataType.BYTE -> {
assignExpressionToRegister(value, RegisterOrPair.A, true) assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.FREADSA") asmgen.out(" jsr floats.FREADSA")
asmgen.restoreRegisterLocal(CpuRegister.X)
} }
DataType.UWORD -> { DataType.UWORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, false) assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.GIVUAYFAY") asmgen.out(" jsr floats.GIVUAYFAY")
asmgen.restoreRegisterLocal(CpuRegister.X)
} }
DataType.WORD -> { DataType.WORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, true) assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.GIVAYFAY") asmgen.out(" jsr floats.GIVAYFAY")
asmgen.restoreRegisterLocal(CpuRegister.X)
} }
else -> throw AssemblyError("invalid dt") else -> throw AssemblyError("invalid dt")
} }
if(target.register==RegisterOrPair.FAC2) { if(target.register==RegisterOrPair.FAC2) {
asmgen.out(" jsr floats.MOVEF") asmgen.out(" jsr floats.MOVEF")
} }
} else { return
// No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
asmgen.assignExpressionTo(origTypeCastExpression, target)
} }
// No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
asmgen.assignExpressionTo(origTypeCastExpression, target)
} }
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) { private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
@ -1170,14 +1300,10 @@ internal class AssignmentAsmGen(private val program: Program,
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02)) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out( asmgen.out(
" st${ " st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
regs.toString().lowercase()
} $targetAsmVarName | stz $targetAsmVarName+1")
else else
asmgen.out( asmgen.out(
" st${ " st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
regs.toString().lowercase()
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
} }
DataType.FLOAT -> { DataType.FLOAT -> {
when(regs) { when(regs) {
@ -1972,18 +2098,10 @@ internal class AssignmentAsmGen(private val program: Program,
} }
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) { internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair // we make an exception in the type check for assigning something to a register pair AX, AY or XY
// these will be correctly typecasted from a byte to a word value // these will be correctly typecasted from a byte to a word value here
if(target.register !in Cx16VirtualRegisters && if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) { require(target.datatype in ByteDatatypes) { "assign target must be byte type ${target.origAstTarget?.position ?: ""}"}
if(target.kind== TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16")
require(target.datatype in ByteDatatypes)
} else {
require(target.datatype in ByteDatatypes)
}
}
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
@ -2072,7 +2190,9 @@ internal class AssignmentAsmGen(private val program: Program,
} }
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) { internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes) require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes) {
"assign target must be word type ${target.origAstTarget?.position ?: ""}"
}
if(target.datatype==DataType.FLOAT) if(target.datatype==DataType.FLOAT)
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer") throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")

View File

@ -16,18 +16,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
) { ) {
fun translate(assign: AsmAssignment) { fun translate(assign: AsmAssignment) {
require(assign.isAugmentable) require(assign.isAugmentable)
require(assign.source.kind== SourceStorageKind.EXPRESSION) require(assign.source.kind == SourceStorageKind.EXPRESSION) {
"non-expression assign value should be handled elsewhere ${assign.position}"
}
when (val value = assign.source.expression!!) { when (val value = assign.source.expression!!) {
is PrefixExpression -> { is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A // A = -A , A = +A, A = ~A, A = not A
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
val itype = value.inferType(program)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
when (value.operator) { when (value.operator) {
"+" -> {} "+" -> {}
"-" -> inplaceNegate(target, type) "-" -> inplaceNegate(assign, false)
"~" -> inplaceInvert(target, type) "~" -> inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator") else -> throw AssemblyError("invalid prefix operator")
} }
} }
@ -1796,8 +1795,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) { internal fun inplaceInvert(assign: AsmAssignment) {
when (dt) { val target = assign.target
when (assign.target.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (target.kind) { when (target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
@ -1840,7 +1840,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert") TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}") TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
@ -1864,15 +1865,24 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert") TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}") TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
} }
} }
else -> throw AssemblyError("invert of invalid type") else -> throw AssemblyError("invert of invalid type")
} }
} }
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) { internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
when (dt) { val target = assign.target
val datatype = if(ignoreDatatype) {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> DataType.BYTE
DataType.UWORD, DataType.WORD -> DataType.WORD
else -> target.datatype
}
} else target.datatype
when (datatype) {
DataType.BYTE -> { DataType.BYTE -> {
when (target.kind) { when (target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
@ -1896,9 +1906,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for byte negate") else -> throw AssemblyError("invalid reg dt for byte negate")
} }
} }
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate") TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate") TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
else -> throw AssemblyError("no asm gen for in-place negate byte") TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
} }
} }
DataType.WORD -> { DataType.WORD -> {
@ -1955,12 +1966,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for word neg") else -> throw AssemblyError("invalid reg dt for word neg")
} }
} }
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate") TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
else -> throw AssemblyError("no asm gen for in-place negate word") TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
when (target.kind) { when (target.kind) {
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.NEGOP")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.MOVFA | jsr floats.NEGOP | jsr floats.MOVEF")
else -> throw AssemblyError("invalid float register")
}
}
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
// simply flip the sign bit in the float // simply flip the sign bit in the float
asmgen.out(""" asmgen.out("""
@ -1970,10 +1990,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""") """)
} }
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate") TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}") TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target for in-place float negation")
} }
} }
else -> throw AssemblyError("negate of invalid type $dt") else -> throw AssemblyError("negate of invalid type")
} }
} }

View File

@ -3,16 +3,12 @@ package prog8.codegen.intermediate
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.AssemblyError import prog8.code.core.AssemblyError
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SignedDatatypes import prog8.code.core.SignedDatatypes
import prog8.intermediate.IRCodeChunk import prog8.intermediate.*
import prog8.intermediate.IRInstruction
import prog8.intermediate.Opcode
import prog8.intermediate.VmDataType
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) { internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
internal fun translate(assignment: PtAssignment): IRCodeChunk { internal fun translate(assignment: PtAssignment): IRCodeChunks {
if(assignment.target.children.single() is PtMachineRegister) if(assignment.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister") throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
@ -22,13 +18,13 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
translateRegularAssign(assignment) translateRegularAssign(assignment)
} }
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunk { private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
val ident = assignment.target.identifier val ident = assignment.target.identifier
val memory = assignment.target.memory val memory = assignment.target.memory
val array = assignment.target.array val array = assignment.target.array
return if(ident!=null) { return if(ident!=null) {
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment) assignSelfInMemory(ident.name, assignment.value, assignment)
} else if(memory != null) { } else if(memory != null) {
if(memory.address is PtNumber) if(memory.address is PtNumber)
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment) assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
@ -48,23 +44,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
address: Int, address: Int,
value: PtExpression, value: PtExpression,
origAssign: PtAssignment origAssign: PtAssignment
): IRCodeChunk { ): IRCodeChunks {
val vmDt = codeGen.vmType(value.type) val vmDt = codeGen.irType(value.type)
val code = IRCodeChunk(origAssign.position)
when(value) { when(value) {
is PtIdentifier -> return code // do nothing, x=x null assignment. is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
is PtMachineRegister -> return code // do nothing, reg=reg null assignment is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null, value.position) is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign) is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
is PtMemoryByte -> { is PtMemoryByte -> {
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt())) return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
code // do nothing, mem=mem null assignment. emptyList() // do nothing, mem=mem null assignment.
else { else {
// read and write a (i/o) memory location to itself. // read and write a (i/o) memory location to itself.
val tempReg = codeGen.vmRegisters.nextFree() val tempReg = codeGen.registers.nextFree()
val code = IRCodeChunk(null, null)
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address) code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address) code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
code listOf(code)
} }
} }
else -> return fallbackAssign(origAssign) else -> return fallbackAssign(origAssign)
@ -75,25 +71,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
symbol: String, symbol: String,
value: PtExpression, value: PtExpression,
origAssign: PtAssignment origAssign: PtAssignment
): IRCodeChunk { ): IRCodeChunks {
val vmDt = codeGen.vmType(value.type) val vmDt = codeGen.irType(value.type)
val code = IRCodeChunk(origAssign.position) return when(value) {
when(value) { is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
is PtIdentifier -> return code // do nothing, x=x null assignment. is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
is PtMachineRegister -> return code // do nothing, reg=reg null assignment is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
is PtPrefix -> return inplacePrefix(value.operator, vmDt, null, symbol, value.position) is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
is PtMemoryByte -> { is PtMemoryByte -> {
val tempReg = codeGen.vmRegisters.nextFree() val code = IRCodeChunk(null, null)
val tempReg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol) code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol) code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
return code listOf(code)
} }
else -> return fallbackAssign(origAssign)
else -> fallbackAssign(origAssign)
} }
} }
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunk { private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
if (codeGen.options.slowCodegenWarnings) if (codeGen.options.slowCodegenWarnings)
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position) codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
return translateRegularAssign(origAssign) return translateRegularAssign(origAssign)
@ -102,12 +99,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun inplaceBinexpr( private fun inplaceBinexpr(
operator: String, operator: String,
operand: PtExpression, operand: PtExpression,
vmDt: VmDataType, vmDt: IRDataType,
signed: Boolean, signed: Boolean,
knownAddress: Int?, knownAddress: Int?,
symbol: String?, symbol: String?,
origAssign: PtAssignment origAssign: PtAssignment
): IRCodeChunk { ): IRCodeChunks {
if(knownAddress!=null) { if(knownAddress!=null) {
when (operator) { when (operator) {
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand) "+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
@ -139,8 +136,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return fallbackAssign(origAssign) return fallbackAssign(origAssign)
} }
private fun inplacePrefix(operator: String, vmDt: VmDataType, knownAddress: Int?, addressSymbol: String?, position: Position): IRCodeChunk { private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
val code= IRCodeChunk(position) val code= IRCodeChunk(null, null)
when(operator) { when(operator) {
"+" -> { } "+" -> { }
"-" -> { "-" -> {
@ -150,8 +147,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol) IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
} }
"~" -> { "~" -> {
val regMask = codeGen.vmRegisters.nextFree() val regMask = codeGen.registers.nextFree()
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask) code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
code += if(knownAddress!=null) code += if(knownAddress!=null)
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress) IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
@ -160,48 +157,48 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
} }
else -> throw AssemblyError("weird prefix operator") else -> throw AssemblyError("weird prefix operator")
} }
return code return listOf(code)
} }
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunk { private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call. // note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
val ident = assignment.target.identifier val ident = assignment.target.identifier
val memory = assignment.target.memory val memory = assignment.target.memory
val array = assignment.target.array val array = assignment.target.array
val vmDt = codeGen.vmType(assignment.value.type) val vmDt = codeGen.irType(assignment.value.type)
val result = mutableListOf<IRCodeChunkBase>()
val code = IRCodeChunk(assignment.position)
var resultRegister = -1 var resultRegister = -1
var resultFpRegister = -1 var resultFpRegister = -1
val zero = codeGen.isZero(assignment.value) val zero = codeGen.isZero(assignment.value)
if(!zero) { if(!zero) {
// calculate the assignment value // calculate the assignment value
if (vmDt == VmDataType.FLOAT) { if (vmDt == IRDataType.FLOAT) {
resultFpRegister = codeGen.vmRegisters.nextFreeFloat() resultFpRegister = codeGen.registers.nextFreeFloat()
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister) result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
} else { } else {
resultRegister = if (assignment.value is PtMachineRegister) { resultRegister = if (assignment.value is PtMachineRegister) {
(assignment.value as PtMachineRegister).register (assignment.value as PtMachineRegister).register
} else { } else {
val reg = codeGen.vmRegisters.nextFree() val reg = codeGen.registers.nextFree()
code += expressionEval.translateExpression(assignment.value, reg, -1) result += expressionEval.translateExpression(assignment.value, reg, -1)
reg reg
} }
} }
} }
if(ident!=null) { if(ident!=null) {
val symbol = ident.targetName.joinToString(".") val instruction = if(zero) {
code += if(zero) { IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = ident.name)
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
} else { } else {
if (vmDt == VmDataType.FLOAT) if (vmDt == IRDataType.FLOAT)
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol) IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = ident.name)
else else
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol) IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = ident.name)
} }
result += IRCodeChunk(null, null).also { it += instruction }
return result
} }
else if(array!=null) { else if(array!=null) {
val variable = array.variable.targetName.joinToString(".") val variable = array.variable.name
val itemsize = codeGen.program.memsizer.memorySize(array.type) val itemsize = codeGen.program.memsizer.memorySize(array.type)
if(array.variable.type==DataType.UWORD) { if(array.variable.type==DataType.UWORD) {
@ -210,85 +207,91 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
throw AssemblyError("non-array var indexing requires bytes dt") throw AssemblyError("non-array var indexing requires bytes dt")
if(array.index.type!=DataType.UBYTE) if(array.index.type!=DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index") throw AssemblyError("non-array var indexing requires bytes index")
val idxReg = codeGen.vmRegisters.nextFree() val idxReg = codeGen.registers.nextFree()
code += expressionEval.translateExpression(array.index, idxReg, -1) result += expressionEval.translateExpression(array.index, idxReg, -1)
val code = IRCodeChunk(null, null)
if(zero) { if(zero) {
// there's no STOREZIX instruction // there's no STOREZIX instruction
resultRegister = codeGen.vmRegisters.nextFree() resultRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0) code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
} }
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable) code += IRInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
return code result += code
return result
} }
val fixedIndex = constIntValue(array.index) val fixedIndex = constIntValue(array.index)
if(zero) { if(zero) {
if(fixedIndex!=null) { if(fixedIndex!=null) {
val offset = fixedIndex*itemsize val offset = fixedIndex*itemsize
code += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
result += chunk
} else { } else {
val indexReg = codeGen.vmRegisters.nextFree() val indexReg = codeGen.registers.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position) result += loadIndexReg(array, itemsize, indexReg)
code += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
} }
} else { } else {
if(vmDt== VmDataType.FLOAT) { if(vmDt== IRDataType.FLOAT) {
if(fixedIndex!=null) { if(fixedIndex!=null) {
val offset = fixedIndex*itemsize val offset = fixedIndex*itemsize
code += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = "$variable+$offset") }
result += chunk
} else { } else {
val indexReg = codeGen.vmRegisters.nextFree() val indexReg = codeGen.registers.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position) result += loadIndexReg(array, itemsize, indexReg)
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
} }
} else { } else {
if(fixedIndex!=null) { if(fixedIndex!=null) {
val offset = fixedIndex*itemsize val offset = fixedIndex*itemsize
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = "$variable+$offset") }
result += chunk
} else { } else {
val indexReg = codeGen.vmRegisters.nextFree() val indexReg = codeGen.registers.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position) result += loadIndexReg(array, itemsize, indexReg)
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
} }
} }
} }
return result
} }
else if(memory!=null) { else if(memory!=null) {
require(vmDt== VmDataType.BYTE) require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
if(zero) { if(zero) {
if(memory.address is PtNumber) { if(memory.address is PtNumber) {
code += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt()) }
result += chunk
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1) result += expressionEval.translateExpression(memory.address, addressReg, -1)
code += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
} }
} else { } else {
if(memory.address is PtNumber) { if(memory.address is PtNumber) {
code += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt()) }
result += chunk
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1) result += expressionEval.translateExpression(memory.address, addressReg, -1)
code += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
} }
} }
return result
} }
else else
throw AssemblyError("weird assigntarget") throw AssemblyError("weird assigntarget")
return code
} }
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int, position: Position): IRCodeChunk { private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
val code = IRCodeChunk(position) return if(itemsize==1) {
if(itemsize==1) { expressionEval.translateExpression(array.index, indexReg, -1)
code += expressionEval.translateExpression(array.index, indexReg, -1) } else {
}
else {
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position) val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index mult.children += array.index
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position) mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
code += expressionEval.translateExpression(mult, indexReg, -1) expressionEval.translateExpression(mult, indexReg, -1)
} }
return code
} }
} }

View File

@ -4,13 +4,12 @@ import prog8.code.StStaticVariable
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.AssemblyError import prog8.code.core.AssemblyError
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.intermediate.* import prog8.intermediate.*
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) { internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
return when(call.name) { return when(call.name) {
"any" -> funcAny(call, resultRegister) "any" -> funcAny(call, resultRegister)
"all" -> funcAll(call, resultRegister) "all" -> funcAll(call, resultRegister)
@ -25,9 +24,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"rsave", "rsave",
"rsavex", "rsavex",
"rrestore", "rrestore",
"rrestorex" -> IRCodeChunk(call.position) // vm doesn't have registers to save/restore "rrestorex" -> emptyList() // vm doesn't have registers to save/restore
"rnd" -> funcRnd(resultRegister, call.position)
"rndw" -> funcRndw(resultRegister, call.position)
"callfar" -> throw AssemblyError("callfar() is for cx16 target only") "callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callrom" -> throw AssemblyError("callrom() is for cx16 target only") "callrom" -> throw AssemblyError("callrom() is for cx16 target only")
"msb" -> funcMsb(call, resultRegister) "msb" -> funcMsb(call, resultRegister)
@ -37,7 +34,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"peekw" -> funcPeekW(call, resultRegister) "peekw" -> funcPeekW(call, resultRegister)
"poke" -> funcPoke(call) "poke" -> funcPoke(call)
"pokew" -> funcPokeW(call) "pokew" -> funcPokeW(call)
"pokemon" -> IRCodeChunk(call.position) "pokemon" -> emptyList()
"mkword" -> funcMkword(call, resultRegister) "mkword" -> funcMkword(call, resultRegister)
"sort" -> funcSort(call) "sort" -> funcSort(call)
"reverse" -> funcReverse(call) "reverse" -> funcReverse(call)
@ -49,20 +46,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
} }
} }
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(call.position) val leftRegister = codeGen.registers.nextFree()
val leftRegister = codeGen.vmRegisters.nextFree() val rightRegister = codeGen.registers.nextFree()
val rightRegister = codeGen.vmRegisters.nextFree() val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args[0], leftRegister, -1) result += exprGen.translateExpression(call.args[0], leftRegister, -1)
code += exprGen.translateExpression(call.args[1], rightRegister, -1) result += exprGen.translateExpression(call.args[1], rightRegister, -1)
code += IRInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister) result += IRCodeChunk(null, null).also {
return code it += IRInstruction(Opcode.CMP, codeGen.irType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
}
return result
} }
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val code = IRCodeChunk(call.position)
val syscall = val syscall =
when (array.dt) { when (array.dt) {
DataType.ARRAY_UB, DataType.ARRAY_UB,
@ -72,17 +70,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
else -> throw IllegalArgumentException("weird type") else -> throw IllegalArgumentException("weird type")
} }
code += exprGen.translateExpression(call.args[0], 0, -1) val result = mutableListOf<IRCodeChunkBase>()
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length) result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal) result += IRCodeChunk(null, null).also {
if (resultRegister != 0) it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0) it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
return code if(resultRegister!=0)
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
}
return result
} }
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall = val syscall =
when(array.dt) { when(array.dt) {
DataType.ARRAY_UB, DataType.ARRAY_UB,
@ -92,100 +93,118 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
else -> throw IllegalArgumentException("weird type") else -> throw IllegalArgumentException("weird type")
} }
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args[0], 0, -1) result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length) result += IRCodeChunk(null, null).also {
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal) it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
if(resultRegister!=0) it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0) if(resultRegister!=0)
return code it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
}
return result
} }
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(call.position)
val sourceDt = call.args.single().type val sourceDt = call.args.single().type
val result = mutableListOf<IRCodeChunkBase>()
if(sourceDt!=DataType.UWORD) { if(sourceDt!=DataType.UWORD) {
code += exprGen.translateExpression(call.args[0], resultRegister, -1) result += exprGen.translateExpression(call.args[0], resultRegister, -1)
when (sourceDt) { when (sourceDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
code += IRInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = resultRegister)
}
} }
DataType.BYTE -> { DataType.BYTE -> {
val notNegativeLabel = codeGen.createLabelName() val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.vmRegisters.nextFree() val compareReg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister) result += IRCodeChunk(null, null).also {
code += IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80) it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=resultRegister)
code += IRInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel) it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, value=0x80)
code += IRInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister) it += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
code += IRInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister) it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=resultRegister)
code += IRCodeLabel(notNegativeLabel) it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultRegister)
}
result += IRCodeChunk(notNegativeLabel, null)
} }
DataType.WORD -> { DataType.WORD -> {
val notNegativeLabel = codeGen.createLabelName() val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.vmRegisters.nextFree() val compareReg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister) result += IRCodeChunk(null, null).also {
code += IRInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000) it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=resultRegister)
code += IRInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel) it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, value=0x8000)
code += IRInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister) it += IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
code += IRCodeLabel(notNegativeLabel) it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=resultRegister)
}
result += IRCodeChunk(notNegativeLabel, null)
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
} }
return code return result
} }
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(call.position) val reg = codeGen.registers.nextFree()
val reg = codeGen.vmRegisters.nextFree() val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args.single(), reg, -1) result += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg) result += IRCodeChunk(null, null).also {
return code it += IRInstruction(Opcode.SGN, codeGen.irType(call.type), reg1 = resultRegister, reg2 = reg)
}
return result
} }
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(call.position) val reg = codeGen.registers.nextFree()
val reg = codeGen.vmRegisters.nextFree() val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args.single(), reg, -1) result += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg) result += IRCodeChunk(null, null).also {
return code it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultRegister, reg2=reg)
}
return result
} }
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(call.position) val code = IRCodeChunk(null, null)
val reg = codeGen.vmRegisters.nextFree() val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg) code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
code += assignRegisterTo(call.args.single(), reg) val result = mutableListOf<IRCodeChunkBase>(code)
return code result += assignRegisterTo(call.args.single(), reg)
return result
} }
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(call.position) val code = IRCodeChunk(null, null)
val reg = codeGen.vmRegisters.nextFree() val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, VmDataType.WORD, reg1=reg) code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
code += assignRegisterTo(call.args.single(), reg) val result = mutableListOf<IRCodeChunkBase>(code)
return code result += assignRegisterTo(call.args.single(), reg)
return result
} }
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
val reg = codeGen.vmRegisters.nextFree() val reg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1) result += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg) result += IRCodeChunk(null, null).also {
return code it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=reg)
}
return result
} }
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
val reg = codeGen.vmRegisters.nextFree() val reg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1) result += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg) result += IRCodeChunk(null, null).also {
return code it += IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1 = reg)
}
return result
} }
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall = val syscall =
when(array.dt) { when(array.dt) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
@ -193,16 +212,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
else -> throw IllegalArgumentException("weird type to reverse") else -> throw IllegalArgumentException("weird type to reverse")
} }
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args[0], 0, -1) result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length) result += IRCodeChunk(null, null).also {
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal) it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
return code it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
}
return result
} }
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
val arrayName = call.args[0] as PtIdentifier val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall = val syscall =
when(array.dt) { when(array.dt) {
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
@ -213,153 +234,171 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported") DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
else -> throw IllegalArgumentException("weird type to sort") else -> throw IllegalArgumentException("weird type to sort")
} }
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args[0], 0, -1) result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length) result += IRCodeChunk(null, null).also {
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal) it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
return code it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
}
return result
} }
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val msbReg = codeGen.vmRegisters.nextFree() val msbReg = codeGen.registers.nextFree()
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args[0], msbReg, -1) result += exprGen.translateExpression(call.args[0], msbReg, -1)
code += exprGen.translateExpression(call.args[1], resultRegister, -1) result += exprGen.translateExpression(call.args[1], resultRegister, -1)
code += IRInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg) result += IRCodeChunk(null, null).also {
return code it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = resultRegister, reg2 = msbReg)
}
return result
} }
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) { if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) { if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt() val address = (call.args[0] as PtNumber).number.toInt()
code += IRInstruction(Opcode.STOREZM, VmDataType.WORD, value = address) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, value = address)
}
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1) result += exprGen.translateExpression(call.args[0], addressReg, -1)
code += IRInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg2 = addressReg)
}
} }
} else { } else {
val valueReg = codeGen.vmRegisters.nextFree() val valueReg = codeGen.registers.nextFree()
if (call.args[0] is PtNumber) { if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt() val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1) result += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = valueReg, value = address)
}
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1) result += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1) result += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueReg, reg2 = addressReg)
}
} }
} }
return code return result
} }
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunk { private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) { if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) { if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt() val address = (call.args[0] as PtNumber).number.toInt()
code += IRInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, value = address)
}
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1) result += exprGen.translateExpression(call.args[0], addressReg, -1)
code += IRInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg2 = addressReg)
}
} }
} else { } else {
val valueReg = codeGen.vmRegisters.nextFree() val valueReg = codeGen.registers.nextFree()
if (call.args[0] is PtNumber) { if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt() val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1) result += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueReg, value = address)
}
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1) result += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1) result += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
}
} }
} }
return code return result
} }
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
if(call.args[0] is PtNumber) { if(call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt() val address = (call.args[0] as PtNumber).number.toInt()
code += IRInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, value = address)
}
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1) result += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += IRInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
}
} }
return code return result
} }
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
if(call.args[0] is PtNumber) { if(call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt() val address = (call.args[0] as PtNumber).number.toInt()
code += IRInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, value = address)
}
} else { } else {
val addressReg = codeGen.vmRegisters.nextFree() val addressReg = codeGen.registers.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1) result += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += IRInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
}
} }
return code return result
} }
private fun funcRnd(resultRegister: Int, position: Position): IRCodeChunk { private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(position)
code += IRInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
return code
}
private fun funcRndw(resultRegister: Int, position: Position): IRCodeChunk {
val code = IRCodeChunk(position)
code += IRInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
return code
}
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val name = (call.args[0] as PtString).value val name = (call.args[0] as PtString).value
val code = IRCodeChunk(call.position) val code = IRCodeChunk(null, null)
code += IRInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name") code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return code return listOf(code)
} }
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(call.position) return exprGen.translateExpression(call.args.single(), resultRegister, -1)
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here. // note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return code
} }
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args.single(), resultRegister, -1) result += exprGen.translateExpression(call.args.single(), resultRegister, -1)
code += IRInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister) result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = resultRegister, reg2 = resultRegister)
}
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here. // note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return code return result
} }
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk { private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val vmDt = codeGen.vmType(call.args[0].type) val vmDt = codeGen.irType(call.args[0].type)
val code = IRCodeChunk(call.position) val result = mutableListOf<IRCodeChunkBase>()
code += exprGen.translateExpression(call.args[0], resultRegister, -1) result += exprGen.translateExpression(call.args[0], resultRegister, -1)
code += IRInstruction(opcode, vmDt, reg1=resultRegister) result += IRCodeChunk(null, null).also {
code += assignRegisterTo(call.args[0], resultRegister) it += IRInstruction(opcode, vmDt, reg1 = resultRegister)
return code }
result += assignRegisterTo(call.args[0], resultRegister)
return result
} }
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunk { private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val code = IRCodeChunk(target.position)
val assignment = PtAssignment(target.position) val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position) val assignTarget = PtAssignTarget(target.position)
assignTarget.children.add(target) assignTarget.children.add(target)
assignment.children.add(assignTarget) assignment.children.add(assignTarget)
assignment.children.add(PtMachineRegister(register, target.type, target.position)) assignment.children.add(PtMachineRegister(register, target.type, target.position))
code += codeGen.translateNode(assignment) val result = mutableListOf<IRCodeChunkBase>()
return code result += codeGen.translateNode(assignment)
return result
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -4,26 +4,113 @@ import prog8.intermediate.*
internal class IRPeepholeOptimizer(private val irprog: IRProgram) { internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
fun optimize() { fun optimize() {
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk -> removeEmptyChunks(sub)
joinChunks(sub)
sub.chunks.withIndex().forEach { (index, chunk1) ->
// we don't optimize Inline Asm chunks here. // we don't optimize Inline Asm chunks here.
if(chunk is IRCodeChunk) { val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null
if(chunk1 is IRCodeChunk) {
do { do {
val indexedInstructions = chunk.lines.withIndex() val indexedInstructions = chunk1.instructions.withIndex()
.filter { it.value is IRInstruction } .map { IndexedValue(it.index, it.value) }
.map { IndexedValue(it.index, it.value as IRInstruction) } val changed = removeNops(chunk1, indexedInstructions)
val changed = removeNops(chunk, indexedInstructions) || removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|| removeDoubleLoadsAndStores(chunk, indexedInstructions) // TODO not yet implemented || removeUselessArithmetic(chunk1, indexedInstructions)
|| removeUselessArithmetic(chunk, indexedInstructions) || removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeWeirdBranches(chunk, indexedInstructions) || removeDoubleSecClc(chunk1, indexedInstructions)
|| removeDoubleSecClc(chunk, indexedInstructions) || cleanupPushPop(chunk1, indexedInstructions)
|| cleanupPushPop(chunk, indexedInstructions)
// TODO other optimizations: // TODO other optimizations:
// more complex optimizations such as unused registers // more complex optimizations such as unused registers
} while (changed) } while (changed)
} }
} }
removeEmptyChunks(sub)
} }
irprog.linkChunks() // re-link
}
private fun removeEmptyChunks(sub: IRSubroutine) {
if(sub.chunks.isEmpty())
return
/*
Empty Code chunk with label ->
If next chunk has no label -> move label to next chunk, remove original
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: consolidate labels into 1)
If is last chunk -> keep chunk in place because of the label.
Empty Code chunk without label ->
should not have been generated! ERROR.
*/
val relabelChunks = mutableListOf<Pair<Int, String>>()
val removeChunks = mutableListOf<Int>()
sub.chunks.withIndex().forEach { (index, chunk) ->
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
if(chunk.label==null) {
removeChunks += index
} else {
if (index < sub.chunks.size - 1) {
val nextchunk = sub.chunks[index + 1]
if (nextchunk.label == null) {
// can transplant label to next chunk and remove this empty one.
relabelChunks += Pair(index + 1, chunk.label!!)
removeChunks += index
} else {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
// TODO: consolidate labels on same chunk
}
}
}
}
}
}
relabelChunks.forEach { (index, label) ->
val chunk = IRCodeChunk(label, null)
chunk.instructions += sub.chunks[index].instructions
sub.chunks[index] = chunk
}
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
}
private fun joinChunks(sub: IRSubroutine) {
// Subroutine contains a list of chunks. Some can be joined into one.
if(sub.chunks.isEmpty())
return
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
if(chunk.label!=null)
return false
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
// if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk
val lastInstruction = previous.instructions.lastOrNull()
if(lastInstruction!=null)
return lastInstruction.opcode !in OpcodesThatJump
return true
}
return false
}
val chunks = mutableListOf<IRCodeChunkBase>()
chunks += sub.chunks[0]
for(ix in 1 until sub.chunks.size) {
val lastChunk = chunks.last()
if(mayJoin(lastChunk, sub.chunks[ix])) {
lastChunk.instructions += sub.chunks[ix].instructions
lastChunk.next = sub.chunks[ix].next
}
else
chunks += sub.chunks[ix]
}
sub.chunks.clear()
sub.chunks += chunks
} }
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean { private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
@ -31,15 +118,15 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false var changed = false
indexedInstructions.reversed().forEach { (idx, ins) -> indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.PUSH) { if(ins.opcode== Opcode.PUSH) {
if(idx < chunk.lines.size-1) { if(idx < chunk.instructions.size-1) {
val insAfter = chunk.lines[idx+1] as? IRInstruction val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter!=null && insAfter.opcode == Opcode.POP) { if(insAfter!=null && insAfter.opcode == Opcode.POP) {
if(ins.reg1==insAfter.reg1) { if(ins.reg1==insAfter.reg1) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
} else { } else {
chunk.lines[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1) chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
chunk.lines.removeAt(idx+1) chunk.instructions.removeAt(idx+1)
} }
changed = true changed = true
} }
@ -55,18 +142,18 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false var changed = false
indexedInstructions.reversed().forEach { (idx, ins) -> indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) { if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
if(idx < chunk.lines.size-1) { if(idx < chunk.instructions.size-1) {
val insAfter = chunk.lines[idx+1] as? IRInstruction val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter?.opcode == ins.opcode) { if(insAfter?.opcode == ins.opcode) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} }
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) { else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} }
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) { else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} }
} }
@ -75,19 +162,24 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed return changed
} }
private fun removeWeirdBranches(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean { private fun removeWeirdBranches(chunk: IRCodeChunk, nextChunk: IRCodeChunkBase?, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// jump/branch to label immediately below
var changed = false var changed = false
indexedInstructions.reversed().forEach { (idx, ins) -> indexedInstructions.reversed().forEach { (idx, ins) ->
val labelSymbol = ins.labelSymbol val labelSymbol = ins.labelSymbol
// remove jump/branch to label immediately below (= next chunk if it has that label)
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) { if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
// if jumping to label immediately following this if(idx==chunk.instructions.size-1 && ins.branchTarget===nextChunk) {
if(idx < chunk.lines.size-1) { chunk.instructions.removeAt(idx)
val label = chunk.lines[idx+1] as? IRCodeLabel changed = true
if(label?.name == labelSymbol) { }
chunk.lines.removeAt(idx) }
changed = true // remove useless RETURN
} if(ins.opcode == Opcode.RETURN && idx>0) {
val previous = chunk.instructions[idx-1] as? IRInstruction
if(previous?.opcode in OpcodesThatJump) {
chunk.instructions.removeAt(idx)
changed = true
} }
} }
} }
@ -101,47 +193,47 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
when (ins.opcode) { when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> { Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
if (ins.value == 1) { if (ins.value == 1) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} }
} }
Opcode.ADD, Opcode.SUB -> { Opcode.ADD, Opcode.SUB -> {
if (ins.value == 1) { if (ins.value == 1) {
chunk.lines[idx] = IRInstruction( chunk.instructions[idx] = IRInstruction(
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC, if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
ins.type, ins.type,
ins.reg1 ins.reg1
) )
changed = true changed = true
} else if (ins.value == 0) { } else if (ins.value == 0) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} }
} }
Opcode.AND -> { Opcode.AND -> {
if (ins.value == 0) { if (ins.value == 0) {
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0) chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
changed = true changed = true
} else if (ins.value == 255 && ins.type == VmDataType.BYTE) { } else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} else if (ins.value == 65535 && ins.type == VmDataType.WORD) { } else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} }
} }
Opcode.OR -> { Opcode.OR -> {
if (ins.value == 0) { if (ins.value == 0) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} else if ((ins.value == 255 && ins.type == VmDataType.BYTE) || (ins.value == 65535 && ins.type == VmDataType.WORD)) { } else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value) chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
changed = true changed = true
} }
} }
Opcode.XOR -> { Opcode.XOR -> {
if (ins.value == 0) { if (ins.value == 0) {
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
changed = true changed = true
} }
} }
@ -156,7 +248,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
indexedInstructions.reversed().forEach { (idx, ins) -> indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.NOP) { if (ins.opcode == Opcode.NOP) {
changed = true changed = true
chunk.lines.removeAt(idx) chunk.instructions.removeAt(idx)
} }
} }
return changed return changed

View File

@ -0,0 +1,123 @@
package prog8.codegen.intermediate
import prog8.code.core.IErrorReporter
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
import prog8.intermediate.*
internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val errors: IErrorReporter) {
fun optimize(): Int {
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.label?.let { allLabeledChunks[it] = chunk }
}
}
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
// remove empty subs
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(libraryFilePrefix))
errors.warn("unused subroutine ${sub.label}", sub.position)
block.children.remove(sub)
numRemoved++
}
}
}
// remove empty blocks
irprog.blocks.reversed().forEach { block ->
if(block.isEmpty()) {
irprog.blocks.remove(block)
numRemoved++
}
}
return numRemoved
}
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
val entrypointSub = irprog.blocks.single { it.name=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
fun grow() {
val new = mutableSetOf<IRCodeChunkBase>()
reachable.forEach {
it.next?.let { next -> new += next }
it.instructions.forEach { instr ->
if (instr.branchTarget == null)
instr.labelSymbol?.let { label -> allLabeledChunks[label]?.let { chunk -> new += chunk } }
else
new += instr.branchTarget!!
}
}
reachable += new
}
var previousCount = reachable.size
while(true) {
grow()
if(reachable.size<=previousCount)
break
previousCount = reachable.size
}
return removeUnlinkedChunks(reachable)
}
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
val linkedChunks = mutableSetOf<IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach {
if(it.branchTarget==null) {
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
} else {
linkedChunks += it.branchTarget!!
}
}
if (chunk.label == "main.start")
linkedChunks += chunk
}
}
return removeUnlinkedChunks(linkedChunks)
}
private fun removeUnlinkedChunks(
linkedChunks: MutableSet<IRCodeChunkBase>
): Int {
var numRemoved = 0
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
if (chunk !in linkedChunks) {
if (chunk === sub.chunks[0]) {
when(chunk) {
is IRCodeChunk -> {
if (chunk.isNotEmpty()) {
// don't remove the first chunk of the sub itself because it has to have the name of the sub as label
chunk.instructions.clear()
numRemoved++
}
}
is IRInlineAsmChunk, is IRInlineBinaryChunk -> {
sub.chunks[index] = IRCodeChunk(chunk.label, chunk.next)
numRemoved++
}
}
} else {
sub.chunks.removeAt(index)
numRemoved++
}
}
}
}
return numRemoved
}
}

View File

@ -1,10 +1,12 @@
package prog8.codegen.intermediate package prog8.codegen.intermediate
import prog8.code.core.AssemblyError import prog8.code.core.AssemblyError
import prog8.intermediate.SyscallRegisterBase
internal class RegisterPool { internal class RegisterPool {
private var firstFree: Int=3 // integer registers 0,1,2 are reserved // reserve 0,1,2 for return values of subroutine calls and syscalls
private var firstFreeFloat: Int=0 private var firstFree: Int=3
private var firstFreeFloat: Int=3
fun peekNext() = firstFree fun peekNext() = firstFree
fun peekNextFloat() = firstFreeFloat fun peekNextFloat() = firstFreeFloat
@ -12,7 +14,7 @@ internal class RegisterPool {
fun nextFree(): Int { fun nextFree(): Int {
val result = firstFree val result = firstFree
firstFree++ firstFree++
if(firstFree>65535) if(firstFree >= SyscallRegisterBase)
throw AssemblyError("out of virtual registers (int)") throw AssemblyError("out of virtual registers (int)")
return result return result
} }
@ -20,7 +22,7 @@ internal class RegisterPool {
fun nextFreeFloat(): Int { fun nextFreeFloat(): Int {
val result = firstFreeFloat val result = firstFreeFloat
firstFreeFloat++ firstFreeFloat++
if(firstFreeFloat>65535) if(firstFreeFloat >= SyscallRegisterBase)
throw AssemblyError("out of virtual registers (fp)") throw AssemblyError("out of virtual registers (fp)")
return result return result
} }

View File

@ -1,4 +1,4 @@
package prog8.vm.codegen package prog8.codegen.vm
import prog8.code.SymbolTable import prog8.code.SymbolTable
import prog8.code.ast.PtProgram import prog8.code.ast.PtProgram
@ -7,10 +7,8 @@ import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram import prog8.code.core.IAssemblyProgram
import prog8.code.core.IErrorReporter import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileReader
import prog8.intermediate.IRFileWriter import prog8.intermediate.IRFileWriter
import prog8.intermediate.IRProgram import prog8.intermediate.IRProgram
import java.nio.file.Path
class VmCodeGen(private val program: PtProgram, class VmCodeGen(private val program: PtProgram,
private val symbolTable: SymbolTable, private val symbolTable: SymbolTable,
@ -22,16 +20,8 @@ class VmCodeGen(private val program: PtProgram,
val irCodeGen = IRCodeGen(program, symbolTable, options, errors) val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate() val irProgram = irCodeGen.generate()
// no need to check options.keepIR, as the VM file format *is* the IR file.
return VmAssemblyProgram(irProgram.name, irProgram) return VmAssemblyProgram(irProgram.name, irProgram)
} }
companion object {
fun compileIR(irFile: Path): IAssemblyProgram {
val irProgram = IRFileReader().read(irFile)
return VmAssemblyProgram(irProgram.name, irProgram)
}
}
} }

View File

@ -6,13 +6,11 @@ import prog8.codegen.intermediate.IRPeepholeOptimizer
import prog8.intermediate.* import prog8.intermediate.*
class TestIRPeepholeOpt: FunSpec({ class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(lines: List<IRCodeLine>): IRProgram { fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
require(chunks.first().label=="main.start")
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY) val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY) val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
val chunk = IRCodeChunk(Position.DUMMY) chunks.forEach { sub += it }
for(line in lines)
chunk += line
sub += chunk
block += sub block += sub
val target = VMTarget() val target = VMTarget()
val options = CompilationOptions( val options = CompilationOptions(
@ -27,44 +25,60 @@ class TestIRPeepholeOpt: FunSpec({
) )
val prog = IRProgram("test", IRSymbolTable(null), options, target) val prog = IRProgram("test", IRSymbolTable(null), options, target)
prog.addBlock(block) prog.addBlock(block)
prog.linkChunks()
prog.validate()
return prog return prog
} }
fun IRProgram.lines(): List<IRCodeLine> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.flatMap { it.lines } fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
val chunk = IRCodeChunk("main.start", null)
instructions.forEach { chunk += it }
return makeIRProgram(listOf(chunk))
}
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
test("remove nops") { test("remove nops") {
val irProg = makeIRProgram(listOf( val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.JUMP, labelSymbol = "dummy"), IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
IRInstruction(Opcode.NOP), IRInstruction(Opcode.NOP),
IRInstruction(Opcode.NOP) IRInstruction(Opcode.NOP)
)) ))
irProg.lines().size shouldBe 3 irProg.chunks().single().instructions.size shouldBe 3
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
irProg.lines().size shouldBe 1 irProg.chunks().single().instructions.size shouldBe 1
} }
test("remove jmp to label below") { test("remove jmp to label below") {
val irProg = makeIRProgram(listOf( val c1 = IRCodeChunk("main.start", null)
IRInstruction(Opcode.JUMP, labelSymbol = "label"), // removed c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
IRCodeLabel("label"), val c2 = IRCodeChunk("label", null)
IRInstruction(Opcode.JUMP, labelSymbol = "label2"), // removed c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
IRInstruction(Opcode.NOP), // removed c2 += IRInstruction(Opcode.NOP) // removed
IRCodeLabel("label2"), val c3 = IRCodeChunk("label2", null)
IRInstruction(Opcode.JUMP, labelSymbol = "label3"), c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
IRInstruction(Opcode.INC, VmDataType.BYTE, reg1=1), c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
IRCodeLabel("label3") val c4 = IRCodeChunk("label3", null)
)) val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
irProg.lines().size shouldBe 8
irProg.chunks().size shouldBe 4
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val lines = irProg.lines() irProg.chunks().size shouldBe 4
lines.size shouldBe 5 irProg.chunks()[0].label shouldBe "main.start"
(lines[0] as IRCodeLabel).name shouldBe "label" irProg.chunks()[1].label shouldBe "label"
(lines[1] as IRCodeLabel).name shouldBe "label2" irProg.chunks()[2].label shouldBe "label2"
(lines[2] as IRInstruction).opcode shouldBe Opcode.JUMP irProg.chunks()[3].label shouldBe "label3"
(lines[3] as IRInstruction).opcode shouldBe Opcode.INC irProg.chunks()[0].isEmpty() shouldBe true
(lines[4] as IRCodeLabel).name shouldBe "label3" irProg.chunks()[1].isEmpty() shouldBe true
irProg.chunks()[2].isEmpty() shouldBe false
irProg.chunks()[3].isEmpty() shouldBe true
val instr = irProg.chunks().flatMap { it.instructions }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.JUMP
instr[1].opcode shouldBe Opcode.INC
} }
test("remove double sec/clc") { test("remove double sec/clc") {
@ -76,102 +90,100 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.CLC), IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC) IRInstruction(Opcode.CLC)
)) ))
irProg.lines().size shouldBe 6 irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val lines = irProg.lines() val instr = irProg.chunks().single().instructions
lines.size shouldBe 1 instr.size shouldBe 1
(lines[0] as IRInstruction).opcode shouldBe Opcode.CLC instr[0].opcode shouldBe Opcode.CLC
} }
test("push followed by pop") { test("push followed by pop") {
val irProg = makeIRProgram(listOf( val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42), IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=42), IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99), IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=222) IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
)) ))
irProg.lines().size shouldBe 4 irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val lines = irProg.lines() val instr = irProg.chunks().single().instructions
lines.size shouldBe 1 instr.size shouldBe 1
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOADR instr[0].opcode shouldBe Opcode.LOADR
(lines[0] as IRInstruction).reg1 shouldBe 222 instr[0].reg1 shouldBe 222
(lines[0] as IRInstruction).reg2 shouldBe 99 instr[0].reg2 shouldBe 99
} }
test("remove useless div/mul, add/sub") { test("remove useless div/mul, add/sub") {
val irProg = makeIRProgram(listOf( val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1), IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1), IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1), IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1), IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2), IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2), IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2), IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2), IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0), IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0) IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
)) ))
irProg.lines().size shouldBe 10 irProg.chunks().single().instructions.size shouldBe 10
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val lines = irProg.lines() irProg.chunks().single().instructions.size shouldBe 4
lines.size shouldBe 4
} }
test("replace add/sub 1 by inc/dec") { test("replace add/sub 1 by inc/dec") {
val irProg = makeIRProgram(listOf( val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1), IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1) IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
)) ))
irProg.lines().size shouldBe 2 irProg.chunks().single().instructions.size shouldBe 2
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val lines = irProg.lines() val instr = irProg.chunks().single().instructions
lines.size shouldBe 2 instr.size shouldBe 2
(lines[0] as IRInstruction).opcode shouldBe Opcode.INC instr[0].opcode shouldBe Opcode.INC
(lines[1] as IRInstruction).opcode shouldBe Opcode.DEC instr[1].opcode shouldBe Opcode.DEC
} }
test("remove useless and/or/xor") { test("remove useless and/or/xor") {
val irProg = makeIRProgram(listOf( val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255), IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535), IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0), IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0), IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200), IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000), IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1), IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1) IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
)) ))
irProg.lines().size shouldBe 8 irProg.chunks().single().instructions.size shouldBe 8
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val lines = irProg.lines() irProg.chunks().single().instructions.size shouldBe 4
lines.size shouldBe 4
} }
test("replace and/or/xor by constant number") { test("replace and/or/xor by constant number") {
val irProg = makeIRProgram(listOf( val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0), IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0), IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255), IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535) IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
)) ))
irProg.lines().size shouldBe 4 irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val lines = irProg.lines() val instr = irProg.chunks().single().instructions
lines.size shouldBe 4 instr.size shouldBe 4
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOAD instr[0].opcode shouldBe Opcode.LOAD
(lines[1] as IRInstruction).opcode shouldBe Opcode.LOAD instr[1].opcode shouldBe Opcode.LOAD
(lines[2] as IRInstruction).opcode shouldBe Opcode.LOAD instr[2].opcode shouldBe Opcode.LOAD
(lines[3] as IRInstruction).opcode shouldBe Opcode.LOAD instr[3].opcode shouldBe Opcode.LOAD
(lines[0] as IRInstruction).value shouldBe 0 instr[0].value shouldBe 0
(lines[1] as IRInstruction).value shouldBe 0 instr[1].value shouldBe 0
(lines[2] as IRInstruction).value shouldBe 255 instr[2].value shouldBe 255
(lines[3] as IRInstruction).value shouldBe 65535 instr[3].value shouldBe 65535
} }
}) })

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -20,7 +20,9 @@ import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html // TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
class ExpressionSimplifier(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() { class ExpressionSimplifier(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet() private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet() private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
@ -191,11 +193,15 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr)) return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
} }
if(expr.operator == ">=" && rightVal?.number == 0.0) { if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) { if(expr.operator == ">=" && rightVal?.number == 0.0) {
// unsigned >= 0 --> true // unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent)) return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
} }
else if(expr.operator == ">" && rightVal?.number == 0.0) {
// unsigned > 0 --> unsigned != 0
return listOf(IAstModification.SetExpression({expr.operator="!="}, expr, parent))
}
} }
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) { if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
@ -204,11 +210,15 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr)) return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
} }
if(expr.operator == "<" && rightVal?.number == 0.0) { if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) { if(expr.operator == "<" && rightVal?.number == 0.0) {
// unsigned < 0 --> false // unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent)) return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
} }
else if(expr.operator == "<=" && rightVal?.number == 0.0) {
// unsigned <= 0 --> unsigned==0
return listOf(IAstModification.SetExpression({expr.operator="=="}, expr, parent))
}
} }
// boolvar & 1 --> boolvar // boolvar & 1 --> boolvar
@ -221,6 +231,18 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
} }
} }
if(leftDt==DataType.BOOL) {
// optimize boolean constant comparisons
// if(expr.operator=="==" && rightVal?.number==0.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
// if(expr.operator=="!=" && rightVal?.number==1.0)
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
if(expr.operator=="==" && rightVal?.number==1.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
if(expr.operator=="!=" && rightVal?.number==0.0)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
}
// simplify when a term is constant and directly determines the outcome // simplify when a term is constant and directly determines the outcome
val constFalse = NumericLiteral.fromBoolean(false, expr.position) val constFalse = NumericLiteral.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) { val newExpr: Expression? = when (expr.operator) {
@ -586,11 +608,13 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) { when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) { if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position) return NumericLiteral(targetDt, 0.0, expr.position)
} }
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD -> {
if (amount >= 16) { if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position) return NumericLiteral(targetDt, 0.0, expr.position)
} }
else if(amount==8) { else if(amount==8) {
@ -605,6 +629,25 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position) return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
} }
} }
DataType.WORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
else if(amount==8) {
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(DataType.UBYTE, 0.0, expr.position)), expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
}
else -> { else -> {
} }
} }
@ -625,6 +668,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
when (idt.getOr(DataType.UNDEFINED)) { when (idt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE -> {
if (amount >= 8) { if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position) return NumericLiteral.optimalInteger(0, expr.position)
} }
} }
@ -636,6 +680,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
} }
DataType.UWORD -> { DataType.UWORD -> {
if (amount >= 16) { if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position) return NumericLiteral.optimalInteger(0, expr.position)
} }
else if(amount==8) { else if(amount==8) {

View File

@ -60,8 +60,8 @@ fun Program.inlineSubroutines(): Int {
return inliner.applyModifications() return inliner.applyModifications()
} }
fun Program.simplifyExpressions(target: ICompilationTarget) : Int { fun Program.simplifyExpressions(errors: IErrorReporter, target: ICompilationTarget) : Int {
val opti = ExpressionSimplifier(this, target) val opti = ExpressionSimplifier(this, errors, target)
opti.visit(this) opti.visit(this)
return opti.applyModifications() return opti.applyModifications()
} }

View File

@ -38,6 +38,8 @@ class Inliner(val program: Program): AstWalker() {
is Return -> { is Return -> {
if(stmt.value is NumericLiteral) if(stmt.value is NumericLiteral)
true true
else if(stmt.value==null)
true
else if (stmt.value is IdentifierReference) { else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference) makeFullyScoped(stmt.value as IdentifierReference)
true true
@ -172,7 +174,9 @@ class Inliner(val program: Program): AstWalker() {
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> { private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
if(sub.inline && sub.parameters.isEmpty()) { if(sub.inline && sub.parameters.isEmpty()) {
require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
}
return if(sub.isAsmSubroutine) { return if(sub.isAsmSubroutine) {
// simply insert the asm for the argument-less routine // simply insert the asm for the argument-less routine
listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent)) listOf(IAstModification.ReplaceNode(origNode, sub.statements.single().copy(), parent))
@ -206,7 +210,9 @@ class Inliner(val program: Program): AstWalker() {
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> { override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
if(sub!=null && sub.inline && sub.parameters.isEmpty()) { if(sub!=null && sub.inline && sub.parameters.isEmpty()) {
require(sub.statements.size==1 || (sub.statements.size==2 && isEmptyReturn(sub.statements[1]))) require(sub.statements.size == 1 || (sub.statements.size == 2 && isEmptyReturn(sub.statements[1]))) {
"invalid inline sub at ${sub.position}"
}
return if(sub.isAsmSubroutine) { return if(sub.isAsmSubroutine) {
// cannot inline assembly directly in the Ast here as an Asm node is not an expression.... // cannot inline assembly directly in the Ast here as an Asm node is not an expression....
noModifications noModifications

View File

@ -97,11 +97,13 @@ class StatementOptimizer(private val program: Program,
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> keep only if-part // always true -> keep only if-part
errors.warn("condition is always true", ifElse.condition.position) if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always true", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent)) listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
} else { } else {
// always false -> keep only else-part // always false -> keep only else-part
errors.warn("condition is always false", ifElse.condition.position) if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always false", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent)) listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
} }
} }
@ -291,9 +293,9 @@ class StatementOptimizer(private val program: Program,
val bexpr=assignment.value as? BinaryExpression val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) { if(bexpr!=null) {
val rightCv = bexpr.right.constValue(program)?.number val rightCv = bexpr.right.constValue(program)?.number
if(bexpr.operator=="-" && rightCv==null) { if(bexpr.operator=="-" && rightCv==null && targetIDt.isInteger) {
if(bexpr.right isSameAs assignment.target) { if(bexpr.right.isSimple && bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation) // X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation, for integers)
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position) val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position) val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
return listOf( return listOf(
@ -355,51 +357,17 @@ class StatementOptimizer(private val program: Program,
} }
} }
// word = msb(word) , word=lsb(word) // word = lsb(word)
if(assignment.target.inferType(program).isWords) { if(assignment.target.inferType(program).isWords) {
var fcall = assignment.value as? FunctionCallExpression var fcall = assignment.value as? FunctionCallExpression
if (fcall == null) if (fcall == null)
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
if (fcall != null && (fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))) { if (fcall != null && (fcall.target.nameInSource == listOf("lsb"))) {
if (fcall.args.single() isSameAs assignment.target) { if (fcall.args.single() isSameAs assignment.target) {
return if (fcall.target.nameInSource == listOf("lsb")) { // optimize word=lsb(word) ==> word &= $00ff
// optimize word=lsb(word) ==> word &= $00ff val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position) val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position) return listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
} else {
// optimize word=msb(word) ==> word >>= 8
val shift8 = BinaryExpression(fcall.args[0], ">>", NumericLiteral(DataType.UBYTE, 8.0, fcall.position), fcall.position)
val newAssign = Assignment(assignment.target, shift8, AssignmentOrigin.OPTIMIZER, fcall.position)
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
}
}
}
}
return noModifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
if(compTarget.name==VMTarget.NAME)
return noModifications
val returnvalue = returnStmt.value
if (returnvalue!=null) {
val dt = returnvalue.inferType(program).getOr(DataType.UNDEFINED)
if(dt!=DataType.UNDEFINED) {
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
// first assign to intermediary variable, then return that
val (returnVarName, _) = program.getTempVar(dt)
val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, returnvalue, AssignmentOrigin.OPTIMIZER, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
)
} }
} }
} }

View File

@ -120,12 +120,26 @@ class UnusedCodeRemover(private val program: Program,
if(singleUse is AssignTarget) { if(singleUse is AssignTarget) {
val assignment = singleUse.parent as Assignment val assignment = singleUse.parent as Assignment
if(assignment.origin==AssignmentOrigin.VARINIT) { if(assignment.origin==AssignmentOrigin.VARINIT) {
if(!decl.definingModule.isLibrary) if(assignment.value.isSimple) {
errors.warn("removing unused variable '${decl.name}'", decl.position) // remove the vardecl
return listOf( if(!decl.definingModule.isLibrary)
IAstModification.Remove(decl, parent as IStatementContainer), errors.warn("removing unused variable '${decl.name}'", decl.position)
IAstModification.Remove(assignment, assignment.parent as IStatementContainer) return listOf(
) IAstModification.Remove(decl, parent as IStatementContainer),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
} else if(assignment.value is IFunctionCall) {
// replace the unused variable's initializer function call by a void
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
} else {
errors.warn("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
}
} }
} }
} }

View File

@ -31,14 +31,16 @@ dependencies {
implementation project(':codeOptimizers') implementation project(':codeOptimizers')
implementation project(':compilerAst') implementation project(':compilerAst')
implementation project(':codeGenCpu6502') implementation project(':codeGenCpu6502')
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental') implementation project(':codeGenExperimental')
implementation project(':virtualmachine') implementation project(':virtualmachine')
implementation 'org.antlr:antlr4-runtime:4.10.1' implementation 'org.antlr:antlr4-runtime:4.11.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect" // implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4' implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16" implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2' testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
} }

View File

@ -21,6 +21,8 @@
<orderEntry type="module" module-name="codeOptimizers" /> <orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="codeGenCpu6502" /> <orderEntry type="module" module-name="codeGenCpu6502" />
<orderEntry type="module" module-name="codeGenExperimental" /> <orderEntry type="module" module-name="codeGenExperimental" />
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" /> <orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" scope="TEST" />
</component> </component>
</module> </module>

View File

@ -56,7 +56,8 @@ romsub $af4b = ROUND() clobbers(A,X,Y) ; round fac1
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1) romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!! romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $af57 = RND() clobbers(A,X,Y) ; alias for RND_0
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2 romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2 romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead) romsub $af60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
@ -151,6 +152,17 @@ asmsub FREADUY (ubyte value @Y) {
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT &uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
sub rndf() -> float {
%asm {{
stx P8ZP_SCRATCH_REG
lda #1
jsr RND_0
ldx P8ZP_SCRATCH_REG
rts
}}
}
%asminclude "library:c128/floats.asm" %asminclude "library:c128/floats.asm"
%asminclude "library:c64/floats_funcs.asm" %asminclude "library:c64/floats_funcs.asm"

View File

@ -12,9 +12,34 @@ c64 {
&ubyte COLOR = $00f1 ; cursor color &ubyte COLOR = $00f1 ; cursor color
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ?? ;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&uword ICRNCH2 = $030c
&uword IQPLOP2 = $030e
&uword IGONE2 = $0310
; $0312 and $0313 are unused.
&uword CINV = $0314 ; IRQ vector (in ram) &uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram) &uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram) &uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
&uword IEXMON = $032e
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in &uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in &uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in &uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
@ -536,15 +561,32 @@ sys {
}} }}
} }
sub wait(uword jiffies) { asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) ; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock ; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies { %asm {{
ubyte jiff = lsb(c64.RDTIM16()) stx P8ZP_SCRATCH_B1
while jiff==lsb(c64.RDTIM16()) { sta P8ZP_SCRATCH_W1
; wait until 1 jiffy has passed sty P8ZP_SCRATCH_W1+1
}
} _loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
ldx P8ZP_SCRATCH_B1
rts
+ lda c64.TIME_LO
sta P8ZP_SCRATCH_B1
- lda c64.TIME_LO
cmp P8ZP_SCRATCH_B1
beq -
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
jmp _loop
}}
} }
asmsub waitvsync() clobbers(A) { asmsub waitvsync() clobbers(A) {

View File

@ -171,6 +171,18 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
&uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT &uword AYINT_facmo = $64 ; $64/$65 contain result of AYINT
sub rndf() -> float {
%asm {{
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx P8ZP_SCRATCH_REG
rts
}}
}
%asminclude "library:c64/floats.asm" %asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm" %asminclude "library:c64/floats_funcs.asm"

View File

@ -11,9 +11,36 @@ c64 {
&ubyte COLOR = $0286 ; cursor color &ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) &ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&ubyte SAREG = $030c ; register storage for A for SYS calls
&ubyte SXREG = $030d ; register storage for X for SYS calls
&ubyte SYREG = $030e ; register storage for Y for SYS calls
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
&uword USRADD = $0311 ; vector for the USR() basic command
; $0313 is unused.
&uword CINV = $0314 ; IRQ vector (in ram) &uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram) &uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram) &uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
&uword USERCMD = $032e
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in &uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in &uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in &uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
@ -500,15 +527,32 @@ sys {
}} }}
} }
sub wait(uword jiffies) { asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) ; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock ; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies { %asm {{
ubyte jiff = lsb(c64.RDTIM16()) stx P8ZP_SCRATCH_B1
while jiff==lsb(c64.RDTIM16()) { sta P8ZP_SCRATCH_W1
; wait until 1 jiffy has passed sty P8ZP_SCRATCH_W1+1
}
} _loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
ldx P8ZP_SCRATCH_B1
rts
+ lda c64.TIME_LO
sta P8ZP_SCRATCH_B1
- lda c64.TIME_LO
cmp P8ZP_SCRATCH_B1
beq -
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
jmp _loop
}}
} }
asmsub waitvsync() clobbers(A) { asmsub waitvsync() clobbers(A) {

View File

@ -1,9 +1,11 @@
; Cx16 specific disk drive I/O routines. ; Cx16 specific disk drive I/O routines.
%import diskio %import diskio
%import string
cx16diskio { cx16diskio {
; Same as diskio.load() but with additional bank parameter to select the Ram bank to load into.
; Use kernal LOAD routine to load the given program file in memory. ; Use kernal LOAD routine to load the given program file in memory.
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1 ; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header. ; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
@ -13,9 +15,10 @@ cx16diskio {
; You can use the load_size() function to calcuate the size of the file that was loaded. ; You can use the load_size() function to calcuate the size of the file that was loaded.
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword { sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
cx16.rambank(bank) cx16.rambank(bank)
return diskio.load(drivenumber, filenameptr, address_override) return diskio.internal_load_routine(drivenumber, filenameptr, address_override, false)
} }
; Same as diskio.load_raw() but with additional bank parameter to select the Ram bank to load into.
; Use kernal LOAD routine to load the given file in memory. ; Use kernal LOAD routine to load the given file in memory.
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file. ; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
; The load address is mandatory. Returns the number of bytes loaded. ; The load address is mandatory. Returns the number of bytes loaded.
@ -23,9 +26,9 @@ cx16diskio {
; or alternatively make sure to reset the correct ram bank yourself after the load! ; or alternatively make sure to reset the correct ram bank yourself after the load!
; Returns the end load address+1 if successful or 0 if a load error occurred. ; Returns the end load address+1 if successful or 0 if a load error occurred.
; You can use the load_size() function to calcuate the size of the file that was loaded. ; You can use the load_size() function to calcuate the size of the file that was loaded.
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword { sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
cx16.rambank(bank) cx16.rambank(bank)
return diskio.load_headerless_cx16(drivenumber, filenameptr, address, true) return diskio.internal_load_routine(drivenumber, filenameptr, address_override, true)
} }
; For use directly after a load or load_raw call (don't mess with the ram bank yet): ; For use directly after a load or load_raw call (don't mess with the ram bank yet):
@ -34,17 +37,22 @@ cx16diskio {
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
} }
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A { asmsub vload(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address ; -- like the basic command VLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A ; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file has to have the usual 2 byte header (which will be skipped)
%asm {{ %asm {{
; -- load a file into video ram clc
internal_vload:
phx phx
pha pha
tya tya
tax tax
lda #1 bcc +
ldy #0 ldy #%00000010 ; headerless load mode
bne ++
+ ldy #0 ; normal load mode
+ lda #1
jsr c64.SETLFS jsr c64.SETLFS
lda cx16.r0 lda cx16.r0
ldy cx16.r0+1 ldy cx16.r0+1
@ -71,9 +79,19 @@ cx16diskio {
}} }}
} }
asmsub vload_raw(str name @R0, ubyte drivenumber @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command BVLOAD "filename",drivenumber,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
; the file is read fully including the first two bytes.
%asm {{
sec
jmp vload.internal_vload
}}
}
; replacement function that makes use of fast block read capability of the X16 ; Replacement function that makes use of fast block read capability of the X16,
; use this in place of regular diskio.f_read() ; and can wrap over multiple ram banks while reading.
; Use this in place of regular diskio.f_read() on X16.
sub f_read(uword bufferpointer, uword num_bytes) -> uword { sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes. ; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions) ; returns the actual number of bytes read. (checks for End-of-file and error conditions)
@ -81,15 +99,6 @@ cx16diskio {
return 0 return 0
diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read diskio.list_blocks = 0 ; we reuse this variable for the total number of bytes read
if diskio.have_first_byte {
diskio.have_first_byte=false
@(bufferpointer) = diskio.first_byte
bufferpointer++
diskio.list_blocks++
num_bytes--
}
void c64.CHKIN(11) ; use #11 as input channel again
; commander X16 supports fast block-read via macptr() kernal call ; commander X16 supports fast block-read via macptr() kernal call
uword size uword size
@ -97,7 +106,7 @@ cx16diskio {
size = 255 size = 255
if num_bytes<size if num_bytes<size
size = num_bytes size = num_bytes
size = cx16.macptr(lsb(size), bufferpointer) size = cx16.macptr(lsb(size), bufferpointer, false)
if_cs if_cs
goto byte_read_loop ; macptr block read not supported, do fallback loop goto byte_read_loop ; macptr block read not supported, do fallback loop
diskio.list_blocks += size diskio.list_blocks += size
@ -119,54 +128,107 @@ byte_read_loop: ; fallback if macptr() isn't supported on the device
lda bufferpointer+1 lda bufferpointer+1
sta m_in_buffer+2 sta m_in_buffer+2
}} }}
repeat num_bytes { while num_bytes {
if c64.READST() {
diskio.f_close()
if c64.READST() & $40 ; eof?
return diskio.list_blocks ; number of bytes read
return 0 ; error.
}
%asm {{ %asm {{
jsr c64.CHRIN jsr c64.CHRIN
sta cx16.r5
m_in_buffer sta $ffff m_in_buffer sta $ffff
inc m_in_buffer+1 inc m_in_buffer+1
bne + bne +
inc m_in_buffer+2 inc m_in_buffer+2
+ inc diskio.list_blocks
bne +
inc diskio.list_blocks+1
+ +
}} }}
diskio.list_blocks++
if cx16.r5==$0d { ; chance on I/o error status? num_bytes--
diskio.first_byte = c64.READST()
if diskio.first_byte & $40 {
diskio.f_close() ; end of file, close it
diskio.list_blocks-- ; don't count that last CHRIN read
}
if diskio.first_byte
return diskio.list_blocks ; number of bytes read
}
} }
return diskio.list_blocks ; number of bytes read return diskio.list_blocks ; number of bytes read
} }
; replacement function that makes use of fast block read capability of the X16 ; replacement function that makes use of fast block read capability of the X16
; use this in place of regular diskio.f_read_all() ; use this in place of regular diskio.f_read_all() on X16
sub f_read_all(uword bufferpointer) -> uword { sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read. ; -- read the full contents of the file, returns number of bytes read.
if not diskio.iteration_in_progress if not diskio.iteration_in_progress
return 0 return 0
uword total_read = 0 uword total_read = 0
if diskio.have_first_byte {
diskio.have_first_byte=false
@(bufferpointer) = diskio.first_byte
bufferpointer++
total_read = 1
}
while not c64.READST() { while not c64.READST() {
uword size = cx16diskio.f_read(bufferpointer, 256) cx16.r0 = cx16diskio.f_read(bufferpointer, 256)
total_read += size total_read += cx16.r0
bufferpointer += size bufferpointer += cx16.r0
} }
return total_read return total_read
} }
sub chdir(ubyte drivenumber, str path) {
; -- change current directory.
diskio.list_filename[0] = 'c'
diskio.list_filename[1] = 'd'
diskio.list_filename[2] = ':'
void string.copy(path, &diskio.list_filename+3)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub mkdir(ubyte drivenumber, str name) {
; -- make a new subdirectory.
diskio.list_filename[0] = 'm'
diskio.list_filename[1] = 'd'
diskio.list_filename[2] = ':'
void string.copy(name, &diskio.list_filename+3)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub rmdir(ubyte drivenumber, str name) {
; -- remove a subdirectory.
void string.find(name, '*')
if_cs
return ; refuse to act on a wildcard *
diskio.list_filename[0] = 'r'
diskio.list_filename[1] = 'd'
diskio.list_filename[2] = ':'
void string.copy(name, &diskio.list_filename+3)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub relabel(ubyte drivenumber, str name) {
; -- change the disk label.
diskio.list_filename[0] = 'r'
diskio.list_filename[1] = '-'
diskio.list_filename[2] = 'h'
diskio.list_filename[3] = ':'
void string.copy(name, &diskio.list_filename+4)
diskio.send_command(drivenumber, diskio.list_filename)
}
sub f_seek(uword pos_hiword, uword pos_loword) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
ubyte[6] command = ['p',0,0,0,0,0]
command[1] = 12 ; f_open uses channel 12
command[2] = lsb(pos_loword)
command[3] = msb(pos_loword)
command[4] = lsb(pos_hiword)
command[5] = msb(pos_hiword)
send_command:
c64.SETNAM(sizeof(command), &command)
c64.SETLFS(15, diskio.last_drivenumber, 15)
void c64.OPEN()
c64.CLOSE(15)
}
; TODO see if we can get this to work as well:
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
; cx16diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
; cx16diskio.f_seek.command[2] = lsb(pos_loword)
; cx16diskio.f_seek.command[3] = msb(pos_loword)
; cx16diskio.f_seek.command[4] = lsb(pos_hiword)
; cx16diskio.f_seek.command[5] = msb(pos_hiword)
; goto cx16diskio.f_seek.send_command
; }
} }

View File

@ -29,7 +29,7 @@ romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!] ; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!]
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15) ; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order) ; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point romsub $fe0f = FLOATC() clobbers(A,X,Y) ; convert address to floating point
@ -57,7 +57,8 @@ romsub $fe4b = ROUND() clobbers(A,X,Y) ; round fac1
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1) romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: special cx16 setup required, use RND() stub instead!! romsub $fe57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator NOTE: incompatible with C64's RND routine
romsub $fe57 = RND() clobbers(A,X,Y) ; alias for RND_0
romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2 romsub $fe5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2 romsub $fe5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead) romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1 (use MOVFM instead)
@ -67,18 +68,19 @@ romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded) romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
; X16 additions ; X16 additions
romsub $fe81 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe84 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0 romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe87 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?) romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe8a = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!) romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
romsub $fe8d = MUL10() clobbers(A,X,Y) ; fac1 *= 10 romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe90 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe93 = MOVEF() clobbers(A,X) ; copy fac1 to fac2 romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe96 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1) romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe99 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
romsub $fe9c = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
romsub $fe9f = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC) romsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
romsub $fea2 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A romsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
asmsub FREADSA (byte value @A) clobbers(A,X,Y) { asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
@ -147,19 +149,21 @@ asmsub FREADUY (ubyte value @Y) {
}} }}
} }
asmsub RND() clobbers(A,X,Y) {
%asm {{
lda #0
php
jsr cx16.entropy_get
plp
jmp RND_0
}}
}
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT &uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
sub rndf() -> float {
%asm {{
phx
lda #1
jsr RND_0
plx
rts
}}
}
%asminclude "library:c64/floats.asm" %asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm" %asminclude "library:c64/floats_funcs.asm"
} }

View File

@ -82,9 +82,10 @@ gfx2 {
bpp = 2 bpp = 2
} }
else -> { else -> {
; back to default text mode and colors ; back to default text mode
cx16.VERA_CTRL = %10000000 ; reset VERA and palette cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
c64.CINT() ; back to text mode c64.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
width = 0 width = 0
height = 0 height = 0
bpp = 0 bpp = 0
@ -366,7 +367,7 @@ _done
position2(x,y,true) position2(x,y,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
color &= 3 color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3] color <<= gfx2.plot.shift4c[lsb(x) & 3] ; TODO with lookup table
ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3] ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat lheight { repeat lheight {
%asm {{ %asm {{
@ -561,7 +562,11 @@ _done
and #1 and #1
}} }}
if_nz { if_nz {
cx16.r0L = lsb(x) & 7 ; xbits %asm {{
lda x
and #7
pha ; xbits
}}
x /= 8 x /= 8
x += y*(320/8) x += y*(320/8)
%asm {{ %asm {{
@ -571,7 +576,7 @@ _done
sta cx16.VERA_ADDR_M sta cx16.VERA_ADDR_M
lda x lda x
sta cx16.VERA_ADDR_L sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits ply ; xbits
lda bits,y lda bits,y
ldy color ldy color
beq + beq +
@ -608,7 +613,11 @@ _done
and #1 and #1
}} }}
if_nz { if_nz {
cx16.r0L = lsb(x) & 7 ; xbits %asm {{
lda x
and #7
pha ; xbits
}}
x /= 8 x /= 8
x += y*(640/8) x += y*(640/8)
%asm {{ %asm {{
@ -618,7 +627,7 @@ _done
sta cx16.VERA_ADDR_M sta cx16.VERA_ADDR_M
lda x lda x
sta cx16.VERA_ADDR_L sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits ply ; xbits
lda bits,y lda bits,y
ldy color ldy color
beq + beq +
@ -635,7 +644,7 @@ _done
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte) void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(x) & 3 ; xbits cx16.r2L = lsb(x) & 3 ; xbits
color &= 3 color &= 3
color <<= shift4c[cx16.r2L] color <<= shift4c[cx16.r2L] ; TODO with lookup table
%asm {{ %asm {{
stz cx16.VERA_CTRL stz cx16.VERA_CTRL
lda cx16.r1L lda cx16.r1L
@ -654,6 +663,93 @@ _done
} }
} }
sub pget(uword @zp x, uword y) -> ubyte {
when active_mode {
1 -> {
; lores monochrome
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.bits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
; TODO mode 2 and 3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
sta cx16.VERA_ADDR_H
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
}}
}
5 -> {
; hires monochrome
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ply ; xbits
lda plot.bits,y
and cx16.VERA_DATA0
beq +
lda #1
+
}}
}
6 -> {
; hires 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.VERA_DATA0
sta cx16.r0L
}}
cx16.r1L = lsb(x) & 3
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L] ; TODO with lookup table
return cx16.r0L & 3
}
else -> return 0
}
}
sub position(uword @zp x, uword y) { sub position(uword @zp x, uword y) {
ubyte bank ubyte bank
when active_mode { when active_mode {
@ -823,7 +919,7 @@ _done
y++ y++
%asm {{ %asm {{
phx phx
ldx #1 ldx color
lda cx16.VERA_DATA1 lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1 sta P8ZP_SCRATCH_B1
ldy #8 ldy #8

View File

@ -26,7 +26,7 @@ palette {
} }
sub set_rgb(uword palette_words_ptr, uword num_colors) { sub set_rgb(uword palette_words_ptr, uword num_colors) {
; 1 word per color entry (in little endian format as layed out in video memory, so $gb0r) ; 1 word per color entry (in little endian format as layed out in video memory, so $gb;$0r)
vera_palette_ptr = $fa00 vera_palette_ptr = $fa00
repeat num_colors*2 { repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr)) cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))

View File

@ -85,6 +85,18 @@ asmsub RDTIM16() -> uword @AY {
cx16 { cx16 {
; irq, system and hardware vectors: ; irq, system and hardware vectors:
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&ubyte SAREG = $030c ; register storage for A for SYS calls
&ubyte SXREG = $030d ; register storage for X for SYS calls
&ubyte SYREG = $030e ; register storage for Y for SYS calls
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
&uword USRADD = $0311 ; vector for the USR() basic command
; $0313 is unused.
&uword CINV = $0314 ; IRQ vector (in ram) &uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram) &uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram) &uword NMINV = $0318 ; NMI vector (in ram)
@ -290,8 +302,9 @@ cx16 {
&ubyte d2ier = via2+14 &ubyte d2ier = via2+14
&ubyte d2ora = via2+15 &ubyte d2ora = via2+15
&ubyte ym2151adr = $9f40 ; YM-2151 sound chip
&ubyte ym2151dat = $9f41 &ubyte YM_ADDRESS = $9f40
&ubyte YM_DATA = $9f41
const uword extdev = $9f60 const uword extdev = $9f60
@ -363,7 +376,7 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y) romsub $fecc = monitor() clobbers(A,X,Y)
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY) clobbers(A) -> ubyte @Pc, uword @XY romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y) romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y) romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time() romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
@ -393,9 +406,10 @@ asmsub kbdbuf_clear() {
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) { asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you ; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
%asm {{ %asm {{
pha ; save shape
sec sec
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
lda #1 pla ; get shape back
jmp cx16.mouse_config jmp cx16.mouse_config
}} }}
} }
@ -446,13 +460,23 @@ inline asmsub getrambank() -> ubyte @A {
}} }}
} }
asmsub numbanks() -> ubyte @A { asmsub numbanks() -> uword @AY {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb) ; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
; Note that the number of such banks can be bigger than 255 so a word is returned, but mostly
; the A register could suffice as the lsb.
; The maximum number of banks is 256 = 2 Megabytes of banked Ram aka Hiram. (Y=1 and A=0 in this case).
; MEMTOP itself reports 0 in this case which we change into 256 for convenience.
; It reporting 0 doesn't mean 'zero banks', instead it means 256 banks (=2Mb banked RAM),
; as there is no X16 without at least 1 page of banked RAM.
%asm {{ %asm {{
phx phx
sec sec
jsr c64.MEMTOP jsr c64.MEMTOP
plx ldy #0
cmp #0
bne +
iny
+ plx
rts rts
}} }}
} }
@ -611,12 +635,19 @@ asmsub init_system() {
%asm {{ %asm {{
sei sei
cld cld
lda VERA_DC_VIDEO
and #%00000111 ; retain chroma + output mode
sta P8ZP_SCRATCH_REG
lda #$80 lda #$80
sta VERA_CTRL sta VERA_CTRL ; reset vera
stz $01 ; select rom bank 0 (enable kernal) stz $01 ; select rom bank 0 (enable kernal)
jsr c64.IOINIT jsr c64.IOINIT
jsr c64.RESTOR jsr c64.RESTOR
jsr c64.CINT jsr c64.CINT
lda VERA_DC_VIDEO
and #%11111000
ora P8ZP_SCRATCH_REG
sta VERA_DC_VIDEO ; keep old output mode
lda #$90 ; black lda #$90 ; black
jsr c64.CHROUT jsr c64.CHROUT
lda #1 ; swap fg/bg lda #1 ; swap fg/bg
@ -887,17 +918,29 @@ sys {
asmsub wait(uword jiffies @AY) { asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1) ; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: regular system vsync irq handler must be running, and no nother irqs ; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
%asm {{ %asm {{
- wai ; wait for irq (assume it was vsync) phx
cmp #0 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne + bne +
dey plx
+ dec a
bne -
cpy #0
bne -
rts rts
+ jsr c64.RDTIM
sta P8ZP_SCRATCH_B1
- jsr c64.RDTIM
cmp P8ZP_SCRATCH_B1
beq -
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
bra _loop
}} }}
} }

View File

@ -14,9 +14,10 @@ cx16logo {
sub logo() { sub logo() {
uword strptr uword strptr
for strptr in logo_lines for strptr in logo_lines {
txt.print(strptr) txt.print(strptr)
txt.nl() txt.nl()
}
} }
str[] logo_lines = [ str[] logo_lines = [

View File

@ -10,12 +10,12 @@ diskio {
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success. ; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
c64.SETNAM(1, "$") c64.SETNAM(1, "$")
c64.SETLFS(13, drivenumber, 0) c64.SETLFS(12, drivenumber, 0)
ubyte status = 1 ubyte status = 1
void c64.OPEN() ; open 13,8,0,"$" void c64.OPEN() ; open 12,8,0,"$"
if_cs if_cs
goto io_error goto io_error
void c64.CHKIN(13) ; use #13 as input channel void c64.CHKIN(12) ; use #12 as input channel
if_cs if_cs
goto io_error goto io_error
@ -23,7 +23,7 @@ diskio {
void c64.CHRIN() ; skip the 4 prologue bytes void c64.CHRIN() ; skip the 4 prologue bytes
} }
; while not key pressed / EOF encountered, read data. ; while not stop key pressed / EOF encountered, read data.
status = c64.READST() status = c64.READST()
if status!=0 { if status!=0 {
status = 1 status = 1
@ -53,7 +53,7 @@ diskio {
io_error: io_error:
c64.CLRCHN() ; restore default i/o devices c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(13) c64.CLOSE(12)
if status and status & $40 == 0 { ; bit 6=end of file if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ") txt.print("\ni/o error, status: ")
@ -69,12 +69,12 @@ io_error:
; -- Returns pointer to disk name string or 0 if failure. ; -- Returns pointer to disk name string or 0 if failure.
c64.SETNAM(1, "$") c64.SETNAM(1, "$")
c64.SETLFS(13, drivenumber, 0) c64.SETLFS(12, drivenumber, 0)
ubyte okay = false ubyte okay = false
void c64.OPEN() ; open 13,8,0,"$" void c64.OPEN() ; open 12,8,0,"$"
if_cs if_cs
goto io_error goto io_error
void c64.CHKIN(13) ; use #13 as input channel void c64.CHKIN(12) ; use #12 as input channel
if_cs if_cs
goto io_error goto io_error
@ -84,7 +84,6 @@ io_error:
if c64.READST()!=0 if c64.READST()!=0
goto io_error goto io_error
; while not key pressed / EOF encountered, read data.
cx16.r0 = &list_filename cx16.r0 = &list_filename
repeat { repeat {
@(cx16.r0) = c64.CHRIN() @(cx16.r0) = c64.CHRIN()
@ -96,7 +95,7 @@ io_error:
io_error: io_error:
c64.CLRCHN() ; restore default i/o devices c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(13) c64.CLOSE(12)
if okay if okay
return &list_filename return &list_filename
return 0 return 0
@ -107,33 +106,37 @@ io_error:
uword list_pattern uword list_pattern
uword list_blocks uword list_blocks
bool iteration_in_progress = false bool iteration_in_progress = false
ubyte @zp first_byte ubyte last_drivenumber = 8 ; which drive was last used for a f_open operation?
bool have_first_byte str list_filetype = "???" ; prg, seq, dir
str list_filename = "?" * 50 str list_filename = "?" * 50
; ----- get a list of files (uses iteration functions internally) ----- ; ----- get a list of files (uses iteration functions internally) -----
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte { sub list_filenames(ubyte drivenumber, uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. Returns number of files. ; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
const uword filenames_buf_size = 800 ; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
uword filenames_buffer = memory("filenames", filenames_buf_size, 0) ; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
uword buffer_start = filenames_buffer uword buffer_start = filenames_buffer
ubyte files_found = 0 ubyte files_found = 0
if lf_start_list(drivenumber, pattern_ptr) { if lf_start_list(drivenumber, pattern_ptr) {
while lf_next_entry() { while lf_next_entry() {
@(name_ptrs) = lsb(filenames_buffer) if list_filetype!="dir" {
name_ptrs++ filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
@(name_ptrs) = msb(filenames_buffer) files_found++
name_ptrs++ if filenames_buffer - buffer_start > filenames_buf_size-20 {
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1 @(filenames_buffer)=0
files_found++ lf_end_list()
if filenames_buffer - buffer_start > filenames_buf_size-18 sys.set_carry()
break return files_found
if files_found == max_names }
break }
} }
lf_end_list() lf_end_list()
} }
@(filenames_buffer)=0
sys.clear_carry()
return files_found return files_found
} }
@ -170,7 +173,7 @@ io_error:
sub lf_next_entry() -> bool { sub lf_next_entry() -> bool {
; -- retrieve the next entry from an iterative file listing session. ; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks and list_filename. ; results will be found in list_blocks, list_filename, and list_filetype.
; if it returns false though, there are no more entries (or an error occurred). ; if it returns false though, there are no more entries (or an error occurred).
if not iteration_in_progress if not iteration_in_progress
@ -207,6 +210,12 @@ io_error:
@(nameptr) = 0 @(nameptr) = 0
do {
cx16.r15L = c64.CHRIN()
} until cx16.r15L!=' ' ; skip blanks up to 3 chars entry type
list_filetype[0] = cx16.r15L
list_filetype[1] = c64.CHRIN()
list_filetype[2] = c64.CHRIN()
while c64.CHRIN() { while c64.CHRIN() {
; read the rest of the entry until the end ; read the rest of the entry until the end
} }
@ -238,7 +247,7 @@ close_end:
} }
; ----- iterative file loader functions (uses io channel 11) ----- ; ----- iterative file loader functions (uses io channel 12) -----
sub f_open(ubyte drivenumber, uword filenameptr) -> bool { sub f_open(ubyte drivenumber, uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read ; -- open a file for iterative reading with f_read
@ -246,17 +255,19 @@ close_end:
f_close() f_close()
c64.SETNAM(string.length(filenameptr), filenameptr) c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(11, drivenumber, 0) c64.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
void c64.OPEN() ; open 11,8,0,"filename" last_drivenumber = drivenumber
void c64.OPEN() ; open 12,8,12,"filename"
if_cc { if_cc {
if c64.READST()==0 { if c64.READST()==0 {
iteration_in_progress = true iteration_in_progress = true
have_first_byte = false void c64.CHKIN(12) ; use #12 as input channel
void c64.CHKIN(11) ; use #11 as input channel
if_cc { if_cc {
first_byte = c64.CHRIN() ; read first byte to test for file not found void c64.CHRIN() ; read first byte to test for file not found
if not c64.READST() { if not c64.READST() {
have_first_byte = true c64.CLOSE(12) ; close file because we already consumed first byte
void c64.OPEN() ; re-open the file
void c64.CHKIN(12)
return true return true
} }
} }
@ -271,19 +282,11 @@ close_end:
; returns the actual number of bytes read. (checks for End-of-file and error conditions) ; returns the actual number of bytes read. (checks for End-of-file and error conditions)
; NOTE: on systems with banked ram (such as Commander X16) this routine DOES NOT ; NOTE: on systems with banked ram (such as Commander X16) this routine DOES NOT
; automatically load into subsequent banks if it reaches a bank boundary! ; automatically load into subsequent banks if it reaches a bank boundary!
; Consider using cx16diskio.f_read() on X16.
if not iteration_in_progress or not num_bytes if not iteration_in_progress or not num_bytes
return 0 return 0
list_blocks = 0 ; we reuse this variable for the total number of bytes read list_blocks = 0 ; we reuse this variable for the total number of bytes read
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
list_blocks++
num_bytes--
}
void c64.CHKIN(11) ; use #11 as input channel again
%asm {{ %asm {{
lda bufferpointer lda bufferpointer
@ -291,50 +294,38 @@ close_end:
lda bufferpointer+1 lda bufferpointer+1
sta m_in_buffer+2 sta m_in_buffer+2
}} }}
repeat num_bytes { while num_bytes {
if c64.READST() {
f_close()
if c64.READST() & $40 ; eof?
return list_blocks ; number of bytes read
return 0 ; error.
}
%asm {{ %asm {{
jsr c64.CHRIN jsr c64.CHRIN
sta cx16.r5
m_in_buffer sta $ffff m_in_buffer sta $ffff
inc m_in_buffer+1 inc m_in_buffer+1
bne + bne +
inc m_in_buffer+2 inc m_in_buffer+2
+ inc list_blocks
bne +
inc list_blocks+1
+ +
}} }}
list_blocks++
if cx16.r5==$0d { ; chance on I/o error status? num_bytes--
first_byte = c64.READST()
if first_byte & $40 {
f_close() ; end of file, close it
list_blocks-- ; don't count that last CHRIN read
}
if first_byte
return list_blocks ; number of bytes read
}
} }
return list_blocks ; number of bytes read return list_blocks ; number of bytes read
} }
sub f_read_all(uword bufferpointer) -> uword { sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read. ; -- read the full contents of the file, returns number of bytes read.
; Note: Consider using cx16diskio.f_read_all() on X16!
if not iteration_in_progress if not iteration_in_progress
return 0 return 0
uword total_read = 0 uword total_read = 0
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
total_read = 1
}
while not c64.READST() { while not c64.READST() {
uword size = f_read(bufferpointer, 256) cx16.r0 = f_read(bufferpointer, 256)
total_read += size total_read += cx16.r0
bufferpointer += size bufferpointer += cx16.r0
} }
return total_read return total_read
} }
@ -348,16 +339,9 @@ m_in_buffer sta $ffff
%asm {{ %asm {{
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
ldx #11 ldx #12
jsr c64.CHKIN ; use channel 11 again for input jsr c64.CHKIN ; use channel 12 again for input
ldy #0 ldy #0
lda have_first_byte
beq _loop
lda #0
sta have_first_byte
lda first_byte
sta (P8ZP_SCRATCH_W1),y
iny
_loop jsr c64.CHRIN _loop jsr c64.CHRIN
sta (P8ZP_SCRATCH_W1),y sta (P8ZP_SCRATCH_W1),y
beq _end beq _end
@ -378,23 +362,23 @@ _end rts
; -- end an iterative file loading session (close channels). ; -- end an iterative file loading session (close channels).
if iteration_in_progress { if iteration_in_progress {
c64.CLRCHN() c64.CLRCHN()
c64.CLOSE(11) c64.CLOSE(12)
iteration_in_progress = false iteration_in_progress = false
} }
} }
; ----- iterative file saver functions (uses io channel 14) ----- ; ----- iterative file writing functions (uses io channel 13) -----
sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool { sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write ; -- open a file for iterative writing with f_write
f_close_w() f_close_w()
c64.SETNAM(string.length(filenameptr), filenameptr) c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(14, drivenumber, 1) c64.SETLFS(13, drivenumber, 1)
void c64.OPEN() ; open 14,8,1,"filename" void c64.OPEN() ; open 13,8,1,"filename"
if_cc { if_cc {
void c64.CHKOUT(14) ; use #14 as input channel c64.CHKOUT(13) ; use #13 as output channel
return not c64.READST() return not c64.READST()
} }
f_close_w() f_close_w()
@ -402,9 +386,9 @@ _end rts
} }
sub f_write(uword bufferpointer, uword num_bytes) -> bool { sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given umber of bytes to the currently open file ; -- write the given number of bytes to the currently open file
if num_bytes!=0 { if num_bytes!=0 {
void c64.CHKOUT(14) ; use #14 as input channel again c64.CHKOUT(13) ; use #13 as output channel again
repeat num_bytes { repeat num_bytes {
c64.CHROUT(@(bufferpointer)) c64.CHROUT(@(bufferpointer))
bufferpointer++ bufferpointer++
@ -417,7 +401,7 @@ _end rts
sub f_close_w() { sub f_close_w() {
; -- end an iterative file writing session (close channels). ; -- end an iterative file writing session (close channels).
c64.CLRCHN() c64.CLRCHN()
c64.CLOSE(14) c64.CLOSE(13)
} }
@ -436,10 +420,10 @@ _end rts
goto io_error goto io_error
while not c64.READST() { while not c64.READST() {
first_byte = c64.CHRIN() cx16.r5L = c64.CHRIN()
if first_byte=='\r' or first_byte=='\n' if cx16.r5L=='\r' or cx16.r5L=='\n'
break break
@(messageptr) = first_byte @(messageptr) = cx16.r5L
messageptr++ messageptr++
} }
@(messageptr) = 0 @(messageptr) = 0
@ -458,7 +442,7 @@ io_error:
c64.SETNAM(string.length(filenameptr), filenameptr) c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0) c64.SETLFS(1, drivenumber, 0)
uword @shared end_address = address + size uword @shared end_address = address + size
first_byte = 0 ; result var reuse cx16.r0L = 0
%asm {{ %asm {{
lda address lda address
@ -476,12 +460,12 @@ io_error:
}} }}
if_cc if_cc
first_byte = c64.READST()==0 cx16.r0L = c64.READST()==0
c64.CLRCHN() c64.CLRCHN()
c64.CLOSE(1) c64.CLOSE(1)
return first_byte return cx16.r0L
} }
; Use kernal LOAD routine to load the given program file in memory. ; Use kernal LOAD routine to load the given program file in memory.
@ -493,9 +477,9 @@ io_error:
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks ; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct, ; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account. ; because it doesn't take the number of ram banks into account.
; Consider using cx16diskio.load() instead. ; Also consider using cx16diskio.load() instead on the Commander X16.
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword { sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
return load_headerless_cx16(drivenumber, filenameptr, address_override, false) return internal_load_routine(drivenumber, filenameptr, address_override, false)
} }
; Use kernal LOAD routine to load the given file in memory. ; Use kernal LOAD routine to load the given file in memory.
@ -506,10 +490,10 @@ io_error:
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks ; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct, ; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account. ; because it doesn't take the number of ram banks into account.
; Consider using cx16diskio.load_raw() instead on the Commander X16. ; Also consider using cx16diskio.load_raw() instead on the Commander X16.
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword { sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
if sys.target==16 ; are we on commander X16? if sys.target==16 ; are we on commander X16?
return load_headerless_cx16(drivenumber, filenameptr, address, true) return internal_load_routine(drivenumber, filenameptr, address, true)
; fallback to reading the 2 header bytes separately ; fallback to reading the 2 header bytes separately
if not f_open(drivenumber, filenameptr) if not f_open(drivenumber, filenameptr)
return 0 return 0
@ -524,7 +508,8 @@ io_error:
; Internal routine, only to be used on Commander X16 platform if headerless=true, ; Internal routine, only to be used on Commander X16 platform if headerless=true,
; because this routine uses kernal support for that to load headerless files. ; because this routine uses kernal support for that to load headerless files.
sub load_headerless_cx16(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword { ; On C64 it will always be called with headerless=false.
sub internal_load_routine(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
c64.SETNAM(string.length(filenameptr), filenameptr) c64.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1 ubyte secondary = 1
cx16.r1 = 0 cx16.r1 = 0

View File

@ -221,13 +221,18 @@ sub ceil(float value) -> float {
}} }}
} }
sub rndf() -> float { sub rndseedf(float seed) {
if seed>0
seed = -seed ; make sure fp seed is always negative
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx floats_store_reg
lda #1 lda #<seed
jsr FREADSA ldy #>seed
jsr RND ; rng into fac1 jsr MOVFM ; load float into fac1
ldx P8ZP_SCRATCH_REG lda #-1
jsr floats.RND
ldx floats_store_reg
rts rts
}} }}
} }

View File

@ -229,60 +229,32 @@ _divisor .word 0
.pend .pend
randseed .proc
; -- reset the random seeds for the byte and word random generators
; arguments: uword seed in A/Y clobbers A
; (default starting values are: A=$2c Y=$9e)
sta randword._seed
sty randword._seed+1
clc
adc #14
sta randbyte._seed
rts
.pend
randbyte .proc
; -- 8 bit pseudo random number generator into A (by just reusing randword)
jmp randword
.pend
randword .proc randword .proc
; -- 16 bit pseudo random number generator into AY ; -- 16 bit pseudo random number generator into AY
; default seed = $00c2 $1137
; rand64k ;Factors of 65535: 3 5 17 257 ; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
lda sr1+1 inc x1
asl a clc
asl a x1=*+1
eor sr1+1 lda #$00 ;x1
asl a c1=*+1
eor sr1+1 eor #$c2 ;c1
asl a a1=*+1
asl a eor #$11 ;a1
eor sr1+1 sta a1
asl a b1=*+1
rol sr1 ;shift this left, "random" bit comes from low adc #$37 ;b1
rol sr1+1 sta b1
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined lsr a
lda sr2+1 eor a1
asl a adc c1
eor sr2+1 sta c1
asl a ldy b1
asl a
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
rol sr2+1
lda sr1+1 ;can be left out
eor sr2+1 ;if you dont use
tay ;y as suggested
lda sr1 ;mix up lowbytes of SR1
eor sr2 ;and SR2 to combine both
rts rts
sr1 .word $a55a
sr2 .word $7653
.pend .pend
randbyte = randword ; -- 8 bit pseudo random number generator into A (by just reusing randword)
; ----------- optimized multiplications (stack) : --------- ; ----------- optimized multiplications (stack) : ---------
stack_mul_byte_3 .proc stack_mul_byte_3 .proc

View File

@ -71,4 +71,28 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}} }}
} }
asmsub rnd() -> ubyte @A {
%asm {{
jmp math.randbyte
}}
}
asmsub rndw() -> uword @AY {
%asm {{
jmp math.randword
}}
}
asmsub rndseed(uword seed1 @AY, uword seed2 @R0) clobbers(A,Y) {
; -- set new pseudo RNG's seed values. Defaults are: $00c2, $1137
%asm {{
sta math.randword.x1
sty math.randword.c1
lda cx16.r0L
sta math.randword.a1
lda cx16.r0H
sta math.randword.b1
rts
}}
}
} }

View File

@ -244,24 +244,6 @@ func_sqrt16_into_A .proc
rts rts
.pend .pend
func_rnd_stack .proc
; -- put a random ubyte on the estack
jsr math.randbyte
sta P8ESTACK_LO,x
dex
rts
.pend
func_rndw_stack .proc
; -- put a random uword on the estack
jsr math.randword
sta P8ESTACK_LO,x
tya
sta P8ESTACK_HI,x
dex
rts
.pend
func_sort_ub .proc func_sort_ub .proc
; 8bit unsigned sort ; 8bit unsigned sort

View File

@ -1082,6 +1082,7 @@ containment_wordarray .proc
iny iny
cmp (P8ZP_SCRATCH_W2),y cmp (P8ZP_SCRATCH_W2),y
beq _found beq _found
dey
+ dey + dey
dey dey
cpy #254 cpy #254

View File

@ -195,8 +195,8 @@ sub str2uword(str string) -> uword {
; -- returns the unsigned word value of the string number argument in AY ; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces ; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed) ; (any non-digit character will terminate the number string that is parsed)
%asm {{ %ir {{
loadm.w r0,conv.str2uword.string loadm.w r65500,conv.str2uword.string
syscall 11 syscall 11
return return
}} }}
@ -206,8 +206,8 @@ sub str2word(str string) -> word {
; -- returns the signed word value of the string number argument in AY ; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces ; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed) ; (any non-digit character will terminate the number string that is parsed)
%asm {{ %ir {{
loadm.w r0,conv.str2word.string loadm.w r65500,conv.str2word.string
syscall 12 syscall 12
return return
}} }}

View File

@ -9,15 +9,15 @@ floats {
sub print_f(float value) { sub print_f(float value) {
; ---- prints the floating point value (without a newline). ; ---- prints the floating point value (without a newline).
%asm {{ %ir {{
loadm.f fr0,floats.print_f.value loadm.f fr65500,floats.print_f.value
syscall 25 syscall 25
return return
}} }}
} }
sub pow(float value, float power) -> float { sub pow(float value, float power) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.pow.value loadm.f fr0,floats.pow.value
loadm.f fr1,floats.pow.power loadm.f fr1,floats.pow.power
fpow.f fr0,fr1 fpow.f fr0,fr1
@ -26,7 +26,7 @@ sub pow(float value, float power) -> float {
} }
sub fabs(float value) -> float { sub fabs(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.fabs.value loadm.f fr0,floats.fabs.value
fabs.f fr0,fr0 fabs.f fr0,fr0
return return
@ -34,7 +34,7 @@ sub fabs(float value) -> float {
} }
sub sin(float angle) -> float { sub sin(float angle) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.sin.angle loadm.f fr0,floats.sin.angle
fsin.f fr0,fr0 fsin.f fr0,fr0
return return
@ -42,7 +42,7 @@ sub sin(float angle) -> float {
} }
sub cos(float angle) -> float { sub cos(float angle) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.cos.angle loadm.f fr0,floats.cos.angle
fcos.f fr0,fr0 fcos.f fr0,fr0
return return
@ -50,7 +50,7 @@ sub cos(float angle) -> float {
} }
sub tan(float value) -> float { sub tan(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.tan.value loadm.f fr0,floats.tan.value
ftan.f fr0,fr0 ftan.f fr0,fr0
return return
@ -58,7 +58,7 @@ sub tan(float value) -> float {
} }
sub atan(float value) -> float { sub atan(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.atan.value loadm.f fr0,floats.atan.value
fatan.f fr0,fr0 fatan.f fr0,fr0
return return
@ -66,7 +66,7 @@ sub atan(float value) -> float {
} }
sub ln(float value) -> float { sub ln(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.ln.value loadm.f fr0,floats.ln.value
fln.f fr0,fr0 fln.f fr0,fr0
return return
@ -74,7 +74,7 @@ sub ln(float value) -> float {
} }
sub log2(float value) -> float { sub log2(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.log2.value loadm.f fr0,floats.log2.value
flog.f fr0,fr0 flog.f fr0,fr0
return return
@ -82,7 +82,7 @@ sub log2(float value) -> float {
} }
sub sqrt(float value) -> float { sub sqrt(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.sqrt.value loadm.f fr0,floats.sqrt.value
sqrt.f fr0,fr0 sqrt.f fr0,fr0
return return
@ -100,7 +100,7 @@ sub deg(float angle) -> float {
} }
sub round(float value) -> float { sub round(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.round.value loadm.f fr0,floats.round.value
fround.f fr0,fr0 fround.f fr0,fr0
return return
@ -108,7 +108,7 @@ sub round(float value) -> float {
} }
sub floor(float value) -> float { sub floor(float value) -> float {
%asm {{ %ir {{
loadm.f fr0,floats.floor.value loadm.f fr0,floats.floor.value
ffloor.f fr0,fr0 ffloor.f fr0,fr0
return return
@ -117,7 +117,7 @@ sub floor(float value) -> float {
sub ceil(float value) -> float { sub ceil(float value) -> float {
; -- ceil: tr = int(f); if tr==f -> return else return tr+1 ; -- ceil: tr = int(f); if tr==f -> return else return tr+1
%asm {{ %ir {{
loadm.f fr0,floats.ceil.value loadm.f fr0,floats.ceil.value
fceil.f fr0,fr0 fceil.f fr0,fr0
return return
@ -125,9 +125,17 @@ sub ceil(float value) -> float {
} }
sub rndf() -> float { sub rndf() -> float {
%asm {{ %ir {{
rnd.f fr0 syscall 35
return return
}} }}
} }
sub rndseedf(float seed) {
%ir {{
loadm.f fr65500,floats.rndseedf.seed
syscall 32
}}
}
} }

View File

@ -159,4 +159,27 @@ math {
return costab[radians] as byte return costab[radians] as byte
} }
sub rnd() -> ubyte {
%ir {{
syscall 33
return
}}
}
sub rndw() -> uword {
%ir {{
syscall 34
return
}}
}
sub rndseed(uword seed1, uword seed2) {
; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653.
%ir {{
loadm.w r65500,math.rndseed.seed1
loadm.w r65501,math.rndseed.seed2
syscall 31
return
}}
}
} }

View File

@ -1,53 +1,6 @@
; Internal library routines - always included by the compiler ; Internal library routines - always included by the compiler
%import textio
prog8_lib { prog8_lib {
%option force_output %option force_output
sub string_contains(ubyte needle, str haystack) -> ubyte {
repeat {
if @(haystack)==0
return false
if @(haystack)==needle
return true
haystack++
}
}
sub bytearray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
haystack_ptr--
while num_elements {
if haystack_ptr[num_elements]==needle
return true
num_elements--
}
return false
}
sub wordarray_contains(ubyte needle, uword haystack_ptr, ubyte num_elements) -> ubyte {
haystack_ptr += (num_elements-1) * 2
while num_elements {
if peekw(haystack_ptr)==needle
return true
haystack_ptr -= 2
num_elements--
}
return false
}
sub string_compare(str st1, str st2) -> byte {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%asm {{
loadm.w r0,prog8_lib.string_compare.st1
loadm.w r1,prog8_lib.string_compare.st2
syscall 29
return
}}
}
} }

View File

@ -83,7 +83,12 @@ string {
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2. ; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using ; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically). ; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
return prog8_lib.string_compare(st1, st2) %ir {{
loadm.w r65500,string.compare.st1
loadm.w r65501,string.compare.st2
syscall 29
return
}}
} }
sub lower(str st) -> ubyte { sub lower(str st) -> ubyte {

View File

@ -7,22 +7,22 @@ sys {
sub reset_system() { sub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt. ; Soft-reset the system back to initial power-on Basic prompt.
%asm {{ %ir {{
syscall 0 syscall 0
}} }}
} }
sub wait(uword jiffies) { sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds) ; --- wait approximately the given number of jiffies (1/60th seconds)
%asm {{ %ir {{
loadm.w r0,sys.wait.jiffies loadm.w r65500,sys.wait.jiffies
syscall 13 syscall 13
}} }}
} }
sub waitvsync() { sub waitvsync() {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling. ; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
%asm {{ %ir {{
syscall 14 syscall 14
}} }}
} }
@ -61,52 +61,52 @@ sys {
sub exit(ubyte returnvalue) { sub exit(ubyte returnvalue) {
; -- immediately exit the program with a return code in the A register ; -- immediately exit the program with a return code in the A register
%asm {{ %ir {{
loadm.b r0,sys.exit.returnvalue loadm.b r65500,sys.exit.returnvalue
syscall 1 syscall 1
}} }}
} }
sub set_carry() { sub set_carry() {
%asm {{ %ir {{
sec sec
}} }}
} }
sub clear_carry() { sub clear_carry() {
%asm {{ %ir {{
clc clc
}} }}
} }
sub gfx_enable(ubyte mode) { sub gfx_enable(ubyte mode) {
%asm {{ %ir {{
loadm.b r0,sys.gfx_enable.mode loadm.b r65500,sys.gfx_enable.mode
syscall 8 syscall 8
}} }}
} }
sub gfx_clear(ubyte color) { sub gfx_clear(ubyte color) {
%asm {{ %ir {{
loadm.b r0,sys.gfx_clear.color loadm.b r65500,sys.gfx_clear.color
syscall 9 syscall 9
}} }}
} }
sub gfx_plot(uword xx, uword yy, ubyte color) { sub gfx_plot(uword xx, uword yy, ubyte color) {
%asm {{ %ir {{
loadm.w r0,sys.gfx_plot.xx loadm.w r65500,sys.gfx_plot.xx
loadm.w r1,sys.gfx_plot.yy loadm.w r65501,sys.gfx_plot.yy
loadm.b r2,sys.gfx_plot.color loadm.b r65502,sys.gfx_plot.color
syscall 10 syscall 10
}} }}
} }
sub gfx_getpixel(uword xx, uword yy) -> ubyte { sub gfx_getpixel(uword xx, uword yy) -> ubyte {
%asm {{ %ir {{
loadm.w r0,sys.gfx_getpixel.xx loadm.w r65500,sys.gfx_getpixel.xx
loadm.w r1,sys.gfx_getpixel.yy loadm.w r65501,sys.gfx_getpixel.yy
syscall 30 syscall 30
return return
}} }}

View File

@ -6,8 +6,8 @@ txt {
sub clear_screen() { sub clear_screen() {
str @shared sequence = "\x1b[2J\x1B[H" str @shared sequence = "\x1b[2J\x1B[H"
%asm {{ %ir {{
load.w r0,txt.clear_screen.sequence load.w r65500,txt.clear_screen.sequence
syscall 3 syscall 3
}} }}
} }
@ -29,15 +29,15 @@ sub uppercase() {
} }
sub chrout(ubyte char) { sub chrout(ubyte char) {
%asm {{ %ir {{
loadm.b r0,txt.chrout.char loadm.b r65500,txt.chrout.char
syscall 2 syscall 2
}} }}
} }
sub print (str text) { sub print (str text) {
%asm {{ %ir {{
loadm.w r0,txt.print.text loadm.w r65500,txt.print.text
syscall 3 syscall 3
}} }}
} }
@ -113,8 +113,8 @@ sub print_w (word value) {
sub input_chars (uword buffer) -> ubyte { sub input_chars (uword buffer) -> ubyte {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well) ; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel! ; It assumes the keyboard is selected as I/O channel!
%asm {{ %ir {{
loadm.w r0,txt.input_chars.buffer loadm.w r65500,txt.input_chars.buffer
syscall 6 syscall 6
return return
}} }}

View File

@ -1 +1 @@
8.6.2 8.9

View File

@ -41,10 +41,9 @@ private fun compileMain(args: Array<String>): Boolean {
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation") val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation") val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen") val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val keepIR by cli.option(ArgType.Boolean, fullName = "keepIR", description = "keep the IR code file (for targets that use it)")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)") val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental)")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)") val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results") val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
@ -127,7 +126,6 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true, quietAssembler == true,
asmListfile == true, asmListfile == true,
experimentalCodegen == true, experimentalCodegen == true,
keepIR == true,
compilationTarget, compilationTarget,
evalStackAddr, evalStackAddr,
processedSymbols, processedSymbols,
@ -192,7 +190,6 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true, quietAssembler == true,
asmListfile == true, asmListfile == true,
experimentalCodegen == true, experimentalCodegen == true,
keepIR == true,
compilationTarget, compilationTarget,
evalStackAddr, evalStackAddr,
processedSymbols, processedSymbols,

View File

@ -14,10 +14,10 @@ import prog8.ast.walk.IAstVisitor
import prog8.code.SymbolTable import prog8.code.SymbolTable
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.* import prog8.code.target.*
import prog8.codegen.vm.VmCodeGen
import prog8.compiler.astprocessing.* import prog8.compiler.astprocessing.*
import prog8.optimizer.* import prog8.optimizer.*
import prog8.parser.ParseError import prog8.parser.ParseError
import prog8.vm.codegen.VmCodeGen
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension import kotlin.io.path.nameWithoutExtension
@ -38,7 +38,6 @@ class CompilerArguments(val filepath: Path,
val quietAssembler: Boolean, val quietAssembler: Boolean,
val asmListfile: Boolean, val asmListfile: Boolean,
val experimentalCodegen: Boolean, val experimentalCodegen: Boolean,
val keepIR: Boolean,
val compilationTarget: String, val compilationTarget: String,
val evalStackBaseAddress: UInt?, val evalStackBaseAddress: UInt?,
val symbolDefs: Map<String, String>, val symbolDefs: Map<String, String>,
@ -81,7 +80,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
asmQuiet = args.quietAssembler asmQuiet = args.quietAssembler
asmListfile = args.asmListfile asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen experimentalCodegen = args.experimentalCodegen
keepIR = args.keepIR
evalStackBaseAddress = args.evalStackBaseAddress evalStackBaseAddress = args.evalStackBaseAddress
outputDir = args.outputDir.normalize() outputDir = args.outputDir.normalize()
symbolDefs = args.symbolDefs symbolDefs = args.symbolDefs
@ -95,7 +93,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
processAst(program, args.errors, compilationOptions) processAst(program, args.errors, compilationOptions)
if (compilationOptions.optimize) { if (compilationOptions.optimize) {
// println("*********** AST RIGHT BEFORE OPTIMIZING *************") // println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program) // printProgram(program)
optimizeAst( optimizeAst(
@ -108,7 +106,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
} }
postprocessAst(program, args.errors, compilationOptions) postprocessAst(program, args.errors, compilationOptions)
// println("*********** AST BEFORE ASSEMBLYGEN *************") // println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
// printProgram(program) // printProgram(program)
determineProgramLoadAddress(program, compilationOptions, args.errors) determineProgramLoadAddress(program, compilationOptions, args.errors)
@ -361,7 +359,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
remover.applyModifications() remover.applyModifications()
while (true) { while (true) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions(compTarget) val optsDone1 = program.simplifyExpressions(errors, compTarget)
val optsDone2 = program.splitBinaryExpressions(compilerOptions) val optsDone2 = program.splitBinaryExpressions(compilerOptions)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget) val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
val optsDone4 = program.inlineSubroutines() val optsDone4 = program.inlineSubroutines()
@ -382,7 +380,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
callGraph.checkRecursiveCalls(errors) callGraph.checkRecursiveCalls(errors)
program.verifyFunctionArgTypes(errors) program.verifyFunctionArgTypes(errors)
errors.report() errors.report()
program.moveMainAndStartToFirst() program.moveMainBlockAsFirst()
program.checkValid(errors, compilerOptions) // check if final tree is still valid program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report() errors.report()
} }
@ -391,10 +389,10 @@ private fun createAssemblyAndAssemble(program: Program,
errors: IErrorReporter, errors: IErrorReporter,
compilerOptions: CompilationOptions compilerOptions: CompilationOptions
): Boolean { ): Boolean {
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions) compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors) program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report() errors.report()
val symbolTable = SymbolTableMaker().makeFrom(program) val symbolTable = SymbolTableMaker(program, compilerOptions).make()
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast, // TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow. // or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
@ -402,7 +400,7 @@ private fun createAssemblyAndAssemble(program: Program,
// to help clean up the code that still depends on them. // to help clean up the code that still depends on them.
// removeAllVardeclsFromAst(program) // removeAllVardeclsFromAst(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************") // println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program) // printProgram(program)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly() val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
@ -448,14 +446,14 @@ internal fun asmGeneratorFor(program: Program,
options: CompilationOptions): IAssemblyGenerator options: CompilationOptions): IAssemblyGenerator
{ {
if(options.experimentalCodegen) { if(options.experimentalCodegen) {
val intermediateAst = IntermediateAstMaker(program).transform() val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors) return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
} else { } else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02)) if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation // TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors) return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
if (options.compTarget.name == VMTarget.NAME) { if (options.compTarget.name == VMTarget.NAME) {
val intermediateAst = IntermediateAstMaker(program).transform() val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return VmCodeGen(intermediateAst, symbolTable, options, errors) return VmCodeGen(intermediateAst, symbolTable, options, errors)
} }
} }

View File

@ -0,0 +1,49 @@
package prog8.compiler.astprocessing
import prog8.ast.statements.Block
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
import prog8.code.core.ICompilationTarget
import prog8.code.target.VMTarget
class AsmInstructionNamesFinder(val target: ICompilationTarget): IAstVisitor {
val blocks = mutableSetOf<Block>()
val variables = mutableSetOf<VarDecl>()
val labels = mutableSetOf<Label>()
val subroutines = mutableSetOf<Subroutine>()
private fun isPossibleInstructionName(name: String) = name.length==3 && name.all { it.isLetter() }
fun foundAny(): Boolean = blocks.isNotEmpty() || variables.isNotEmpty() || subroutines.isNotEmpty() || labels.isNotEmpty()
override fun visit(block: Block) {
if(target.name!=VMTarget.NAME && !block.isInLibrary && isPossibleInstructionName(block.name)) {
blocks += block
}
super.visit(block)
}
override fun visit(decl: VarDecl) {
if(target.name!=VMTarget.NAME && !decl.definingModule.isLibrary && isPossibleInstructionName(decl.name)) {
variables += decl
}
super.visit(decl)
}
override fun visit(label: Label) {
if(target.name!=VMTarget.NAME && !label.definingModule.isLibrary && isPossibleInstructionName(label.name)) {
labels += label
}
super.visit(label)
}
override fun visit(subroutine: Subroutine) {
if(target.name!=VMTarget.NAME && !subroutine.definingModule.isLibrary && isPossibleInstructionName(subroutine.name)) {
subroutines += subroutine
}
super.visit(subroutine)
}
}

View File

@ -0,0 +1,101 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AsmInstructionNamesReplacer(
val blocks: Set<Block>,
val subroutines: Set<Subroutine>,
val variables: Set<VarDecl>,
val labels: Set<Label>): AstWalker() {
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
val newName = identifier.nameInSource.map { ident ->
if(ident.length==3 && !identifier.definingModule.isLibrary) {
val blockTarget = blocks.firstOrNull { it.name==ident }
val subTarget = subroutines.firstOrNull {it.name==ident }
val varTarget = variables.firstOrNull { it.name==ident }
val labelTarget = labels.firstOrNull { it.name==ident}
if(blockTarget!=null || subTarget!=null || varTarget!=null || labelTarget!=null) {
"p8p_$ident"
} else
ident
} else
ident
}
return if(newName!=identifier.nameInSource)
listOf(IAstModification.ReplaceNode(identifier, IdentifierReference(newName, identifier.position), parent))
else
noModifications
}
override fun after(label: Label, parent: Node): Iterable<IAstModification> {
return if(label in labels)
listOf(IAstModification.ReplaceNode(label, Label("p8p_${label.name}", label.position), parent))
else
noModifications
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
return if(block in blocks)
listOf(IAstModification.ReplaceNode(block, Block("p8p_${block.name}", block.address, block.statements, block.isInLibrary, block.position), parent))
else
noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
return if(decl in variables)
listOf(IAstModification.ReplaceNode(decl, decl.renamed("p8p_${decl.name}"), parent))
else
noModifications
}
private val subsWithParamRefsToFix = mutableListOf<Subroutine>()
override fun applyModifications(): Int {
var count = super.applyModifications()
subsWithParamRefsToFix.forEach { subroutine ->
subroutine.statements.withIndex().reversed().forEach { (index,stmt) ->
if(stmt is VarDecl && stmt.origin==VarDeclOrigin.SUBROUTINEPARAM) {
val param = subroutine.parameters.single { it.name == stmt.name}
val decl = VarDecl.fromParameter(param)
subroutine.statements[index] = decl
decl.linkParents(subroutine)
count++
}
}
}
return count
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val changedParams = mutableListOf<Pair<Int, SubroutineParameter>>()
subroutine.parameters.withIndex().forEach { (index, param) ->
if(param.name.length==3 && param.name.all { it.isLetter() } && !param.definingModule.isLibrary) {
changedParams.add(index to SubroutineParameter("p8p_${param.name}", param.type, param.position))
}
}
changedParams.forEach { (index, newParam) -> subroutine.parameters[index] = newParam }
val newName = if(subroutine in subroutines) "p8p_${subroutine.name}" else subroutine.name
return if(newName!=subroutine.name || changedParams.isNotEmpty()) {
val newSub = Subroutine(newName, subroutine.parameters, subroutine.returntypes,
subroutine.asmParameterRegisters, subroutine.asmReturnvaluesRegisters, subroutine.asmClobbers, subroutine.asmAddress, subroutine.isAsmSubroutine,
subroutine.inline, subroutine.statements, subroutine.position)
if(changedParams.isNotEmpty())
subsWithParamRefsToFix += newSub
listOf(IAstModification.ReplaceNode(subroutine, newSub, parent))
} else {
if(changedParams.isNotEmpty())
subsWithParamRefsToFix += subroutine
noModifications
}
}
}

View File

@ -61,6 +61,14 @@ internal class AstChecker(private val program: Program,
} }
} }
override fun visit(identifier: IdentifierReference) {
val targetParam = identifier.targetVarDecl(program)?.subroutineParameter
if(targetParam!=null) {
if((targetParam.parent as Subroutine).isAsmSubroutine)
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
}
}
override fun visit(returnStmt: Return) { override fun visit(returnStmt: Return) {
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList() val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
if(expectedReturnValues.size>1) { if(expectedReturnValues.size>1) {
@ -252,16 +260,8 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(inlineAssembly: InlineAssembly) { override fun visit(inlineAssembly: InlineAssembly) {
val assembly = inlineAssembly.assembly if(inlineAssembly.hasReturnOrRts())
if(compilerOptions.compTarget.name!=VMTarget.NAME) { count++
if (" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly
)
count++
} else {
if(" return" in assembly || "\treturn" in assembly || " jump" in assembly || "\tjump" in assembly)
count++
}
} }
} }
@ -308,6 +308,8 @@ internal class AstChecker(private val program: Program,
err("subroutines can only be defined in the scope of a block or within another subroutine") err("subroutines can only be defined in the scope of a block or within another subroutine")
if(subroutine.isAsmSubroutine) { if(subroutine.isAsmSubroutine) {
if(compilerOptions.compTarget.name==VMTarget.NAME)
err("cannot use asmsub for vm target, use regular subs")
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size) if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
err("number of asm parameter registers is not the isSameAs as number of parameters") err("number of asm parameter registers is not the isSameAs as number of parameters")
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size) if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
@ -319,12 +321,12 @@ internal class AstChecker(private val program: Program,
} }
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT) && param.first.type != DataType.STR && param.first.type !in ArrayDatatypes)
err("parameter '${param.first.name}' should be (u)word (an address) or str") err("parameter '${param.first.name}' should be (u)word (an address) or str")
} }
else if(param.second.statusflag!=null) { else if(param.second.statusflag!=null) {
if (param.first.type != DataType.UBYTE) if (param.first.type != DataType.UBYTE && param.first.type != DataType.BOOL)
err("parameter '${param.first.name}' should be ubyte") err("parameter '${param.first.name}' should be bool or ubyte")
} }
} }
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair -> subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
@ -334,12 +336,12 @@ internal class AstChecker(private val program: Program,
} }
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (pair.first != DataType.UWORD && pair.first != DataType.WORD if (pair.first != DataType.UWORD && pair.first != DataType.WORD
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT) && pair.first != DataType.STR && pair.first !in ArrayDatatypes)
err("return type #${index + 1} should be (u)word/address") err("return type #${index + 1} should be (u)word/address")
} }
else if(pair.second.statusflag!=null) { else if(pair.second.statusflag!=null) {
if (pair.first != DataType.UBYTE) if (pair.first != DataType.UBYTE && pair.first != DataType.BOOL)
err("return type #${index + 1} should be ubyte") err("return type #${index + 1} should be bool or ubyte")
} }
} }
@ -518,7 +520,7 @@ internal class AstChecker(private val program: Program,
val sourceDatatype = assignment.value.inferType(program) val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype.isUnknown) { if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCallExpression) if (assignment.value !is FunctionCallExpression)
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position) errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
} else { } else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED), checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value) sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
@ -843,6 +845,8 @@ internal class AstChecker(private val program: Program,
else if(expr.operator == "~") { else if(expr.operator == "~") {
if(dt !in IntegerDatatypes) if(dt !in IntegerDatatypes)
errors.err("can only use bitwise invert on integer types", expr.position) errors.err("can only use bitwise invert on integer types", expr.position)
if(dt==DataType.BOOL)
errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position)
} }
super.visit(expr) super.visit(expr)
} }
@ -852,8 +856,15 @@ internal class AstChecker(private val program: Program,
val leftIDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown) if(!leftIDt.isKnown || !rightIDt.isKnown) {
// check if maybe one of the operands is a label, this would need a '&'
if (!leftIDt.isKnown && expr.left !is FunctionCallExpression)
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.left.position)
if (!rightIDt.isKnown && expr.right !is FunctionCallExpression)
errors.err("invalid operand, maybe forgot '&' (address-of)", expr.right.position)
return // hopefully this error will be detected elsewhere return // hopefully this error will be detected elsewhere
}
val leftDt = leftIDt.getOr(DataType.UNDEFINED) val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED) val rightDt = rightIDt.getOr(DataType.UNDEFINED)
@ -1005,7 +1016,7 @@ internal class AstChecker(private val program: Program,
// It's not (yet) possible to handle these multiple return values because assignments // It's not (yet) possible to handle these multiple return values because assignments
// are only to a single unique target at the same time. // are only to a single unique target at the same time.
// EXCEPTION: // EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit, // if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
// it *is* possible to handle them by just actually assigning the register value and // it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call. // dealing with the status bit as just being that, the status bit after the call.
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null } val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
@ -1037,6 +1048,23 @@ internal class AstChecker(private val program: Program,
if(targetStatement!=null) { if(targetStatement!=null) {
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position) checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
checkUnusedReturnValues(functionCallStatement, targetStatement, errors) checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
if(functionCallStatement.void) {
when(targetStatement) {
is BuiltinFunctionPlaceholder -> {
if(!builtinFunctionReturnType(targetStatement.name).isKnown)
errors.warn("redundant void", functionCallStatement.position)
}
is Label -> {
errors.warn("redundant void", functionCallStatement.position)
}
is Subroutine -> {
if(targetStatement.returntypes.isEmpty())
errors.warn("redundant void", functionCallStatement.position)
}
else -> {}
}
}
} }
val funcName = functionCallStatement.target.nameInSource val funcName = functionCallStatement.target.nameInSource
@ -1244,8 +1272,14 @@ internal class AstChecker(private val program: Program,
val elementDt = containment.element.inferType(program) val elementDt = containment.element.inferType(program)
val iterableDt = containment.iterable.inferType(program) val iterableDt = containment.iterable.inferType(program)
if(containment.parent is BinaryExpression) if(compilerOptions.compTarget.name!=VMTarget.NAME) {
errors.err("containment check is currently not supported inside complex expressions", containment.position) val parentBinexpr = containment.parent as? BinaryExpression
if(parentBinexpr!=null) {
// only supported if compared to 1 or 0, more complex expressions aren't supported in the 6502 code-gen.
if(parentBinexpr.operator!="==" || parentBinexpr.right.constValue(program)?.number !in listOf(0.0, 1.0))
errors.err("containment check is currently not supported inside complex expressions", containment.position)
}
}
if(iterableDt.isIterable && containment.iterable !is RangeExpression) { if(iterableDt.isIterable && containment.iterable !is RangeExpression) {
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED)) val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))

View File

@ -27,6 +27,21 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
val boolRemover = BoolRemover(this) val boolRemover = BoolRemover(this)
boolRemover.visit(this) boolRemover.visit(this)
boolRemover.applyModifications() boolRemover.applyModifications()
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
val finder = AsmInstructionNamesFinder(compilerOptions.compTarget)
finder.visit(this)
if(finder.foundAny()) {
val replacer = AsmInstructionNamesReplacer(
finder.blocks,
finder.subroutines,
finder.variables,
finder.labels)
replacer.visit(this)
replacer.applyModifications()
}
}
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors) val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
fixer.visit(this) fixer.visit(this)
while (errors.noErrors() && fixer.applyModifications() > 0) { while (errors.noErrors() && fixer.applyModifications() > 0) {
@ -130,35 +145,19 @@ internal fun Program.variousCleanups(errors: IErrorReporter, options: Compilatio
} }
} }
internal fun Program.moveMainAndStartToFirst() { internal fun Program.moveMainBlockAsFirst() {
// The module containing the program entrypoint is moved to the first in the sequence. // The module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there, // the "main" block containing the entrypoint is moved to the top in there.
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
// sortModules() val module = this.entrypoint.definingModule
val directives = modules[0].statements.filterIsInstance<Directive>() val block = this.entrypoint.definingBlock
val start = this.entrypoint moveModuleToFront(module)
val mod = start.definingModule module.remove(block)
val block = start.definingBlock val afterDirective = module.statements.indexOfFirst { it !is Directive }
moveModuleToFront(mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0) if(afterDirective<0)
mod.statements.add(block) module.statements.add(block)
else else
mod.statements.add(afterDirective, block) module.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
} }
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean { internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
@ -169,16 +168,9 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
return false return false
} }
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean { internal fun Subroutine.hasRtsInAsm(): Boolean {
val instructions =
if(compTarget.name == VMTarget.NAME)
listOf(" return", "\treturn", " jump", "\tjump")
else
listOf(" rti", "\trti", " rts", "\trts", " jmp", "\tjmp", " bra", "\tbra")
return statements return statements
.asSequence() .asSequence()
.filterIsInstance<InlineAssembly>() .filterIsInstance<InlineAssembly>()
.any { .any { it.hasReturnOrRts() }
instructions.any { instr->instr in it.assembly }
}
} }

View File

@ -10,6 +10,7 @@ import prog8.ast.walk.IAstVisitor
import prog8.code.core.ICompilationTarget import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter import prog8.code.core.IErrorReporter
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions import prog8.compiler.BuiltinFunctions
@ -28,9 +29,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
} }
override fun visit(block: Block) { override fun visit(block: Block) {
if(block.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name] val existing = blocks[block.name]
if(existing!=null) { if(existing!=null) {
if(block.isInLibrary) if(block.isInLibrary)
@ -50,9 +48,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
if(decl.name in BuiltinFunctions) if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position) errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name)) val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
if(existingInSameScope!=null && existingInSameScope!==decl) if(existingInSameScope!=null && existingInSameScope!==decl)
nameError(decl.name, decl.position, existingInSameScope) nameError(decl.name, decl.position, existingInSameScope)
@ -74,9 +69,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
} }
override fun visit(subroutine: Subroutine) { override fun visit(subroutine: Subroutine) {
if(subroutine.name in compTarget.machine.opcodeNames) { if(subroutine.name in BuiltinFunctions) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", subroutine.position) errors.err("builtin function cannot be redefined", subroutine.position)
} else { } else {
@ -86,7 +79,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
val existing = subroutine.lookup(listOf(subroutine.name)) val existing = subroutine.lookup(listOf(subroutine.name))
if (existing != null && existing !== subroutine) { if (existing != null && existing !== subroutine) {
if(existing.parent!==existing.parent) if(existing.parent!==subroutine.parent && existing is Subroutine)
nameShadowWarning(subroutine.name, existing.position, subroutine) nameShadowWarning(subroutine.name, existing.position, subroutine)
else else
nameError(subroutine.name, existing.position, subroutine) nameError(subroutine.name, existing.position, subroutine)
@ -107,10 +100,12 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position) errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
} }
if(subroutine.name == subroutine.definingBlock.name) { if(compTarget.name != VMTarget.NAME) {
// subroutines cannot have the same name as their enclosing block, if (subroutine.name == subroutine.definingBlock.name) {
// because this causes symbol scoping issues in the resulting assembly source // subroutines cannot have the same name as their enclosing block,
nameError(subroutine.name, subroutine.position, subroutine.definingBlock) // because this causes symbol scoping issues in the resulting assembly source
errors.err("name conflict '${subroutine.name}', also defined at ${subroutine.definingBlock.position} (64tass scope nesting limitation)", subroutine.position)
}
} }
} }
@ -118,11 +113,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
} }
override fun visit(label: Label) { override fun visit(label: Label) {
if(label.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) { if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", label.position) errors.err("builtin function cannot be redefined", label.position)
} else { } else {
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name) val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
@ -150,6 +141,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement) override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
private fun visitFunctionCall(call: IFunctionCall) { private fun visitFunctionCall(call: IFunctionCall) {
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
val target = call.target.targetStatement(program)
if(target==null) {
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
return
}
}
when (val target = call.target.targetStatement(program)) { when (val target = call.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
val expectedNumberOfArgs: Int = target.parameters.size val expectedNumberOfArgs: Int = target.parameters.size

View File

@ -18,9 +18,8 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(parent !is VarDecl) { if(parent !is VarDecl) {
// TODO move this / remove this, and make the codegen better instead. if(options.compTarget.name == VMTarget.NAME)
// If the expression is pointervar[idx] where pointervar is uword and not a real array, return noModifications // vm codegen deals correctly with all cases
// replace it by a @(pointervar+idx) expression.
// Don't replace the initializer value in a vardecl - this will be moved to a separate // Don't replace the initializer value in a vardecl - this will be moved to a separate
// assignment statement soon in after(VarDecl) // assignment statement soon in after(VarDecl)
return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent) return replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression, parent)
@ -29,15 +28,16 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
} }
private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { private fun replacePointerVarIndexWithMemreadOrMemwrite(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(options.compTarget.name==VMTarget.NAME) // note: The CodeDesugarer already does something similar, but that is meant ONLY to take
return noModifications // vm codegen deals correctly with all cases // into account the case where the index value is a word type.
// The replacement here is to fix missing cases in the 6502 codegen.
// TODO make the 6502 codegen better so this work around can be removed
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program) val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) { if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
if(parent is AssignTarget) { if(parent is AssignTarget) {
val assignment = parent.parent as? Assignment val assignment = parent.parent as? Assignment
if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) { if(assignment?.value is NumericLiteral || assignment?.value is IdentifierReference) {
// ONLY for a constant assignment, or direct variable assignment, the codegen contains correct optimized code. // the codegen contains correct optimized code ONLY for a constant assignment, or direct variable assignment.
return noModifications return noModifications
} }
// Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases. // Other cases aren't covered correctly by the 6502 codegen, and there are a LOT of cases.

View File

@ -120,16 +120,19 @@ class AstPreprocessor(val program: Program,
movements.add(IAstModification.InsertFirst(decl, parentscope)) movements.add(IAstModification.InsertFirst(decl, parentscope))
replacements.add(IAstModification.Remove(decl, scope)) replacements.add(IAstModification.Remove(decl, scope))
} else { } else {
val declToInsert: VarDecl
if(decl.value!=null && decl.datatype in NumericDatatypes) { if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position) val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope)) replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null decl.value = null
decl.allowInitializeWithZero = false decl.allowInitializeWithZero = false
declToInsert = decl.copy()
} else { } else {
replacements.add(IAstModification.Remove(decl, scope)) replacements.add(IAstModification.Remove(decl, scope))
declToInsert = decl
} }
movements.add(IAstModification.InsertFirst(decl, parentscope)) movements.add(IAstModification.InsertFirst(declToInsert, parentscope))
} }
} }
return movements + replacements return movements + replacements
@ -138,12 +141,15 @@ class AstPreprocessor(val program: Program,
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// this has to be done here becuse otherwise the string / range literal values will have been replaced by variables
if(expr.operator=="in") { if(expr.operator=="in") {
val containment = ContainmentCheck(expr.left, expr.right, expr.position) val containment = ContainmentCheck(expr.left, expr.right, expr.position)
return listOf(IAstModification.ReplaceNode(expr, containment, parent)) return listOf(IAstModification.ReplaceNode(expr, containment, parent))
} }
if(expr.operator=="not in") {
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
val notContainment = PrefixExpression("not", containment, expr.position)
return listOf(IAstModification.ReplaceNode(expr, notContainment, parent))
}
return noModifications return noModifications
} }

View File

@ -37,11 +37,6 @@ internal class BeforeAsmAstChanger(val program: Program,
} }
override fun before(block: Block, parent: Node): Iterable<IAstModification> { override fun before(block: Block, parent: Node): Iterable<IAstModification> {
// move all subroutines to the bottom of the block
val subs = block.statements.filterIsInstance<Subroutine>()
block.statements.removeAll(subs)
block.statements.addAll(subs)
// adjust global variables initialization // adjust global variables initialization
if(options.dontReinitGlobals) { if(options.dontReinitGlobals) {
block.statements.asSequence().filterIsInstance<VarDecl>().forEach { block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
@ -135,15 +130,20 @@ internal class BeforeAsmAstChanger(val program: Program,
val mods = mutableListOf<IAstModification>() val mods = mutableListOf<IAstModification>()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine. // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations. // and if an assembly block doesn't contain a rts/rti.
if (!subroutine.isAsmSubroutine) { if (!subroutine.isAsmSubroutine) {
if(subroutine.statements.isEmpty() || if(subroutine.isEmpty()) {
(!subroutine.hasRtsInAsm(options.compTarget)
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine
&& subroutine.statements.last() !is Return)) {
val returnStmt = Return(null, subroutine.position) val returnStmt = Return(null, subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine) mods += IAstModification.InsertLast(returnStmt, subroutine)
} else {
val last = subroutine.statements.last()
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
if(lastStatement !is Return) {
val returnStmt = Return(null, subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
} }
} }
@ -164,11 +164,21 @@ internal class BeforeAsmAstChanger(val program: Program,
} }
if (!subroutine.inline || !options.optimize) { if (!subroutine.inline || !options.optimize) {
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm(options.compTarget)) { if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && !subroutine.hasRtsInAsm()) {
// make sure the NOT INLINED asm subroutine actually has a rts at the end // make sure the NOT INLINED asm subroutine actually has a rts at the end
// (non-asm routines get a Return statement as needed, above) // (non-asm routines get a Return statement as needed, above)
val instruction = if(options.compTarget.name==VMTarget.NAME) " return\n" else " rts\n" mods += if(options.compTarget.name==VMTarget.NAME)
mods += IAstModification.InsertLast(InlineAssembly(instruction, Position.DUMMY), subroutine) IAstModification.InsertLast(InlineAssembly(" return\n", true, Position.DUMMY), subroutine)
else
IAstModification.InsertLast(InlineAssembly(" rts\n", false, Position.DUMMY), subroutine)
}
}
if(subroutine.isNotEmpty() && subroutine.statements.last() is Return) {
// maybe the last return can be removed because there is a fall-through prevention above it
val lastStatementBefore = subroutine.statements.reversed().drop(1).firstOrNull { it !is Subroutine }
if(lastStatementBefore is Return) {
mods += IAstModification.Remove(subroutine.statements.last(), subroutine)
} }
} }

View File

@ -129,4 +129,18 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
} }
return noModifications return noModifications
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="<<" || expr.operator==">>") {
val shifts = expr.right.constValue(program)
if(shifts!=null) {
val dt = expr.left.inferType(program)
if(dt.istype(DataType.UBYTE) && shifts.number>=8.0)
errors.warn("shift always results in 0", expr.position)
if(dt.istype(DataType.UWORD) && shifts.number>=16.0)
errors.warn("shift always results in 0", expr.position)
}
}
return noModifications
}
} }

View File

@ -2,6 +2,7 @@ package prog8.compiler.astprocessing
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter import prog8.ast.statements.SubroutineParameter
@ -14,7 +15,8 @@ internal class BoolRemover(val program: Program) : AstWalker() {
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.type == DataType.BOOL) { if(typecast.type == DataType.BOOL) {
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(DataType.UBYTE, 0.0, typecast.position), typecast.position) val valueDt = typecast.expression.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(valueDt, 0.0, typecast.position), typecast.position)
return listOf(IAstModification.ReplaceNode(typecast, notZero, parent)) return listOf(IAstModification.ReplaceNode(typecast, notZero, parent))
} }
return noModifications return noModifications

View File

@ -10,8 +10,7 @@ import prog8.code.core.IErrorReporter
import prog8.code.core.Position import prog8.code.core.Position
internal class CodeDesugarer(val program: Program, internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process. // Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer ! // Several changes have already been done by the StatementReorderer !
@ -23,6 +22,7 @@ internal class CodeDesugarer(val program: Program,
// - replace while and do-until loops by just jumps. // - replace while and do-until loops by just jumps.
// - replace peek() and poke() by direct memory accesses. // - replace peek() and poke() by direct memory accesses.
// - repeat-forever loops replaced by label+jump. // - repeat-forever loops replaced by label+jump.
// - pointer[word] replaced by @(pointer+word)
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> { override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
@ -135,4 +135,40 @@ _after:
} }
return noModifications return noModifications
} }
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
// replace pointervar[word] by @(pointervar+word) to avoid the
// "array indexing is limited to byte size 0..255" error for pointervariables.
val indexExpr = arrayIndexedExpression.indexer.indexExpr
val indexerDt = indexExpr.inferType(program)
if(indexerDt.isWords) {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)!!
if(arrayVar.datatype==DataType.UWORD) {
val add: Expression =
if(indexExpr.constValue(program)?.number==0.0)
arrayIndexedExpression.arrayvar.copy()
else
BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// assignment to array
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
// read from array
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="in") {
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
}
return noModifications
}
} }

View File

@ -8,9 +8,10 @@ import prog8.ast.Program
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.code.SymbolTable
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SourceCode import prog8.code.core.SourceCode
import prog8.compiler.BuiltinFunctions import prog8.compiler.BuiltinFunctions
import prog8.compiler.builtinFunctionReturnType import prog8.compiler.builtinFunctionReturnType
@ -22,7 +23,7 @@ import kotlin.io.path.isRegularFile
/** /**
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types. * Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
*/ */
class IntermediateAstMaker(val program: Program) { class IntermediateAstMaker(private val program: Program, private val symbolTable: SymbolTable, private val options: CompilationOptions) {
fun transform(): PtProgram { fun transform(): PtProgram {
val ptProgram = PtProgram( val ptProgram = PtProgram(
program.name, program.name,
@ -109,19 +110,9 @@ class IntermediateAstMaker(val program: Program) {
return target return target
} }
private fun targetOf(identifier: IdentifierReference): Pair<List<String>, DataType> {
val target=identifier.targetStatement(program)!! as INamedStatement
val targetname = if(target.name in program.builtinFunctions.names)
listOf("<builtin>", target.name)
else
target.scopedName
val type = identifier.inferType(program).getOr(DataType.UNDEFINED)
return Pair(targetname, type)
}
private fun transform(identifier: IdentifierReference): PtIdentifier { private fun transform(identifier: IdentifierReference): PtIdentifier {
val (target, type) = targetOf(identifier) val (target, type) = identifier.targetNameAndType(program)
return PtIdentifier(identifier.nameInSource, target, type, identifier.position) return PtIdentifier(target, type, identifier.position)
} }
private fun transform(srcBlock: Block): PtBlock { private fun transform(srcBlock: Block): PtBlock {
@ -140,16 +131,16 @@ class IntermediateAstMaker(val program: Program) {
} }
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl } val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position) val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position)
if(vardecls.isNotEmpty()) block.add(makeScopeVarsDecls(vardecls, srcBlock.position)) makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements) for (stmt in statements)
block.add(transformStatement(stmt)) block.add(transformStatement(stmt))
return block return block
} }
private fun makeScopeVarsDecls(vardecls: List<Statement>, position: Position): PtNode { private fun makeScopeVarsDecls(vardecls: Iterable<Statement>): Iterable<PtNamedNode> {
val decls = PtScopeVarsDecls(position) val decls = mutableListOf<PtNamedNode>()
vardecls.forEach { vardecls.forEach {
decls.add(transformStatement(it as VarDecl)) decls.add(transformStatement(it as VarDecl) as PtNamedNode)
} }
return decls return decls
} }
@ -197,7 +188,7 @@ class IntermediateAstMaker(val program: Program) {
"%asminclude" -> { "%asminclude" -> {
val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source) val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source)
val assembly = result.getOrElse { throw it } val assembly = result.getOrElse { throw it }
PtInlineAssembly(assembly, directive.position) PtInlineAssembly(assembly.trimEnd().trimStart('\r', '\n'), false, directive.position)
} }
else -> { else -> {
// other directives don't output any code (but could end up in option flags somewhere else) // other directives don't output any code (but could end up in option flags somewhere else)
@ -218,7 +209,7 @@ class IntermediateAstMaker(val program: Program) {
} }
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall { private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
val (target, type) = targetOf(srcCall.target) val (target, type) = srcCall.target.targetNameAndType(program)
val call = PtFunctionCall(target,true, type, srcCall.position) val call = PtFunctionCall(target,true, type, srcCall.position)
for (arg in srcCall.args) for (arg in srcCall.args)
call.add(transformExpression(arg)) call.add(transformExpression(arg))
@ -226,7 +217,7 @@ class IntermediateAstMaker(val program: Program) {
} }
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall { private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
val (target, _) = targetOf(srcCall.target) val (target, _) = srcCall.target.targetNameAndType(program)
val type = srcCall.inferType(program).getOrElse { val type = srcCall.inferType(program).getOrElse {
throw FatalAstException("unknown dt $srcCall") throw FatalAstException("unknown dt $srcCall")
} }
@ -251,8 +242,10 @@ class IntermediateAstMaker(val program: Program) {
return ifelse return ifelse
} }
private fun transform(srcNode: InlineAssembly): PtInlineAssembly = private fun transform(srcNode: InlineAssembly): PtInlineAssembly {
PtInlineAssembly(srcNode.assembly, srcNode.position) val assembly = srcNode.assembly.trimEnd().trimStart('\r', '\n')
return PtInlineAssembly(assembly, srcNode.isIR, srcNode.position)
}
private fun transform(srcJump: Jump): PtJump { private fun transform(srcJump: Jump): PtJump {
val identifier = if(srcJump.identifier!=null) transform(srcJump.identifier!!) else null val identifier = if(srcJump.identifier!=null) transform(srcJump.identifier!!) else null
@ -306,13 +299,26 @@ class IntermediateAstMaker(val program: Program) {
sub.parameters.forEach { it.first.parent=sub } sub.parameters.forEach { it.first.parent=sub }
if(srcSub.asmAddress==null) { if(srcSub.asmAddress==null) {
var combinedAsm = "" var combinedTrueAsm = ""
for (asm in srcSub.statements) var combinedIrAsm = ""
combinedAsm += (asm as InlineAssembly).assembly + "\n" for (asm in srcSub.statements) {
if(combinedAsm.isNotEmpty()) asm as InlineAssembly
sub.add(PtInlineAssembly(combinedAsm, srcSub.statements[0].position)) if(asm.isIR)
else combinedIrAsm += asm.assembly + "\n"
sub.add(PtInlineAssembly("", srcSub.position)) else
combinedTrueAsm += asm.assembly + "\n"
}
if(combinedTrueAsm.isNotEmpty()) {
combinedTrueAsm = combinedTrueAsm.trimEnd().trimStart('\r', '\n')
sub.add(PtInlineAssembly(combinedTrueAsm, false, srcSub.statements[0].position))
}
if(combinedIrAsm.isNotEmpty()) {
combinedIrAsm = combinedIrAsm.trimEnd().trimStart('\r', '\n')
sub.add(PtInlineAssembly(combinedIrAsm, true, srcSub.statements[0].position))
}
if(combinedIrAsm.isEmpty() && combinedTrueAsm.isEmpty())
sub.add(PtInlineAssembly("", true, srcSub.position))
} }
return sub return sub
@ -329,8 +335,7 @@ class IntermediateAstMaker(val program: Program) {
srcSub.inline, srcSub.inline,
srcSub.position) srcSub.position)
sub.parameters.forEach { it.parent=sub } sub.parameters.forEach { it.parent=sub }
makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
if(vardecls.isNotEmpty()) sub.add(makeScopeVarsDecls(vardecls, sub.position))
for (statement in statements) for (statement in statements)
sub.add(transformStatement(statement)) sub.add(transformStatement(statement))
@ -344,7 +349,7 @@ class IntermediateAstMaker(val program: Program) {
PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position) PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
} }
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position) VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.position) VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
} }
} }

View File

@ -40,11 +40,33 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
} }
} }
} }
if(expr.operator=="^" && expr.left.inferType(program) istype DataType.BOOL && expr.right.constValue(program)?.number == 1.0) {
// boolean ^ 1 --> not boolean
val notExpr = PrefixExpression("not", expr.left, expr.position)
return listOf(IAstModification.ReplaceNode(expr, notExpr, parent))
}
return noModifications return noModifications
} }
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator == "not") { if(expr.operator == "not") {
// first check if we're already part of a "boolean" expresion (i.e. comparing against 0)
// if so, simplify THAT whole expression rather than making it more complicated
if(parent is BinaryExpression && parent.right.constValue(program)?.number==0.0) {
if(parent.operator=="==") {
// (NOT X)==0 --> X!=0
val replacement = BinaryExpression(expr.expression, "!=", NumericLiteral.optimalInteger(0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
} else if(parent.operator=="!=") {
// (NOT X)!=0 --> X==0
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral.optimalInteger(0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(parent, replacement, parent.parent))
}
}
// not(not(x)) -> x // not(not(x)) -> x
if((expr.expression as? PrefixExpression)?.operator=="not") if((expr.expression as? PrefixExpression)?.operator=="not")
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent)) return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))

View File

@ -1,13 +1,11 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.code.core.* import prog8.code.core.*
import prog8.compiler.BuiltinFunctions
internal class StatementReorderer(val program: Program, internal class StatementReorderer(val program: Program,
val errors: IErrorReporter, val errors: IErrorReporter,
@ -129,15 +127,6 @@ internal class StatementReorderer(val program: Program,
} }
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.asSequence().filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
val subs = subroutine.statements.filterIsInstance<Subroutine>() val subs = subroutine.statements.filterIsInstance<Subroutine>()
@ -195,54 +184,6 @@ internal class StatementReorderer(val program: Program,
&& maySwapOperandOrder(expr)) && maySwapOperandOrder(expr))
return listOf(IAstModification.SwapOperands(expr)) return listOf(IAstModification.SwapOperands(expr))
// when using a simple bit shift and assigning it to a variable of a different type,
// try to make the bit shifting 'wide enough' to fall into the variable's type.
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
if(expr.operator=="<<" || expr.operator==">>") {
val leftDt = expr.left.inferType(program)
when (parent) {
is Assignment -> {
val targetDt = parent.target.inferType(program)
if(leftDt != targetDt) {
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is VarDecl -> {
if(leftDt isnot parent.datatype) {
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is IFunctionCall -> {
val argnum = parent.args.indexOf(expr)
when (val callee = parent.target.targetStatement(program)) {
is Subroutine -> {
val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) {
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(callee.name)
val paramTypes = func.parameters[argnum].possibleDatatypes
for(type in paramTypes) {
if(leftDt isAssignableTo type) {
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
if(replaced)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
}
else -> throw FatalAstException("weird callee")
}
}
else -> return noModifications
}
}
return noModifications return noModifications
} }
@ -364,9 +305,7 @@ internal class StatementReorderer(val program: Program,
AddressOf(sourceIdent, assign.position), AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position), AddressOf(identifier, assign.position),
NumericLiteral.optimalInteger(numelements*eltsize, assign.position) NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
), ), false, assign.position
true,
assign.position
) )
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent)) return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
} }
@ -378,7 +317,7 @@ internal class StatementReorderer(val program: Program,
assign.value as? IdentifierReference ?: assign.value, assign.value as? IdentifierReference ?: assign.value,
identifier identifier
), ),
true, false,
assign.position assign.position
) )
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent)) return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))

View File

@ -7,22 +7,27 @@ import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.code.* import prog8.code.*
import prog8.code.core.ArrayDatatypes import prog8.code.core.ArrayDatatypes
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.Position import prog8.code.core.Position
import java.util.* import java.util.*
internal class SymbolTableMaker: IAstVisitor { internal class SymbolTableMaker(private val program: Program, private val options: CompilationOptions): IAstVisitor {
private val st = SymbolTable() private val st = SymbolTable()
private val scopestack = Stack<StNode>() private val scopestack = Stack<StNode>()
private var dontReinitGlobals = false
fun makeFrom(program: Program): SymbolTable { fun make(): SymbolTable {
scopestack.clear() scopestack.clear()
st.children.clear() st.children.clear()
dontReinitGlobals = options.dontReinitGlobals
this.visit(program) this.visit(program)
program.builtinFunctions.names.forEach { program.builtinFunctions.names.forEach {
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY) val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
st.add(node) st.add(node)
} }
require(scopestack.isEmpty())
return st return st
} }
@ -38,7 +43,7 @@ internal class SymbolTableMaker: IAstVisitor {
override fun visit(subroutine: Subroutine) { override fun visit(subroutine: Subroutine) {
if(subroutine.asmAddress!=null) { if(subroutine.asmAddress!=null) {
val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) } val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmParameterRegisters, subroutine.position) val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmReturnvaluesRegisters, subroutine.position)
scopestack.peek().add(node) scopestack.peek().add(node)
// st.origAstLinks[subroutine] = node // st.origAstLinks[subroutine] = node
} else { } else {
@ -57,7 +62,9 @@ internal class SymbolTableMaker: IAstVisitor {
val node = val node =
when(decl.type) { when(decl.type) {
VarDeclType.VAR -> { VarDeclType.VAR -> {
val initialNumeric = (decl.value as? NumericLiteral)?.number var initialNumeric = (decl.value as? NumericLiteral)?.number
if(initialNumeric==0.0)
initialNumeric=null // variable will go into BSS and this will be set to 0
val initialStringLit = decl.value as? StringLiteral val initialStringLit = decl.value as? StringLiteral
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding) val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
val initialArrayLit = decl.value as? ArrayLiteral val initialArrayLit = decl.value as? ArrayLiteral
@ -71,7 +78,13 @@ internal class SymbolTableMaker: IAstVisitor {
initialStringLit.value.length+1 // include the terminating 0-byte initialStringLit.value.length+1 // include the terminating 0-byte
else else
null null
StStaticVariable(decl.name, decl.datatype, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position) val bss = if(decl.datatype==DataType.STR)
false
else if(decl.isArray)
initialArray.isNullOrEmpty()
else
initialNumeric == null
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
} }
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position) VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
@ -91,8 +104,14 @@ internal class SymbolTableMaker: IAstVisitor {
return null return null
return arrayLit.value.map { return arrayLit.value.map {
when(it){ when(it){
is AddressOf -> StArrayElement(null, it.identifier.nameInSource) is AddressOf -> {
is IdentifierReference -> StArrayElement(null, it.nameInSource) val scopedName = it.identifier.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is IdentifierReference -> {
val scopedName = it.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is NumericLiteral -> StArrayElement(it.number, null) is NumericLiteral -> StArrayElement(it.number, null)
else -> throw FatalAstException("weird element dt in array literal") else -> throw FatalAstException("weird element dt in array literal")
} }
@ -105,15 +124,15 @@ internal class SymbolTableMaker: IAstVisitor {
// st.origAstLinks[label] = node // st.origAstLinks[label] = node
} }
override fun visit(fcall: BuiltinFunctionCall) { override fun visit(bfc: BuiltinFunctionCall) {
if(fcall.name=="memory") { if(bfc.name=="memory") {
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable // memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
val name = (fcall.args[0] as StringLiteral).value val name = (bfc.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"} require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val size = (fcall.args[1] as NumericLiteral).number.toUInt() val size = (bfc.args[1] as NumericLiteral).number.toUInt()
val align = (fcall.args[2] as NumericLiteral).number.toUInt() val align = (bfc.args[2] as NumericLiteral).number.toUInt()
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, fcall.position)) st.add(StMemorySlab("prog8_memoryslab_$name", size, align, bfc.position))
} }
super.visit(fcall) super.visit(bfc)
} }
} }

View File

@ -47,6 +47,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val rightCv = expr.right.constValue(program) val rightCv = expr.right.constValue(program)
if(leftDt.isKnown && rightDt.isKnown) { if(leftDt.isKnown && rightDt.isKnown) {
if(expr.operator=="<<" && leftDt.isBytes) {
// uword ww = 1 << shift --> make the '1' a word constant
val leftConst = expr.left.constValue(program)
if(leftConst!=null) {
val leftConstAsWord =
if(leftDt.istype(DataType.UBYTE))
NumericLiteral(DataType.UWORD, leftConst.number, leftConst.position)
else
NumericLiteral(DataType.WORD, leftConst.number, leftConst.position)
val modifications = mutableListOf<IAstModification>()
if (parent is Assignment) {
if (parent.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
if(rightDt.isBytes)
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
}
} else if (parent is TypecastExpression && parent.type == DataType.UWORD && parent.parent is Assignment) {
val assign = parent.parent as Assignment
if (assign.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
if(rightDt.isBytes)
modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
}
}
if(modifications.isNotEmpty())
return modifications
}
}
if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) { if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) {
// see if any of the operands needs conversion to bool // see if any of the operands needs conversion to bool
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()

View File

@ -6,8 +6,7 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope import prog8.ast.statements.*
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.code.core.* import prog8.code.core.*
@ -63,10 +62,18 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
} }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val nextAssign = assignment.nextSibling() as? Assignment // remove duplicated assignments, but not if it's a memory mapped IO register
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) { val isIO = try {
if(!nextAssign.isAugmentable && nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates assignment.target.isIOAddress(options.compTarget.machine)
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer)) } catch (_: FatalAstException) {
false
}
if(!isIO) {
val nextAssign = assignment.nextSibling() as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(assignment.target, program)) {
if (!nextAssign.isAugmentable && nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
} }
return noModifications return noModifications
@ -173,6 +180,19 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return noModifications return noModifications
} }
fun checkArray(variable: VarDecl): Iterable<IAstModification> {
return if(variable.value==null) {
val arraySpec = variable.arraysize!!
val size = arraySpec.indexExpr.constValue(program)?.number?.toInt() ?: throw FatalAstException("no array size")
return if(size==0)
replaceWithFalse()
else
noModifications
}
else
checkArray((variable.value as ArrayLiteral).value)
}
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> { fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
if(stringVal.value.isEmpty()) if(stringVal.value.isEmpty())
return replaceWithFalse() return replaceWithFalse()
@ -196,8 +216,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return checkString(stringVal) return checkString(stringVal)
} }
in ArrayDatatypes -> { in ArrayDatatypes -> {
val array = (variable.value as ArrayLiteral).value return checkArray(variable)
return checkArray(array)
} }
else -> {} else -> {}
} }
@ -219,5 +238,22 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
} }
return noModifications return noModifications
} }
override fun after(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> {
if(branch.truepart.isEmpty() && branch.elsepart.isEmpty()) {
errors.warn("removing empty conditional branch", branch.position)
return listOf(IAstModification.Remove(branch, parent as IStatementContainer))
}
return noModifications
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty()) {
errors.warn("removing empty if-else statement", ifElse.position)
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
}
return noModifications
}
} }

View File

@ -5,6 +5,7 @@ import prog8.ast.Program
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.code.core.ByteDatatypes
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.IErrorReporter import prog8.code.core.IErrorReporter
import prog8.code.core.Position import prog8.code.core.Position
@ -82,7 +83,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
if(mismatch>=0) { if(mismatch>=0) {
val actual = argtypes[mismatch] val actual = argtypes[mismatch]
val expected = consideredParamTypes[mismatch] val expected = consideredParamTypes[mismatch]
return if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0)) return if(actual==DataType.BOOL && expected in ByteDatatypes)
null // a bool is just 1 or 0.
else if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number in setOf(0.0, 1.0))
null // specifying a 1 or 0 as a BOOL is okay null // specifying a 1 or 0 as a BOOL is okay
else else
Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position) Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
@ -91,6 +94,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
if(target.asmReturnvaluesRegisters.size>1) { if(target.asmReturnvaluesRegisters.size>1) {
// multiple return values will NOT work inside an expression. // multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement. // they MIGHT work in a regular assignment or just a function call statement.
// EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if (call !is FunctionCallStatement) { if (call !is FunctionCallStatement) {
val checkParent = val checkParent =
@ -99,7 +106,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
else else
parent parent
if (checkParent !is Assignment && checkParent !is VarDecl) { if (checkParent !is Assignment && checkParent !is VarDecl) {
return Pair("can't use subroutine call that returns multiple return values here", call.position) val (returnRegisters, _) = target.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.size>1) {
return Pair("can't use subroutine call that returns multiple return values here", call.position)
}
} }
} }
} }

View File

@ -52,8 +52,8 @@ class TestAstChecks: FunSpec({
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldBe null compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2 errors.errors.size shouldBe 2
errors.warnings.size shouldBe 0 errors.warnings.size shouldBe 0
errors.errors[0] shouldContain ":7:28: assignment value is invalid" errors.errors[0] shouldContain ":7:28: invalid assignment value, maybe forgot '&'"
errors.errors[1] shouldContain ":8:28: assignment value is invalid" errors.errors[1] shouldContain ":8:28: invalid assignment value, maybe forgot '&'"
} }
test("can't do str or array expression without using address-of") { test("can't do str or array expression without using address-of") {

View File

@ -44,18 +44,18 @@ class TestBuiltinFunctions: FunSpec({
conv.returns.reg shouldBe RegisterOrPair.A conv.returns.reg shouldBe RegisterOrPair.A
} }
test("not-pure func with fixed type") { test("not-pure func with varying result value type") {
val func = BuiltinFunctions.getValue("rnd") val func = BuiltinFunctions.getValue("cmp")
func.name shouldBe "rnd" func.name shouldBe "cmp"
func.parameters.size shouldBe 0 func.parameters.size shouldBe 2
func.pure shouldBe false func.pure shouldBe false
func.returnType shouldBe DataType.UBYTE func.returnType shouldBe null
val conv = func.callConvention(emptyList()) val conv = func.callConvention(listOf(DataType.UWORD, DataType.UWORD))
conv.params.size shouldBe 0 conv.params.size shouldBe 2
conv.returns.dt shouldBe DataType.UBYTE conv.returns.dt shouldBe null
conv.returns.floatFac1 shouldBe false conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe RegisterOrPair.A conv.returns.reg shouldBe null
} }
test("func without return type") { test("func without return type") {

View File

@ -112,7 +112,7 @@ class TestCallgraph: FunSpec({
""" """
val result = compileText(C64Target(), false, sourcecode)!! val result = compileText(C64Target(), false, sourcecode)!!
val graph = CallGraph(result.program) val graph = CallGraph(result.program)
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 9 graph.allIdentifiers.size shouldBeGreaterThanOrEqual 5
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") } val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
empties.size shouldBe 3 empties.size shouldBe 3
empties[0].position.line shouldBe 4 empties[0].position.line shouldBe 4

View File

@ -8,7 +8,6 @@ import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8tests.helpers.* import prog8tests.helpers.*
import prog8tests.helpers.compileText
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.absolute import kotlin.io.path.absolute
import kotlin.io.path.exists import kotlin.io.path.exists
@ -34,7 +33,6 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
quietAssembler = true, quietAssembler = true,
asmListfile = false, asmListfile = false,
experimentalCodegen = false, experimentalCodegen = false,
keepIR = false,
compilationTarget = target.name, compilationTarget = target.name,
evalStackBaseAddress = null, evalStackBaseAddress = null,
symbolDefs = emptyMap(), symbolDefs = emptyMap(),
@ -100,14 +98,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
"circles", "circles",
"cobramk3-gfx", "cobramk3-gfx",
"colorbars", "colorbars",
"cube3d",
"datetime", "datetime",
"diskspeed",
"fileseek",
"highresbitmap", "highresbitmap",
"kefrenbars", "kefrenbars",
"keyboardhandler",
"mandelbrot", "mandelbrot",
"mandelbrot-gfx-colors", "mandelbrot-gfx-colors",
"multipalette", "multipalette",
"rasterbars", "rasterbars",
"sincos", "sincos",
"snow",
"tehtriz", "tehtriz",
"testgfx2", "testgfx2",
), ),
@ -185,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val (displayName, filepath) = prepareTestFiles(it, false, target) val (displayName, filepath) = prepareTestFiles(it, false, target)
test(displayName) { test(displayName) {
val src = filepath.readText() val src = filepath.readText()
compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null compileText(target, false, src, writeAssembly = true) shouldNotBe null
compileText(target, false, src, writeAssembly = true, keepIR=true) shouldNotBe null compileText(target, false, src, writeAssembly = true) shouldNotBe null
} }
} }
}) })

View File

@ -40,7 +40,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
strLits[0].value shouldBe "main.bar" strLits[0].value shouldBe "main.bar"
strLits[1].value shouldBe "foo.bar" strLits[1].value shouldBe "foo.bar"
strLits[0].definingScope.name shouldBe "main" strLits[0].definingScope.name shouldBe "main"
strLits[1].definingScope.name shouldBe "foo" strLits[1].definingScope.name shouldBe "foobar"
} }
} }

View File

@ -50,7 +50,6 @@ class TestCompilerOptionSourcedirs: FunSpec({
quietAssembler = true, quietAssembler = true,
asmListfile = false, asmListfile = false,
experimentalCodegen = false, experimentalCodegen = false,
keepIR = false,
compilationTarget = Cx16Target.NAME, compilationTarget = Cx16Target.NAME,
evalStackBaseAddress = null, evalStackBaseAddress = null,
symbolDefs = emptyMap(), symbolDefs = emptyMap(),
@ -87,7 +86,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") { test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8") val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
val sourcedirs = listOf("${fixturesDir}") val sourcedirs = listOf("$fixturesDir")
compileFile(filepath.fileName, sourcedirs) shouldNotBe null compileFile(filepath.fileName, sourcedirs) shouldNotBe null
} }

View File

@ -0,0 +1,61 @@
package prog8tests
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
class TestGoldenRam: FunSpec({
val options = CompilationOptions(
OutputType.RAW,
CbmPrgLauncherType.NONE,
ZeropageType.FULL,
listOf((0x00u..0xffu)),
floats = true,
noSysInit = false,
compTarget = VMTarget(),
loadAddress = 999u
)
test("empty golden ram allocations") {
val errors = ErrorReporterForTests()
val golden = GoldenRam(options, UIntRange.EMPTY)
val result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
result.expectError { "should not be able to allocate anything" }
}
test("regular golden ram allocations") {
val errors = ErrorReporterForTests()
val golden = GoldenRam(options, 0x400u until 0x800u)
var result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
var alloc = result.getOrThrow()
alloc.size shouldBe 1
alloc.address shouldBe 0x400u
result = golden.allocate(listOf("test"), DataType.STR, 100, null, errors)
alloc = result.getOrThrow()
alloc.size shouldBe 100
alloc.address shouldBe 0x401u
repeat(461) {
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
alloc = result.getOrThrow()
alloc.size shouldBe 2
}
result = golden.allocate(listOf("test"), DataType.UWORD, null, null, errors)
result.expectError { "just 1 more byte available" }
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
alloc = result.getOrThrow()
alloc.size shouldBe 1
alloc.address shouldBe golden.region.last
result = golden.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
result.expectError { "nothing more available" }
}
})

View File

@ -11,10 +11,16 @@ class TestLaunchEmu: FunSpec({
test("test launch virtualmachine via target") { test("test launch virtualmachine via target") {
val target = VMTarget() val target = VMTarget()
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir") val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
tmpfile.writeText("""<PROGRAM NAME=test> tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test">
<OPTIONS> <OPTIONS>
</OPTIONS> </OPTIONS>
<ASMSYMBOLS>
</ASMSYMBOLS>
<BSS>
</BSS>
<VARIABLES> <VARIABLES>
</VARIABLES> </VARIABLES>
@ -27,7 +33,7 @@ class TestLaunchEmu: FunSpec({
<INITGLOBALS> <INITGLOBALS>
</INITGLOBALS> </INITGLOBALS>
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]> <BLOCK NAME="main" ADDRESS="null" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
</BLOCK> </BLOCK>
</PROGRAM> </PROGRAM>
""") """)

View File

@ -15,6 +15,7 @@ import prog8.ast.statements.*
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.compiler.printProgram import prog8.compiler.printProgram
import prog8tests.helpers.* import prog8tests.helpers.*
@ -736,4 +737,60 @@ class TestOptimization: FunSpec({
val stmts = result.program.entrypoint.statements val stmts = result.program.entrypoint.statements
stmts.size shouldBe 3 stmts.size shouldBe 3
} }
test("repeated assignments to IO register should remain") {
val srcX16="""
main {
sub start() {
ubyte @shared xx
xx = 42
xx = 42 ; removed
xx = 42 ; removed
cx16.VERA_DATA0 = 0
cx16.VERA_DATA0 = 0
cx16.VERA_DATA0 = 0
@($9fff) = 0
@($9fff) = 0
@($9fff) = 0
return
}
}"""
var result = compileText(Cx16Target(), true, srcX16, writeAssembly = true)!!
var statements = result.program.entrypoint.statements
statements.size shouldBe 9
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "VERA_DATA0")
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 0x9fff
val srcC64="""
main {
sub start() {
ubyte @shared xx
xx = 42
xx = 42 ;removed
xx = 42 ;removed
c64.EXTCOL = 0
c64.EXTCOL = 0
c64.EXTCOL = 0
@(53281) = 0
@(53281) = 0
@(53281) = 0
return
}
}"""
result = compileText(C64Target(), true, srcC64, writeAssembly = true)!!
statements = result.program.entrypoint.statements
statements.size shouldBe 9
(statements[1] as Assignment).target.identifier!!.nameInSource shouldBe listOf("xx")
(statements[2] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[3] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[4] as Assignment).target.identifier!!.nameInSource shouldBe listOf("c64", "EXTCOL")
(statements[5] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
(statements[6] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.program)!!.number shouldBe 53281.0
}
}) })

Some files were not shown because too many files have changed in this diff Show More