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">
<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>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<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-runtime/4.10.1/antlr4-runtime-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.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/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/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>
<JAVADOC />
<SOURCES />

2
.idea/misc.xml generated
View File

@ -19,7 +19,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</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" />
</component>
</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,
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
------------

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.
*
*/
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).
*/
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.
* 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) {
result[node.scopedName] = node
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 }
}
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
val scopedName: List<String> by lazy {
if(type== StNodeType.GLOBAL)
emptyList()
else
parent.scopedName + name
}
val scopedName: String by lazy { scopedNameList.joinToString(".") }
fun lookup(name: String) =
lookupUnqualified(name)
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()
open fun lookup(scopedName: String) =
lookup(scopedName.split('.'))
private fun lookupQualified(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
fun lookupUnscopedOrElse(name: String, default: () -> StNode) =
lookupUnscoped(name) ?: default()
for(name in scopedName) {
if(name in node.children)
node = node.children.getValue(name)
else
return null
}
return node
}
fun lookupOrElse(scopedName: String, default: () -> StNode): StNode =
lookup(scopedName.split('.')) ?: default()
private fun lookupUnqualified(name: String): StNode? {
fun lookupUnscoped(name: String): StNode? {
// first consider the builtin functions
var globalscope = this
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) {
children[child.name] = child
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,
val dt: DataType,
val bss: Boolean,
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
val onetimeInitializationStringValue: StString?,
val onetimeInitializationArrayValue: StArray?,
@ -177,10 +154,19 @@ class StStaticVariable(name: String,
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
init {
if(bss) {
require(onetimeInitializationNumericValue==null)
require(onetimeInitializationStringValue==null)
require(onetimeInitializationArrayValue.isNullOrEmpty())
} else {
require(onetimeInitializationNumericValue!=null ||
onetimeInitializationStringValue!=null ||
onetimeInitializationArrayValue!=null)
}
if(length!=null) {
require(onetimeInitializationNumericValue == null)
if(onetimeInitializationArrayValue!=null)
require(length == onetimeInitializationArrayValue.size)
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
}
if(onetimeInitializationNumericValue!=null)
require(dt in NumericDatatypes)
@ -191,18 +177,11 @@ class StStaticVariable(name: String,
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) :
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
position: Position) :
StNode(name, StNodeType.MEMVAR, position) {
override fun printProperties() {
print("$name dt=$dt address=${address.toHex()}")
}
}
class StMemorySlab(
@ -224,16 +200,10 @@ class StMemorySlab(
position: 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) :
StNode(name, StNodeType.SUBROUTINE, position) {
override fun printProperties() {
print(name)
}
}
@ -243,15 +213,12 @@ class StRomSub(name: String,
val returns: List<RegisterOrStatusflag>,
position: Position) :
StNode(name, StNodeType.ROMSUB, position) {
override fun printProperties() {
print("$name address=${address.toHex()}")
}
}
class StSubroutineParameter(val name: String, 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 StArray = List<StArrayElement>

View File

@ -1,6 +1,8 @@
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
// 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) {
val scopedName: List<String> by lazy {
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
// Note that as an exception, the 'name' is not read-only
// but a var. This is to allow for cheap node renames.
val scopedName: String by lazy {
var namedParent: PtNode = this.parent
if(namedParent is PtProgram)
listOf(name)
name
else {
while (namedParent !is PtNamedNode)
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() {}
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
// (useful to figure out in what namespace/block something is defined, etc.)
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 PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtIdentifier -> other is PtIdentifier && other.type==type && other.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 PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
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,
type: DataType,
position: Position) : PtExpression(type, position) {
@ -120,14 +120,14 @@ class PtFunctionCall(val functionName: List<String>,
val args: List<PtExpression>
get() = children.map { it as PtExpression }
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() {
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
}
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 PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
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 -> {
(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 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() {
print("&$type $name = ${address.toHex()}")
}

View File

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

View File

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

View File

@ -5,21 +5,31 @@ import com.github.michaelbull.result.Ok
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_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_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.
// 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.
@ -41,17 +51,16 @@ abstract class Zeropage(protected val options: CompilationOptions) {
return free.windowed(2).any { it[0] == it[1] - 1u }
}
fun allocate(name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter
): Result<Pair<UInt, Int>, ZeropageAllocationError> {
override fun allocate(name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
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 =
when (datatype) {
@ -72,9 +81,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
else
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
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) {
@ -82,18 +91,18 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
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) {
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)
@ -103,9 +112,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
free.removeAll(address until address+size.toUInt())
if(name.isNotEmpty()) {
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
DataType.STR -> ZpAllocation(address, datatype, size)
in ArrayDatatypes -> ZpAllocation(address, datatype, size)
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 -> VarAllocation(address, datatype, size)
in ArrayDatatypes -> VarAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt")
}
}
@ -120,3 +129,37 @@ abstract class Zeropage(protected val options: CompilationOptions) {
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
import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import java.nio.file.Path
@ -19,6 +18,7 @@ class AtariMachineDefinition: IMachineDefinition {
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
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 initializeZeropage(compilerOptions: CompilationOptions) {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = AtariZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
override val opcodeNames = normal6502instructions
}

View File

@ -1,7 +1,6 @@
package prog8.code.target.c128
import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import prog8.code.target.cbm.Mflpt5
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)
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
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 initializeZeropage(compilerOptions: CompilationOptions) {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
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)
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
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 initializeZeropage(compilerOptions: CompilationOptions) {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
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)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
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}s")] = ZpAllocation((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}H")] = ZpAllocation((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}sH")] = ZpAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+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)
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
@ -50,28 +50,16 @@ class CX16MachineDefinition: IMachineDefinition {
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
processb.environment()["PULSE_LATENCY_MSEC"] = "10"
val process: Process = processb.start()
process.waitFor()
}
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)
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.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
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}s")] = ZpAllocation((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}H")] = ZpAllocation((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}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
}
}
}

View File

@ -1,9 +1,6 @@
package prog8.code.target.virtual
import prog8.code.core.CompilationOptions
import prog8.code.core.CpuType
import prog8.code.core.IMachineDefinition
import prog8.code.core.Zeropage
import prog8.code.core.*
import java.nio.file.Path
import kotlin.io.path.isReadable
import kotlin.io.path.name
@ -20,8 +17,8 @@ class VirtualMachineDefinition: IMachineDefinition {
override var ESTACK_LO = 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")
@ -47,11 +44,9 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun isIOAddress(address: UInt): Boolean = false
override fun initializeZeropage(compilerOptions: CompilationOptions) {}
override val opcodeNames = emptySet<String>()
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
}
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 asmVariableName(identifier: IdentifierReference) = asmVariableName(identifier.nameInSource)
internal fun getTempVarName(dt: DataType): List<String> {
internal fun getTempVarName(dt: DataType): String {
return when(dt) {
DataType.UBYTE -> listOf("cx16", "r9L")
DataType.BYTE -> listOf("cx16", "r9sL")
DataType.UWORD -> listOf("cx16", "r9")
DataType.WORD -> listOf("cx16", "r9s")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8
DataType.UBYTE -> "cx16.r9L"
DataType.BYTE -> "cx16.r9sL"
DataType.UWORD -> "cx16.r9"
DataType.WORD -> "cx16.r9s"
DataType.FLOAT -> TODO("no temporary float var available")
else -> throw FatalAstException("invalid dt $dt")
}
}
@ -375,7 +375,7 @@ class AsmGen(internal val program: Program,
}
}
DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
out(
"""
lda $indexName
@ -406,7 +406,7 @@ class AsmGen(internal val program: Program,
}
}
DataType.FLOAT -> {
require(options.compTarget.memorySize(DataType.FLOAT) == 5)
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
out(
"""
lda $indexName
@ -605,7 +605,7 @@ class AsmGen(internal val program: Program,
}
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")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
@ -667,7 +667,7 @@ $repeatLabel lda $counterVar
}
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")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
@ -794,7 +794,7 @@ $repeatLabel lda $counterVar
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
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) {
// branch with only a jump (goto)
val instruction = branchInstruction(stmt.condition, false)
@ -812,11 +812,13 @@ $repeatLabel lda $counterVar
translate(stmt.elsepart)
} else {
if(stmt.elsepart.isEmpty()) {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = program.makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
if(stmt.truepart.isNotEmpty()) {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = program.makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
}
} else {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = program.makeLabel("branch_else")
@ -909,8 +911,7 @@ $repeatLabel lda $counterVar
}
private fun translate(asm: InlineAssembly) {
val assembly = asm.assembly.trimEnd().trimStart('\r', '\n')
assemblyLines.add(assembly)
assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
}
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
@ -1069,11 +1070,20 @@ $repeatLabel lda $counterVar
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
if(ptrAndIndex.second.isSimple) {
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
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 {
if(pointervar!=null && isZpVar(pointervar)) {
@ -1081,9 +1091,16 @@ $repeatLabel lda $counterVar
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
if(ptrAndIndex.second.isSimple) {
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
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
@ -1577,8 +1594,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -1612,8 +1635,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -1649,8 +1678,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
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")
}
@ -1688,8 +1724,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
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")
}
@ -1728,8 +1771,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -1765,8 +1814,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -1808,8 +1863,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
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")
}
@ -1852,8 +1914,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleLeftOperand(left, right, ::code))
return
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
if(right.isSimple) {
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")
}
@ -1893,8 +1962,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -1930,8 +2005,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -1975,8 +2056,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
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")
}
@ -2023,8 +2111,15 @@ $repeatLabel lda $counterVar
return code(asmVariableName(left))
}
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
if(right.isSimple) {
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")
}
@ -2060,8 +2155,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -2094,8 +2195,14 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -2130,8 +2237,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(right, RegisterOrPair.AY)
if(right.isSimple) {
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")
}
@ -2169,8 +2283,15 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
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")
}
@ -2205,8 +2326,17 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -2242,8 +2372,17 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
assignExpressionToRegister(left, RegisterOrPair.A)
if(left.isSimple) {
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")
}
@ -2309,8 +2448,18 @@ $repeatLabel lda $counterVar
""")
}
else -> {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
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("""
cmp P8ZP_SCRATCH_W2
bne $jumpIfFalseLabel
@ -2385,8 +2534,18 @@ $repeatLabel lda $counterVar
+""")
}
else -> {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
assignExpressionToRegister(left, RegisterOrPair.AY)
if(left.isSimple) {
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("""
cmp P8ZP_SCRATCH_W2
bne +
@ -2781,7 +2940,7 @@ $repeatLabel lda $counterVar
val parameter = target.subroutineParameter
if(parameter!=null) {
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 reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
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) {
val signed = value.inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(dt in ByteDatatypes) {
assignExpressionToRegister(value, RegisterOrPair.A, signed)
out(" pha")
} else {
} else if(dt in WordDatatypes) {
assignExpressionToRegister(value, RegisterOrPair.AY, signed)
if (isTargetCpu(CpuType.CPU65c02))
out(" pha | phy")
else
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++
}
mods= optimizeJsrRts(linesByFour)
mods= optimizeJsrRtsAndOtherCombinations(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@ -450,7 +450,7 @@ private fun getAddressArg(line: String, program: Program): UInt? {
val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) {
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) {
when(decl.type){
VarDeclType.VAR -> null
@ -486,8 +486,11 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
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
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
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[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
}

View File

@ -124,7 +124,7 @@ internal class AssemblyProgram(
breakpoints.add("break \$" + match.groupValues[1])
}
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(2, "del")
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)
"any", "all" -> funcAnyAll(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)
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
@ -255,15 +254,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
} else {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
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 -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
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
@ -289,13 +300,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
+""")
}
else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
if(arg1.isSimple) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
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
@ -307,7 +330,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
if(discardResult || fcall !is BuiltinFunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall")
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)
slabname.linkParents(fcall)
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) {
when(val addrExpr = fcall.args[0]) {
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==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
// 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!!
@ -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
// 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())
return

View File

@ -202,16 +202,10 @@ internal class ProgramAndVarsGen(
asmsubs2asm(block.statements)
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 statements = block.statements.filterNot { it in initializers }
val (subroutine, stmts) = statements.partition { it is Subroutine }
stmts.forEach { asmgen.translate(it) }
subroutine.forEach { asmgen.translate(it) }
val notInitializers = block.statements.filterNot { it in initializers }
notInitializers.forEach { asmgen.translate(it) }
if(!options.dontReinitGlobals) {
// 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) }
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)
val varsInBlock = getVars(scope)
@ -248,7 +242,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
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 }
nonZpVariables2asm(variables)
}
@ -292,7 +286,7 @@ internal class ProgramAndVarsGen(
// regular subroutine
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)
val varsInSubroutine = getVars(scope)
@ -363,7 +357,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
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 }
nonZpVariables2asm(variables)
@ -434,14 +428,14 @@ internal class ProgramAndVarsGen(
}
private class ZpStringWithInitial(
val name: List<String>,
val alloc: Zeropage.ZpAllocation,
val name: String,
val alloc: MemoryAllocator.VarAllocation,
val value: Pair<String, Encoding>
)
private class ZpArrayWithInitial(
val name: List<String>,
val alloc: Zeropage.ZpAllocation,
val name: String,
val alloc: MemoryAllocator.VarAllocation,
val value: StArray
)
@ -449,9 +443,10 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpStringWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
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)
result.add(ZpStringWithInitial(variable.key, variable.value, svar.onetimeInitializationStringValue!!))
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
}
return result
}
@ -460,15 +455,17 @@ internal class ProgramAndVarsGen(
val result = mutableListOf<ZpArrayWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
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)
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.onetimeInitializationArrayValue!!))
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
}
return result
}
private fun zeropagevars2asm(varNames: Set<List<String>>) {
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }
private fun zeropagevars2asm(varNames: Set<String>) {
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) {
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
@ -479,7 +476,7 @@ internal class ProgramAndVarsGen(
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
asmgen.out("")
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 {
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>) {
memvars.forEach {
memvars.sortedBy { it.address }.forEach {
asmgen.out(" ${it.name} = ${it.address.toHex()}")
}
consts.forEach {
consts.sortedBy { it.name }.forEach {
if(it.dt==DataType.FLOAT)
asmgen.out(" ${it.name} = ${it.value}")
else
@ -626,8 +623,8 @@ internal class ProgramAndVarsGen(
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
else if(it.addressOf!=null) {
asmgen.asmSymbolName(it.addressOf!!)
else if(it.addressOfSymbol!=null) {
asmgen.asmSymbolName(it.addressOfSymbol!!)
}
else
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
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 {
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 {
val asmName = globalFloatConsts[number]
@ -56,7 +56,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedName.split('.'),
variable.dt,
variable.length,
variable.position,
@ -75,7 +75,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedName.split('.'),
variable.dt,
variable.length,
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.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
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(zeropage.free.isEmpty()) {
break
} else {
val result = zeropage.allocate(
variable.scopedName,
variable.scopedName.split('.'),
variable.dt,
variable.length,
variable.position,
@ -124,6 +125,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
}
}
collect(st)
return vars
return vars.sortedBy { it.dt }
}
}

View File

@ -207,7 +207,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
init {
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)) {
"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)
}
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) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
@ -287,18 +280,25 @@ internal class AssignmentAsmGen(private val program: Program,
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
if(assign.target.array==null) {
// First assign the value to the target then apply the operator in place on the target.
// This saves a temporary variable
translateNormalAssignment(
AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
)
)
when (value.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 -> {
@ -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) {
when(target.datatype) {
in ByteDatatypes -> {
@ -357,48 +379,60 @@ internal class AssignmentAsmGen(private val program: Program,
if(!expr.inferType(program).isInteger)
return false
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
if(expr.left.inferType(program).isBytes && expr.right.inferType(program).isBytes &&
expr.left.isSimple && expr.right.isSimple) {
if(expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
asmgen.restoreRegisterStack(CpuRegister.A, false)
when (expr.operator) {
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(assign.target, CpuRegister.A)
fun simpleLogicalBytesExpr() {
// both left and right expression operands are simple.
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE, expr.definingSubroutine)
asmgen.restoreRegisterStack(CpuRegister.A, false)
when (expr.operator) {
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator")
}
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)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
else if(expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
when (expr.operator) {
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
"|", "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")
else -> throw AssemblyError("invalid operator")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
fun simpleLogicalWordsExpr() {
// both left and right expression operands are simple.
if (expr.right is NumericLiteral || expr.right is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
else if (expr.left is NumericLiteral || expr.left is IdentifierReference)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD, expr.definingSubroutine)
when (expr.operator) {
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
"|", "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")
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
}
@ -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
}
@ -729,7 +815,7 @@ internal class AssignmentAsmGen(private val program: Program,
if(variable.origin!=VarDeclOrigin.USERCODE) {
when(variable.datatype) {
DataType.STR -> {
require(elementDt.isBytes)
require(elementDt.isBytes) { "must be byte string ${variable.position}" }
val stringVal = variable.value as StringLiteral
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
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")
}
in ArrayDatatypes -> {
require(elementDt.isInteger)
require(elementDt.isInteger) { "must be integer array ${variable.position}" }
val arrayVal = variable.value as ArrayLiteral
val dt = elementDt.getOr(DataType.UNDEFINED)
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")
}
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)
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
asmgen.restoreRegisterLocal(CpuRegister.A)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" ldy #$numElements")
asmgen.out(" jsr prog8_lib.containment_bytearray")
return
}
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)
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")
return
}
@ -852,18 +938,17 @@ internal class AssignmentAsmGen(private val program: Program,
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(target, CpuRegister.A)
asmgen.out(" ldy #0")
assignRegisterpairWord(target, RegisterOrPair.AY)
}
when (value.addressExpression) {
is NumericLiteral -> {
val address = (value.addressExpression as NumericLiteral).number.toUInt()
assignMemoryByteIntoWord(target, address, null)
return
}
is IdentifierReference -> {
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
return
}
is BinaryExpression -> {
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
@ -877,6 +962,7 @@ internal class AssignmentAsmGen(private val program: Program,
assignViaExprEval(value.addressExpression)
}
}
return
}
}
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
if(target.kind== TargetStorageKind.VARIABLE) {
if(target.kind == TargetStorageKind.VARIABLE) {
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
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) {
RegisterOrPair.A,
RegisterOrPair.X,
@ -963,11 +1049,12 @@ internal class AssignmentAsmGen(private val program: Program,
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
return
} else {
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
return assignCastViaLsbFunc(value, target)
assignCastViaLsbFunc(value, target)
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
// word to word, just assign
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) {
// byte to word, just assign
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")
}
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)) {
when(valueDt) {
DataType.UBYTE -> {
assignExpressionToRegister(value, RegisterOrPair.Y, false)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.FREADUY")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
DataType.BYTE -> {
assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.FREADSA")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
DataType.UWORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.GIVUAYFAY")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
DataType.WORD -> {
assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.saveRegisterLocal(CpuRegister.X, origTypeCastExpression.definingSubroutine!!)
asmgen.out(" jsr floats.GIVAYFAY")
asmgen.restoreRegisterLocal(CpuRegister.X)
}
else -> throw AssemblyError("invalid dt")
}
if(target.register==RegisterOrPair.FAC2) {
asmgen.out(" jsr floats.MOVEF")
}
} else {
// 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)
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)
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
@ -1170,14 +1300,10 @@ internal class AssignmentAsmGen(private val program: Program,
DataType.UWORD, DataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | stz $targetAsmVarName+1")
" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(
" st${
regs.toString().lowercase()
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
DataType.FLOAT -> {
when(regs) {
@ -1972,18 +2098,10 @@ internal class AssignmentAsmGen(private val program: Program,
}
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
// these will be correctly typecasted from a byte to a word value
if(target.register !in Cx16VirtualRegisters &&
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
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)
}
}
// 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 here
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
require(target.datatype in ByteDatatypes) { "assign target must be byte type ${target.origAstTarget?.position ?: ""}"}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -2072,7 +2190,9 @@ internal class AssignmentAsmGen(private val program: Program,
}
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)
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) {
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!!) {
is PrefixExpression -> {
// 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) {
"+" -> {}
"-" -> inplaceNegate(target, type)
"~" -> inplaceInvert(target, type)
"-" -> inplaceNegate(assign, false)
"~" -> inplaceInvert(assign)
else -> throw AssemblyError("invalid prefix operator")
}
}
@ -1796,8 +1795,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
when (dt) {
internal fun inplaceInvert(assign: AsmAssignment) {
val target = assign.target
when (assign.target.datatype) {
DataType.UBYTE -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1840,7 +1840,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
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 -> {
@ -1864,15 +1865,24 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
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")
}
}
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
when (dt) {
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
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 -> {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1896,9 +1906,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
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")
else -> throw AssemblyError("no asm gen for in-place negate byte")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.WORD -> {
@ -1955,12 +1966,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
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")
else -> throw AssemblyError("no asm gen for in-place negate word")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
else -> throw AssemblyError("weird target")
}
}
DataType.FLOAT -> {
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 -> {
// simply flip the sign bit in the float
asmgen.out("""
@ -1970,10 +1990,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
}
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.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SignedDatatypes
import prog8.intermediate.IRCodeChunk
import prog8.intermediate.IRInstruction
import prog8.intermediate.Opcode
import prog8.intermediate.VmDataType
import prog8.intermediate.*
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)
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)
}
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunk {
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
return if(ident!=null) {
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment)
assignSelfInMemory(ident.name, assignment.value, assignment)
} else if(memory != null) {
if(memory.address is PtNumber)
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,
value: PtExpression,
origAssign: PtAssignment
): IRCodeChunk {
val vmDt = codeGen.vmType(value.type)
val code = IRCodeChunk(origAssign.position)
): IRCodeChunks {
val vmDt = codeGen.irType(value.type)
when(value) {
is PtIdentifier -> return code // do nothing, x=x null assignment.
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null, value.position)
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
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 PtMemoryByte -> {
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
code // do nothing, mem=mem null assignment.
emptyList() // do nothing, mem=mem null assignment.
else {
// 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.STOREM, vmDt, reg1 = tempReg, value = address)
code
listOf(code)
}
}
else -> return fallbackAssign(origAssign)
@ -75,25 +71,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
symbol: String,
value: PtExpression,
origAssign: PtAssignment
): IRCodeChunk {
val vmDt = codeGen.vmType(value.type)
val code = IRCodeChunk(origAssign.position)
when(value) {
is PtIdentifier -> return code // do nothing, x=x null assignment.
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, null, symbol, value.position)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
): IRCodeChunks {
val vmDt = codeGen.irType(value.type)
return when(value) {
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
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.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)
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
return translateRegularAssign(origAssign)
@ -102,12 +99,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun inplaceBinexpr(
operator: String,
operand: PtExpression,
vmDt: VmDataType,
vmDt: IRDataType,
signed: Boolean,
knownAddress: Int?,
symbol: String?,
origAssign: PtAssignment
): IRCodeChunk {
): IRCodeChunks {
if(knownAddress!=null) {
when (operator) {
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
@ -139,8 +136,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return fallbackAssign(origAssign)
}
private fun inplacePrefix(operator: String, vmDt: VmDataType, knownAddress: Int?, addressSymbol: String?, position: Position): IRCodeChunk {
val code= IRCodeChunk(position)
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
val code= IRCodeChunk(null, null)
when(operator) {
"+" -> { }
"-" -> {
@ -150,8 +147,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
}
"~" -> {
val regMask = codeGen.vmRegisters.nextFree()
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
val regMask = codeGen.registers.nextFree()
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
code += if(knownAddress!=null)
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")
}
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.
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
val vmDt = codeGen.vmType(assignment.value.type)
val code = IRCodeChunk(assignment.position)
val vmDt = codeGen.irType(assignment.value.type)
val result = mutableListOf<IRCodeChunkBase>()
var resultRegister = -1
var resultFpRegister = -1
val zero = codeGen.isZero(assignment.value)
if(!zero) {
// calculate the assignment value
if (vmDt == VmDataType.FLOAT) {
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
if (vmDt == IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.nextFreeFloat()
result += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
} else {
resultRegister = if (assignment.value is PtMachineRegister) {
(assignment.value as PtMachineRegister).register
} else {
val reg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(assignment.value, reg, -1)
val reg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(assignment.value, reg, -1)
reg
}
}
}
if(ident!=null) {
val symbol = ident.targetName.joinToString(".")
code += if(zero) {
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = ident.name)
} else {
if (vmDt == VmDataType.FLOAT)
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
if (vmDt == IRDataType.FLOAT)
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = ident.name)
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) {
val variable = array.variable.targetName.joinToString(".")
val variable = array.variable.name
val itemsize = codeGen.program.memsizer.memorySize(array.type)
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")
if(array.index.type!=DataType.UBYTE)
throw AssemblyError("non-array var indexing requires bytes index")
val idxReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(array.index, idxReg, -1)
val idxReg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(array.index, idxReg, -1)
val code = IRCodeChunk(null, null)
if(zero) {
// 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.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, labelSymbol = variable)
return code
result += code
return result
}
val fixedIndex = constIntValue(array.index)
if(zero) {
if(fixedIndex!=null) {
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 {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position)
code += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable)
val indexReg = codeGen.registers.nextFree()
result += loadIndexReg(array, itemsize, indexReg)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
}
} else {
if(vmDt== VmDataType.FLOAT) {
if(vmDt== IRDataType.FLOAT) {
if(fixedIndex!=null) {
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 {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position)
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
val indexReg = codeGen.registers.nextFree()
result += loadIndexReg(array, itemsize, indexReg)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = resultFpRegister, labelSymbol = variable) }
}
} else {
if(fixedIndex!=null) {
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 {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg, array.position)
code += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable)
val indexReg = codeGen.registers.nextFree()
result += loadIndexReg(array, itemsize, indexReg)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, labelSymbol = variable) }
}
}
}
return result
}
else if(memory!=null) {
require(vmDt== VmDataType.BYTE)
require(vmDt== IRDataType.BYTE) { "must be byte type ${memory.position}"}
if(zero) {
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 {
val addressReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1)
code += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
val addressReg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(memory.address, addressReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
}
} else {
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 {
val addressReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1)
code += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
val addressReg = codeGen.registers.nextFree()
result += expressionEval.translateExpression(memory.address, addressReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg) }
}
}
return result
}
else
throw AssemblyError("weird assigntarget")
return code
}
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int, position: Position): IRCodeChunk {
val code = IRCodeChunk(position)
if(itemsize==1) {
code += expressionEval.translateExpression(array.index, indexReg, -1)
}
else {
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): IRCodeChunks {
return if(itemsize==1) {
expressionEval.translateExpression(array.index, indexReg, -1)
} else {
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index
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.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.intermediate.*
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) {
"any" -> funcAny(call, resultRegister)
"all" -> funcAll(call, resultRegister)
@ -25,9 +24,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"rsave",
"rsavex",
"rrestore",
"rrestorex" -> IRCodeChunk(call.position) // vm doesn't have registers to save/restore
"rnd" -> funcRnd(resultRegister, call.position)
"rndw" -> funcRndw(resultRegister, call.position)
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
"msb" -> funcMsb(call, resultRegister)
@ -37,7 +34,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"peekw" -> funcPeekW(call, resultRegister)
"poke" -> funcPoke(call)
"pokew" -> funcPokeW(call)
"pokemon" -> IRCodeChunk(call.position)
"pokemon" -> emptyList()
"mkword" -> funcMkword(call, resultRegister)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
@ -49,20 +46,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val leftRegister = codeGen.vmRegisters.nextFree()
val rightRegister = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
code += IRInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
return code
private fun funcCmp(call: PtBuiltinFunctionCall): IRCodeChunks {
val leftRegister = codeGen.registers.nextFree()
val rightRegister = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], leftRegister, -1)
result += exprGen.translateExpression(call.args[1], rightRegister, -1)
result += IRCodeChunk(null, null).also {
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 array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val code = IRCodeChunk(call.position)
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when (array.dt) {
DataType.ARRAY_UB,
@ -72,17 +70,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
else -> throw IllegalArgumentException("weird type")
}
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
if (resultRegister != 0)
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
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 array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(array.dt) {
DataType.ARRAY_UB,
@ -92,100 +93,118 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
else -> throw IllegalArgumentException("weird type")
}
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
if(resultRegister!=0)
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
if(resultRegister!=0)
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1 = resultRegister, reg2 = 0)
}
return result
}
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val sourceDt = call.args.single().type
val result = mutableListOf<IRCodeChunkBase>()
if(sourceDt!=DataType.UWORD) {
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
when (sourceDt) {
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 -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.vmRegisters.nextFree()
code += IRInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister)
code += IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80)
code += IRInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
code += IRInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
code += IRInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
code += IRCodeLabel(notNegativeLabel)
val compareReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=resultRegister)
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=compareReg, value=0x80)
it += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=resultRegister)
it += IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultRegister)
}
result += IRCodeChunk(notNegativeLabel, null)
}
DataType.WORD -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.vmRegisters.nextFree()
code += IRInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister)
code += IRInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000)
code += IRInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
code += IRInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
code += IRCodeLabel(notNegativeLabel)
val compareReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=resultRegister)
it += IRInstruction(Opcode.AND, IRDataType.WORD, reg1=compareReg, value=0x8000)
it += IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=resultRegister)
}
result += IRCodeChunk(notNegativeLabel, null)
}
else -> throw AssemblyError("weird type")
}
}
return code
return result
}
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
return code
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val reg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, codeGen.irType(call.type), reg1 = resultRegister, reg2 = reg)
}
return result
}
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
return code
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val reg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultRegister, reg2=reg)
}
return result
}
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
code += assignRegisterTo(call.args.single(), reg)
return code
private fun funcPop(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(null, null)
val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=reg)
val result = mutableListOf<IRCodeChunkBase>(code)
result += assignRegisterTo(call.args.single(), reg)
return result
}
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += IRInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
code += assignRegisterTo(call.args.single(), reg)
return code
private fun funcPopw(call: PtBuiltinFunctionCall): IRCodeChunks {
val code = IRCodeChunk(null, null)
val reg = codeGen.registers.nextFree()
code += IRInstruction(Opcode.POP, IRDataType.WORD, reg1=reg)
val result = mutableListOf<IRCodeChunkBase>(code)
result += assignRegisterTo(call.args.single(), reg)
return result
}
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
return code
private fun funcPush(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val reg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=reg)
}
return result
}
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += IRInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
return code
private fun funcPushw(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val reg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), reg, -1)
result += IRCodeChunk(null, null).also {
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 array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(array.dt) {
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
else -> throw IllegalArgumentException("weird type to reverse")
}
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
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 array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
val syscall =
when(array.dt) {
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")
else -> throw IllegalArgumentException("weird type to sort")
}
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], 0, -1)
code += IRInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += IRInstruction(Opcode.SYSCALL, value=syscall.ordinal)
return code
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], SyscallRegisterBase, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = SyscallRegisterBase+1, value = array.length)
it += IRInstruction(Opcode.SYSCALL, value = syscall.number)
}
return result
}
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val msbReg = codeGen.vmRegisters.nextFree()
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], msbReg, -1)
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
code += IRInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
return code
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val msbReg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], msbReg, -1)
result += exprGen.translateExpression(call.args[1], resultRegister, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = resultRegister, reg2 = msbReg)
}
return result
}
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunk {
val code = IRCodeChunk(call.position)
private fun funcPokeW(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += IRInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += IRInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg2 = addressReg)
}
}
} else {
val valueReg = codeGen.vmRegisters.nextFree()
val valueReg = codeGen.registers.nextFree()
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = valueReg, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
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 {
val code = IRCodeChunk(call.position)
private fun funcPoke(call: PtBuiltinFunctionCall): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += IRInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += IRInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg2 = addressReg)
}
}
} else {
val valueReg = codeGen.vmRegisters.nextFree()
val valueReg = codeGen.registers.nextFree()
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueReg, value = address)
}
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += IRInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args[0], addressReg, -1)
result += exprGen.translateExpression(call.args[1], valueReg, -1)
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 {
val code = IRCodeChunk(call.position)
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(call.args[0] is PtNumber) {
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 {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += IRInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
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 {
val code = IRCodeChunk(call.position)
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
if(call.args[0] is PtNumber) {
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 {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += IRInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
val addressReg = codeGen.registers.nextFree()
result += exprGen.translateExpression(call.args.single(), addressReg, -1)
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 {
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 {
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val name = (call.args[0] as PtString).value
val code = IRCodeChunk(call.position)
code += IRInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return code
val code = IRCodeChunk(null, null)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, labelSymbol = "prog8_slabs.prog8_memoryslab_$name")
return listOf(code)
}
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
return 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.
return code
}
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
code += IRInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args.single(), resultRegister, -1)
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.
return code
return result
}
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
val vmDt = codeGen.vmType(call.args[0].type)
val code = IRCodeChunk(call.position)
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
code += IRInstruction(opcode, vmDt, reg1=resultRegister)
code += assignRegisterTo(call.args[0], resultRegister)
return code
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
val vmDt = codeGen.irType(call.args[0].type)
val result = mutableListOf<IRCodeChunkBase>()
result += exprGen.translateExpression(call.args[0], resultRegister, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(opcode, vmDt, reg1 = resultRegister)
}
result += assignRegisterTo(call.args[0], resultRegister)
return result
}
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunk {
val code = IRCodeChunk(target.position)
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtMachineRegister(register, target.type, target.position))
code += codeGen.translateNode(assignment)
return code
val result = mutableListOf<IRCodeChunkBase>()
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) {
fun optimize() {
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
sub.chunks.forEach { chunk ->
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
removeEmptyChunks(sub)
joinChunks(sub)
sub.chunks.withIndex().forEach { (index, chunk1) ->
// 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 {
val indexedInstructions = chunk.lines.withIndex()
.filter { it.value is IRInstruction }
.map { IndexedValue(it.index, it.value as IRInstruction) }
val changed = removeNops(chunk, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk, indexedInstructions) // TODO not yet implemented
|| removeUselessArithmetic(chunk, indexedInstructions)
|| removeWeirdBranches(chunk, indexedInstructions)
|| removeDoubleSecClc(chunk, indexedInstructions)
|| cleanupPushPop(chunk, indexedInstructions)
val indexedInstructions = chunk1.instructions.withIndex()
.map { IndexedValue(it.index, it.value) }
val changed = removeNops(chunk1, indexedInstructions)
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|| removeUselessArithmetic(chunk1, indexedInstructions)
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
// TODO other optimizations:
// more complex optimizations such as unused registers
} 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 {
@ -31,15 +118,15 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.PUSH) {
if(idx < chunk.lines.size-1) {
val insAfter = chunk.lines[idx+1] as? IRInstruction
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
if(ins.reg1==insAfter.reg1) {
chunk.lines.removeAt(idx)
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
chunk.instructions.removeAt(idx)
} else {
chunk.lines[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
chunk.lines.removeAt(idx+1)
chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
chunk.instructions.removeAt(idx+1)
}
changed = true
}
@ -55,18 +142,18 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
if(idx < chunk.lines.size-1) {
val insAfter = chunk.lines[idx+1] as? IRInstruction
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter?.opcode == ins.opcode) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
@ -75,19 +162,24 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed
}
private fun removeWeirdBranches(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// jump/branch to label immediately below
private fun removeWeirdBranches(chunk: IRCodeChunk, nextChunk: IRCodeChunkBase?, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
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 jumping to label immediately following this
if(idx < chunk.lines.size-1) {
val label = chunk.lines[idx+1] as? IRCodeLabel
if(label?.name == labelSymbol) {
chunk.lines.removeAt(idx)
changed = true
}
if(idx==chunk.instructions.size-1 && ins.branchTarget===nextChunk) {
chunk.instructions.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) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
if (ins.value == 1) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.ADD, Opcode.SUB -> {
if (ins.value == 1) {
chunk.lines[idx] = IRInstruction(
chunk.instructions[idx] = IRInstruction(
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
ins.type,
ins.reg1
)
changed = true
} else if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.AND -> {
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
} else if (ins.value == 255 && ins.type == VmDataType.BYTE) {
chunk.lines.removeAt(idx)
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
chunk.instructions.removeAt(idx)
changed = true
} else if (ins.value == 65535 && ins.type == VmDataType.WORD) {
chunk.lines.removeAt(idx)
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.OR -> {
if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
} else if ((ins.value == 255 && ins.type == VmDataType.BYTE) || (ins.value == 65535 && ins.type == VmDataType.WORD)) {
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
changed = true
}
}
Opcode.XOR -> {
if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
@ -156,7 +248,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.NOP) {
changed = true
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
}
}
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
import prog8.code.core.AssemblyError
import prog8.intermediate.SyscallRegisterBase
internal class RegisterPool {
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
private var firstFreeFloat: Int=0
// reserve 0,1,2 for return values of subroutine calls and syscalls
private var firstFree: Int=3
private var firstFreeFloat: Int=3
fun peekNext() = firstFree
fun peekNextFloat() = firstFreeFloat
@ -12,7 +14,7 @@ internal class RegisterPool {
fun nextFree(): Int {
val result = firstFree
firstFree++
if(firstFree>65535)
if(firstFree >= SyscallRegisterBase)
throw AssemblyError("out of virtual registers (int)")
return result
}
@ -20,7 +22,7 @@ internal class RegisterPool {
fun nextFreeFloat(): Int {
val result = firstFreeFloat
firstFreeFloat++
if(firstFreeFloat>65535)
if(firstFreeFloat >= SyscallRegisterBase)
throw AssemblyError("out of virtual registers (fp)")
return result
}

View File

@ -1,4 +1,4 @@
package prog8.vm.codegen
package prog8.codegen.vm
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
@ -7,10 +7,8 @@ import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileReader
import prog8.intermediate.IRFileWriter
import prog8.intermediate.IRProgram
import java.nio.file.Path
class VmCodeGen(private val program: PtProgram,
private val symbolTable: SymbolTable,
@ -22,16 +20,8 @@ class VmCodeGen(private val program: PtProgram,
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
val irProgram = irCodeGen.generate()
// no need to check options.keepIR, as the VM file format *is* the IR file.
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.*
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 sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
val chunk = IRCodeChunk(Position.DUMMY)
for(line in lines)
chunk += line
sub += chunk
chunks.forEach { sub += it }
block += sub
val target = VMTarget()
val options = CompilationOptions(
@ -27,44 +25,60 @@ class TestIRPeepholeOpt: FunSpec({
)
val prog = IRProgram("test", IRSymbolTable(null), options, target)
prog.addBlock(block)
prog.linkChunks()
prog.validate()
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") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.JUMP, labelSymbol = "dummy"),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, value=42),
IRInstruction(Opcode.NOP),
IRInstruction(Opcode.NOP)
))
irProg.lines().size shouldBe 3
irProg.chunks().single().instructions.size shouldBe 3
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
irProg.lines().size shouldBe 1
irProg.chunks().single().instructions.size shouldBe 1
}
test("remove jmp to label below") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.JUMP, labelSymbol = "label"), // removed
IRCodeLabel("label"),
IRInstruction(Opcode.JUMP, labelSymbol = "label2"), // removed
IRInstruction(Opcode.NOP), // removed
IRCodeLabel("label2"),
IRInstruction(Opcode.JUMP, labelSymbol = "label3"),
IRInstruction(Opcode.INC, VmDataType.BYTE, reg1=1),
IRCodeLabel("label3")
))
irProg.lines().size shouldBe 8
val c1 = IRCodeChunk("main.start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
val c2 = IRCodeChunk("label", null)
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
c2 += IRInstruction(Opcode.NOP) // removed
val c3 = IRCodeChunk("label2", null)
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
val c4 = IRCodeChunk("label3", null)
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
irProg.chunks().size shouldBe 4
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 5
(lines[0] as IRCodeLabel).name shouldBe "label"
(lines[1] as IRCodeLabel).name shouldBe "label2"
(lines[2] as IRInstruction).opcode shouldBe Opcode.JUMP
(lines[3] as IRInstruction).opcode shouldBe Opcode.INC
(lines[4] as IRCodeLabel).name shouldBe "label3"
irProg.chunks().size shouldBe 4
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label"
irProg.chunks()[2].label shouldBe "label2"
irProg.chunks()[3].label shouldBe "label3"
irProg.chunks()[0].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe 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") {
@ -76,102 +90,100 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC)
))
irProg.lines().size shouldBe 6
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 1
(lines[0] as IRInstruction).opcode shouldBe Opcode.CLC
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.CLC
}
test("push followed by pop") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
IRInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
IRInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
))
irProg.lines().size shouldBe 4
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 1
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOADR
(lines[0] as IRInstruction).reg1 shouldBe 222
(lines[0] as IRInstruction).reg2 shouldBe 99
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.LOADR
instr[0].reg1 shouldBe 222
instr[0].reg2 shouldBe 99
}
test("remove useless div/mul, add/sub") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, value = 2),
IRInstruction(Opcode.ADD, IRDataType.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)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 4
irProg.chunks().single().instructions.size shouldBe 4
}
test("replace add/sub 1 by inc/dec") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
IRInstruction(Opcode.ADD, IRDataType.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)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 2
(lines[0] as IRInstruction).opcode shouldBe Opcode.INC
(lines[1] as IRInstruction).opcode shouldBe Opcode.DEC
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.INC
instr[1].opcode shouldBe Opcode.DEC
}
test("remove useless and/or/xor") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200),
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000),
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 65535),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 200),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 60000),
IRInstruction(Opcode.OR, IRDataType.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)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 4
irProg.chunks().single().instructions.size shouldBe 4
}
test("replace and/or/xor by constant number") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
IRInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, value = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
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)
opt.optimize()
val lines = irProg.lines()
lines.size shouldBe 4
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOAD
(lines[1] as IRInstruction).opcode shouldBe Opcode.LOAD
(lines[2] as IRInstruction).opcode shouldBe Opcode.LOAD
(lines[3] as IRInstruction).opcode shouldBe Opcode.LOAD
(lines[0] as IRInstruction).value shouldBe 0
(lines[1] as IRInstruction).value shouldBe 0
(lines[2] as IRInstruction).value shouldBe 255
(lines[3] as IRInstruction).value shouldBe 65535
val instr = irProg.chunks().single().instructions
instr.size shouldBe 4
instr[0].opcode shouldBe Opcode.LOAD
instr[1].opcode shouldBe Opcode.LOAD
instr[2].opcode shouldBe Opcode.LOAD
instr[3].opcode shouldBe Opcode.LOAD
instr[0].value shouldBe 0
instr[1].value shouldBe 0
instr[2].value shouldBe 255
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
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 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))
}
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
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) {
@ -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))
}
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
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
@ -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
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
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)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
DataType.UWORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral(targetDt, 0.0, expr.position)
}
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)
}
}
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 -> {
}
}
@ -625,6 +668,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
when (idt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
if (amount >= 8) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position)
}
}
@ -636,6 +680,7 @@ class ExpressionSimplifier(private val program: Program, private val compTarget:
}
DataType.UWORD -> {
if (amount >= 16) {
errors.warn("shift always results in 0", expr.position)
return NumericLiteral.optimalInteger(0, expr.position)
}
else if(amount==8) {

View File

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

View File

@ -38,6 +38,8 @@ class Inliner(val program: Program): AstWalker() {
is Return -> {
if(stmt.value is NumericLiteral)
true
else if(stmt.value==null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
@ -172,7 +174,9 @@ class Inliner(val program: Program): AstWalker() {
private fun possibleInlineFcallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<IAstModification> {
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) {
// simply insert the asm for the argument-less routine
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> {
val sub = functionCallExpr.target.targetStatement(program) as? Subroutine
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) {
// cannot inline assembly directly in the Ast here as an Asm node is not an expression....
noModifications

View File

@ -97,11 +97,13 @@ class StatementOptimizer(private val program: Program,
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// 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))
} else {
// 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))
}
}
@ -291,9 +293,9 @@ class StatementOptimizer(private val program: Program,
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val rightCv = bexpr.right.constValue(program)?.number
if(bexpr.operator=="-" && rightCv==null) {
if(bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
if(bexpr.operator=="-" && rightCv==null && targetIDt.isInteger) {
if(bexpr.right.isSimple && bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation, for integers)
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)
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) {
var fcall = assignment.value as? FunctionCallExpression
if (fcall == null)
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) {
return if (fcall.target.nameInSource == listOf("lsb")) {
// optimize word=lsb(word) ==> word &= $00ff
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)
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)
)
// optimize word=lsb(word) ==> word &= $00ff
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)
return listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
}
}
}

View File

@ -120,12 +120,26 @@ class UnusedCodeRemover(private val program: Program,
if(singleUse is AssignTarget) {
val assignment = singleUse.parent as Assignment
if(assignment.origin==AssignmentOrigin.VARINIT) {
if(!decl.definingModule.isLibrary)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(
IAstModification.Remove(decl, parent as IStatementContainer),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
if(assignment.value.isSimple) {
// remove the vardecl
if(!decl.definingModule.isLibrary)
errors.warn("removing unused variable '${decl.name}'", decl.position)
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(':compilerAst')
implementation project(':codeGenCpu6502')
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
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-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
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'
}

View File

@ -21,6 +21,8 @@
<orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="codeGenCpu6502" />
<orderEntry type="module" module-name="codeGenExperimental" />
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="virtualmachine" />
<orderEntry type="module" module-name="intermediate" scope="TEST" />
</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 $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $af57 = RND_0() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator 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 $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)
@ -151,6 +152,17 @@ asmsub FREADUY (ubyte value @Y) {
&uword AYINT_facmo = $66 ; $66/$67 contain result of AYINT
sub rndf() -> float {
%asm {{
stx P8ZP_SCRATCH_REG
lda #1
jsr RND_0
ldx P8ZP_SCRATCH_REG
rts
}}
}
%asminclude "library:c128/floats.asm"
%asminclude "library:c64/floats_funcs.asm"

View File

@ -12,9 +12,34 @@ c64 {
&ubyte COLOR = $00f1 ; cursor color
;;&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 CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
&uword IEXMON = $032e
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
@ -536,15 +561,32 @@ sys {
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
%asm {{
stx P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
ldx P8ZP_SCRATCH_B1
rts
+ lda 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) {

View File

@ -171,6 +171,18 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
&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_funcs.asm"

View File

@ -11,9 +11,36 @@ c64 {
&ubyte COLOR = $0286 ; cursor color
&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 CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
&uword USERCMD = $032e
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
@ -500,15 +527,32 @@ sys {
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
%asm {{
stx P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
ldx P8ZP_SCRATCH_B1
rts
+ lda 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) {

View File

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

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 $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)
romsub $fe0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
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 $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 $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 $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)
@ -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)
; X16 additions
romsub $fe81 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe84 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe87 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe8a = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
romsub $fe8d = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe90 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe93 = 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 $fe99 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
romsub $fe9c = 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 $fea2 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (u8).A
romsub $fe8a = FLOATS() clobbers(A,X,Y) ; FAC = (s16)facho+1:facho
romsub $fe8d = QINT() clobbers(A,X,Y) ; facho:facho+1:facho+2:facho+3 = u32(FAC)
romsub $fe90 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
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
sub rndf() -> float {
%asm {{
phx
lda #1
jsr RND_0
plx
rts
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}

View File

@ -82,9 +82,10 @@ gfx2 {
bpp = 2
}
else -> {
; back to default text mode and colors
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
c64.CINT() ; back to text mode
; back to default text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
c64.CINT()
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
width = 0
height = 0
bpp = 0
@ -366,7 +367,7 @@ _done
position2(x,y,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
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]
repeat lheight {
%asm {{
@ -561,7 +562,11 @@ _done
and #1
}}
if_nz {
cx16.r0L = lsb(x) & 7 ; xbits
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(320/8)
%asm {{
@ -571,7 +576,7 @@ _done
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
ply ; xbits
lda bits,y
ldy color
beq +
@ -608,7 +613,11 @@ _done
and #1
}}
if_nz {
cx16.r0L = lsb(x) & 7 ; xbits
%asm {{
lda x
and #7
pha ; xbits
}}
x /= 8
x += y*(640/8)
%asm {{
@ -618,7 +627,7 @@ _done
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
ply ; xbits
lda bits,y
ldy color
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)
cx16.r2L = lsb(x) & 3 ; xbits
color &= 3
color <<= shift4c[cx16.r2L]
color <<= shift4c[cx16.r2L] ; TODO with lookup table
%asm {{
stz cx16.VERA_CTRL
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) {
ubyte bank
when active_mode {
@ -823,7 +919,7 @@ _done
y++
%asm {{
phx
ldx #1
ldx color
lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1
ldy #8

View File

@ -26,7 +26,7 @@ palette {
}
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
repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))

View File

@ -85,6 +85,18 @@ asmsub RDTIM16() -> uword @AY {
cx16 {
; 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 CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
@ -290,8 +302,9 @@ cx16 {
&ubyte d2ier = via2+14
&ubyte d2ora = via2+15
&ubyte ym2151adr = $9f40
&ubyte ym2151dat = $9f41
; YM-2151 sound chip
&ubyte YM_ADDRESS = $9f40
&ubyte YM_DATA = $9f41
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 $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 $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()
@ -393,9 +406,10 @@ asmsub kbdbuf_clear() {
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
%asm {{
pha ; save shape
sec
jsr cx16.screen_mode ; set current screen mode and res in A, X, Y
lda #1
pla ; get shape back
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)
; 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 {{
phx
sec
jsr c64.MEMTOP
plx
ldy #0
cmp #0
bne +
iny
+ plx
rts
}}
}
@ -611,12 +635,19 @@ asmsub init_system() {
%asm {{
sei
cld
lda VERA_DC_VIDEO
and #%00000111 ; retain chroma + output mode
sta P8ZP_SCRATCH_REG
lda #$80
sta VERA_CTRL
sta VERA_CTRL ; reset vera
stz $01 ; select rom bank 0 (enable kernal)
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda VERA_DC_VIDEO
and #%11111000
ora P8ZP_SCRATCH_REG
sta VERA_DC_VIDEO ; keep old output mode
lda #$90 ; black
jsr c64.CHROUT
lda #1 ; swap fg/bg
@ -887,17 +918,29 @@ sys {
asmsub wait(uword jiffies @AY) {
; --- 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 {{
- wai ; wait for irq (assume it was vsync)
cmp #0
phx
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda P8ZP_SCRATCH_W1
ora P8ZP_SCRATCH_W1+1
bne +
dey
+ dec a
bne -
cpy #0
bne -
plx
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() {
uword strptr
for strptr in logo_lines
for strptr in logo_lines {
txt.print(strptr)
txt.nl()
txt.nl()
}
}
str[] logo_lines = [

View File

@ -10,12 +10,12 @@ diskio {
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
c64.SETNAM(1, "$")
c64.SETLFS(13, drivenumber, 0)
c64.SETLFS(12, drivenumber, 0)
ubyte status = 1
void c64.OPEN() ; open 13,8,0,"$"
void c64.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(13) ; use #13 as input channel
void c64.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
@ -23,7 +23,7 @@ diskio {
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()
if status!=0 {
status = 1
@ -53,7 +53,7 @@ diskio {
io_error:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(13)
c64.CLOSE(12)
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
@ -69,12 +69,12 @@ io_error:
; -- Returns pointer to disk name string or 0 if failure.
c64.SETNAM(1, "$")
c64.SETLFS(13, drivenumber, 0)
c64.SETLFS(12, drivenumber, 0)
ubyte okay = false
void c64.OPEN() ; open 13,8,0,"$"
void c64.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(13) ; use #13 as input channel
void c64.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
@ -84,7 +84,6 @@ io_error:
if c64.READST()!=0
goto io_error
; while not key pressed / EOF encountered, read data.
cx16.r0 = &list_filename
repeat {
@(cx16.r0) = c64.CHRIN()
@ -96,7 +95,7 @@ io_error:
io_error:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(13)
c64.CLOSE(12)
if okay
return &list_filename
return 0
@ -107,33 +106,37 @@ io_error:
uword list_pattern
uword list_blocks
bool iteration_in_progress = false
ubyte @zp first_byte
bool have_first_byte
ubyte last_drivenumber = 8 ; which drive was last used for a f_open operation?
str list_filetype = "???" ; prg, seq, dir
str list_filename = "?" * 50
; ----- get a list of files (uses iteration functions internally) -----
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. Returns number of files.
const uword filenames_buf_size = 800
uword filenames_buffer = memory("filenames", filenames_buf_size, 0)
sub list_filenames(ubyte drivenumber, uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
; After the last filename one additional 0 byte is placed to indicate the end of the list.
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
uword buffer_start = filenames_buffer
ubyte files_found = 0
if lf_start_list(drivenumber, pattern_ptr) {
while lf_next_entry() {
@(name_ptrs) = lsb(filenames_buffer)
name_ptrs++
@(name_ptrs) = msb(filenames_buffer)
name_ptrs++
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
files_found++
if filenames_buffer - buffer_start > filenames_buf_size-18
break
if files_found == max_names
break
if list_filetype!="dir" {
filenames_buffer += string.copy(diskio.list_filename, filenames_buffer) + 1
files_found++
if filenames_buffer - buffer_start > filenames_buf_size-20 {
@(filenames_buffer)=0
lf_end_list()
sys.set_carry()
return files_found
}
}
}
lf_end_list()
}
@(filenames_buffer)=0
sys.clear_carry()
return files_found
}
@ -170,7 +173,7 @@ io_error:
sub lf_next_entry() -> bool {
; -- 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 not iteration_in_progress
@ -207,6 +210,12 @@ io_error:
@(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() {
; 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 {
; -- open a file for iterative reading with f_read
@ -246,17 +255,19 @@ close_end:
f_close()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(11, drivenumber, 0)
void c64.OPEN() ; open 11,8,0,"filename"
c64.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
last_drivenumber = drivenumber
void c64.OPEN() ; open 12,8,12,"filename"
if_cc {
if c64.READST()==0 {
iteration_in_progress = true
have_first_byte = false
void c64.CHKIN(11) ; use #11 as input channel
void c64.CHKIN(12) ; use #12 as input channel
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() {
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
}
}
@ -271,19 +282,11 @@ close_end:
; 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
; 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
return 0
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 {{
lda bufferpointer
@ -291,50 +294,38 @@ close_end:
lda bufferpointer+1
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 {{
jsr c64.CHRIN
sta cx16.r5
m_in_buffer sta $ffff
inc m_in_buffer+1
bne +
inc m_in_buffer+2
+ inc list_blocks
bne +
inc list_blocks+1
+
}}
if cx16.r5==$0d { ; chance on I/o error status?
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
}
list_blocks++
num_bytes--
}
return list_blocks ; number of bytes read
}
sub f_read_all(uword bufferpointer) -> uword {
; -- 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
return 0
uword total_read = 0
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
total_read = 1
}
while not c64.READST() {
uword size = f_read(bufferpointer, 256)
total_read += size
bufferpointer += size
cx16.r0 = f_read(bufferpointer, 256)
total_read += cx16.r0
bufferpointer += cx16.r0
}
return total_read
}
@ -348,16 +339,9 @@ m_in_buffer sta $ffff
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #11
jsr c64.CHKIN ; use channel 11 again for input
ldx #12
jsr c64.CHKIN ; use channel 12 again for input
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
sta (P8ZP_SCRATCH_W1),y
beq _end
@ -378,23 +362,23 @@ _end rts
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
c64.CLRCHN()
c64.CLOSE(11)
c64.CLOSE(12)
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 {
; -- open a file for iterative writing with f_write
f_close_w()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(14, drivenumber, 1)
void c64.OPEN() ; open 14,8,1,"filename"
c64.SETLFS(13, drivenumber, 1)
void c64.OPEN() ; open 13,8,1,"filename"
if_cc {
void c64.CHKOUT(14) ; use #14 as input channel
c64.CHKOUT(13) ; use #13 as output channel
return not c64.READST()
}
f_close_w()
@ -402,9 +386,9 @@ _end rts
}
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 {
void c64.CHKOUT(14) ; use #14 as input channel again
c64.CHKOUT(13) ; use #13 as output channel again
repeat num_bytes {
c64.CHROUT(@(bufferpointer))
bufferpointer++
@ -417,7 +401,7 @@ _end rts
sub f_close_w() {
; -- end an iterative file writing session (close channels).
c64.CLRCHN()
c64.CLOSE(14)
c64.CLOSE(13)
}
@ -436,10 +420,10 @@ _end rts
goto io_error
while not c64.READST() {
first_byte = c64.CHRIN()
if first_byte=='\r' or first_byte=='\n'
cx16.r5L = c64.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
@(messageptr) = first_byte
@(messageptr) = cx16.r5L
messageptr++
}
@(messageptr) = 0
@ -458,7 +442,7 @@ io_error:
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword @shared end_address = address + size
first_byte = 0 ; result var reuse
cx16.r0L = 0
%asm {{
lda address
@ -476,12 +460,12 @@ io_error:
}}
if_cc
first_byte = c64.READST()==0
cx16.r0L = c64.READST()==0
c64.CLRCHN()
c64.CLOSE(1)
return first_byte
return cx16.r0L
}
; 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
; (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.
; 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 {
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.
@ -506,10 +490,10 @@ io_error:
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account.
; 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 {
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
if not f_open(drivenumber, filenameptr)
return 0
@ -524,7 +508,8 @@ io_error:
; 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.
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)
ubyte secondary = 1
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 {{
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx P8ZP_SCRATCH_REG
stx floats_store_reg
lda #<seed
ldy #>seed
jsr MOVFM ; load float into fac1
lda #-1
jsr floats.RND
ldx floats_store_reg
rts
}}
}

View File

@ -229,60 +229,32 @@ _divisor .word 0
.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
; -- 16 bit pseudo random number generator into AY
; rand64k ;Factors of 65535: 3 5 17 257
lda sr1+1
asl a
asl a
eor sr1+1
asl a
eor sr1+1
asl a
asl a
eor sr1+1
asl a
rol sr1 ;shift this left, "random" bit comes from low
rol sr1+1
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
lda sr2+1
asl a
eor sr2+1
asl a
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
; default seed = $00c2 $1137
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
inc x1
clc
x1=*+1
lda #$00 ;x1
c1=*+1
eor #$c2 ;c1
a1=*+1
eor #$11 ;a1
sta a1
b1=*+1
adc #$37 ;b1
sta b1
lsr a
eor a1
adc c1
sta c1
ldy b1
rts
sr1 .word $a55a
sr2 .word $7653
.pend
randbyte = randword ; -- 8 bit pseudo random number generator into A (by just reusing randword)
; ----------- optimized multiplications (stack) : ---------
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
.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
; 8bit unsigned sort

View File

@ -1082,6 +1082,7 @@ containment_wordarray .proc
iny
cmp (P8ZP_SCRATCH_W2),y
beq _found
dey
+ dey
dey
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
; 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)
%asm {{
loadm.w r0,conv.str2uword.string
%ir {{
loadm.w r65500,conv.str2uword.string
syscall 11
return
}}
@ -206,8 +206,8 @@ sub str2word(str string) -> word {
; -- 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
; (any non-digit character will terminate the number string that is parsed)
%asm {{
loadm.w r0,conv.str2word.string
%ir {{
loadm.w r65500,conv.str2word.string
syscall 12
return
}}

View File

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

View File

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

View File

@ -6,8 +6,8 @@ txt {
sub clear_screen() {
str @shared sequence = "\x1b[2J\x1B[H"
%asm {{
load.w r0,txt.clear_screen.sequence
%ir {{
load.w r65500,txt.clear_screen.sequence
syscall 3
}}
}
@ -29,15 +29,15 @@ sub uppercase() {
}
sub chrout(ubyte char) {
%asm {{
loadm.b r0,txt.chrout.char
%ir {{
loadm.b r65500,txt.chrout.char
syscall 2
}}
}
sub print (str text) {
%asm {{
loadm.w r0,txt.print.text
%ir {{
loadm.w r65500,txt.print.text
syscall 3
}}
}
@ -113,8 +113,8 @@ sub print_w (word value) {
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)
; It assumes the keyboard is selected as I/O channel!
%asm {{
loadm.w r0,txt.input_chars.buffer
%ir {{
loadm.w r65500,txt.input_chars.buffer
syscall 6
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 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 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 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 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")
@ -127,7 +126,6 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
keepIR == true,
compilationTarget,
evalStackAddr,
processedSymbols,
@ -192,7 +190,6 @@ private fun compileMain(args: Array<String>): Boolean {
quietAssembler == true,
asmListfile == true,
experimentalCodegen == true,
keepIR == true,
compilationTarget,
evalStackAddr,
processedSymbols,

View File

@ -14,10 +14,10 @@ import prog8.ast.walk.IAstVisitor
import prog8.code.SymbolTable
import prog8.code.core.*
import prog8.code.target.*
import prog8.codegen.vm.VmCodeGen
import prog8.compiler.astprocessing.*
import prog8.optimizer.*
import prog8.parser.ParseError
import prog8.vm.codegen.VmCodeGen
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension
@ -38,7 +38,6 @@ class CompilerArguments(val filepath: Path,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val experimentalCodegen: Boolean,
val keepIR: Boolean,
val compilationTarget: String,
val evalStackBaseAddress: UInt?,
val symbolDefs: Map<String, String>,
@ -81,7 +80,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen
keepIR = args.keepIR
evalStackBaseAddress = args.evalStackBaseAddress
outputDir = args.outputDir.normalize()
symbolDefs = args.symbolDefs
@ -95,7 +93,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
processAst(program, args.errors, compilationOptions)
if (compilationOptions.optimize) {
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program)
optimizeAst(
@ -108,7 +106,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
postprocessAst(program, args.errors, compilationOptions)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
// printProgram(program)
determineProgramLoadAddress(program, compilationOptions, args.errors)
@ -361,7 +359,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
remover.applyModifications()
while (true) {
// 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 optsDone3 = program.optimizeStatements(errors, functions, compTarget)
val optsDone4 = program.inlineSubroutines()
@ -382,7 +380,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
callGraph.checkRecursiveCalls(errors)
program.verifyFunctionArgTypes(errors)
errors.report()
program.moveMainAndStartToFirst()
program.moveMainBlockAsFirst()
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
}
@ -391,10 +389,10 @@ private fun createAssemblyAndAssemble(program: Program,
errors: IErrorReporter,
compilerOptions: CompilationOptions
): Boolean {
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
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,
// 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.
// removeAllVardeclsFromAst(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
@ -448,14 +446,14 @@ internal fun asmGeneratorFor(program: Program,
options: CompilationOptions): IAssemblyGenerator
{
if(options.experimentalCodegen) {
val intermediateAst = IntermediateAstMaker(program).transform()
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
if (options.compTarget.name == VMTarget.NAME) {
val intermediateAst = IntermediateAstMaker(program).transform()
val intermediateAst = IntermediateAstMaker(program, symbolTable, options).transform()
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) {
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
if(expectedReturnValues.size>1) {
@ -252,16 +260,8 @@ internal class AstChecker(private val program: Program,
}
override fun visit(inlineAssembly: InlineAssembly) {
val assembly = inlineAssembly.assembly
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
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++
}
if(inlineAssembly.hasReturnOrRts())
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")
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)
err("number of asm parameter registers is not the isSameAs as number of parameters")
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)) {
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")
}
else if(param.second.statusflag!=null) {
if (param.first.type != DataType.UBYTE)
err("parameter '${param.first.name}' should be ubyte")
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BOOL)
err("parameter '${param.first.name}' should be bool or ubyte")
}
}
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)) {
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")
}
else if(pair.second.statusflag!=null) {
if (pair.first != DataType.UBYTE)
err("return type #${index + 1} should be ubyte")
if (pair.first != DataType.UBYTE && pair.first != DataType.BOOL)
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)
if (sourceDatatype.isUnknown) {
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 {
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
@ -843,6 +845,8 @@ internal class AstChecker(private val program: Program,
else if(expr.operator == "~") {
if(dt !in IntegerDatatypes)
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)
}
@ -852,8 +856,15 @@ internal class AstChecker(private val program: Program,
val leftIDt = expr.left.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
}
val leftDt = leftIDt.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
// are only to a single unique target at the same time.
// 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
// dealing with the status bit as just being that, the status bit after the call.
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
@ -1037,6 +1048,23 @@ internal class AstChecker(private val program: Program,
if(targetStatement!=null) {
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
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
@ -1244,8 +1272,14 @@ internal class AstChecker(private val program: Program,
val elementDt = containment.element.inferType(program)
val iterableDt = containment.iterable.inferType(program)
if(containment.parent is BinaryExpression)
errors.err("containment check is currently not supported inside complex expressions", containment.position)
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
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) {
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))

View File

@ -27,6 +27,21 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
val boolRemover = BoolRemover(this)
boolRemover.visit(this)
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)
fixer.visit(this)
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 "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.
// the "main" block containing the entrypoint is moved to the top in there.
// sortModules()
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint
val mod = start.definingModule
val block = start.definingBlock
moveModuleToFront(mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
val module = this.entrypoint.definingModule
val block = this.entrypoint.definingBlock
moveModuleToFront(module)
module.remove(block)
val afterDirective = module.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
module.statements.add(block)
else
mod.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)
}
module.statements.add(afterDirective, block)
}
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
@ -169,16 +168,9 @@ internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolea
return false
}
internal fun Subroutine.hasRtsInAsm(compTarget: ICompilationTarget): Boolean {
val instructions =
if(compTarget.name == VMTarget.NAME)
listOf(" return", "\treturn", " jump", "\tjump")
else
listOf(" rti", "\trti", " rts", "\trts", " jmp", "\tjmp", " bra", "\tbra")
internal fun Subroutine.hasRtsInAsm(): Boolean {
return statements
.asSequence()
.filterIsInstance<InlineAssembly>()
.any {
instructions.any { instr->instr in it.assembly }
}
.any { it.hasReturnOrRts() }
}

View File

@ -10,6 +10,7 @@ import prog8.ast.walk.IAstVisitor
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
@ -28,9 +29,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
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]
if(existing!=null) {
if(block.isInLibrary)
@ -50,9 +48,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
if(decl.name in BuiltinFunctions)
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))
if(existingInSameScope!=null && existingInSameScope!==decl)
nameError(decl.name, decl.position, existingInSameScope)
@ -74,9 +69,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
override fun visit(subroutine: Subroutine) {
if(subroutine.name in compTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", subroutine.position)
} else {
@ -86,7 +79,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
val existing = subroutine.lookup(listOf(subroutine.name))
if (existing != null && existing !== subroutine) {
if(existing.parent!==existing.parent)
if(existing.parent!==subroutine.parent && existing is Subroutine)
nameShadowWarning(subroutine.name, existing.position, subroutine)
else
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)
}
if(subroutine.name == subroutine.definingBlock.name) {
// subroutines cannot have the same name as their enclosing block,
// because this causes symbol scoping issues in the resulting assembly source
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
if(compTarget.name != VMTarget.NAME) {
if (subroutine.name == subroutine.definingBlock.name) {
// subroutines cannot have the same name as their enclosing block,
// 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) {
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) {
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", label.position)
} else {
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)
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)) {
is Subroutine -> {
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> {
if(parent !is VarDecl) {
// TODO move this / remove this, and make the codegen better instead.
// If the expression is pointervar[idx] where pointervar is uword and not a real array,
// replace it by a @(pointervar+idx) expression.
if(options.compTarget.name == VMTarget.NAME)
return noModifications // vm codegen deals correctly with all cases
// Don't replace the initializer value in a vardecl - this will be moved to a separate
// assignment statement soon in after(VarDecl)
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> {
if(options.compTarget.name==VMTarget.NAME)
return noModifications // vm codegen deals correctly with all cases
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
// 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)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
if(parent is AssignTarget) {
val assignment = parent.parent as? Assignment
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
}
// 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))
replacements.add(IAstModification.Remove(decl, scope))
} else {
val declToInsert: VarDecl
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
declToInsert = decl.copy()
} else {
replacements.add(IAstModification.Remove(decl, scope))
declToInsert = decl
}
movements.add(IAstModification.InsertFirst(decl, parentscope))
movements.add(IAstModification.InsertFirst(declToInsert, parentscope))
}
}
return movements + replacements
@ -138,12 +141,15 @@ class AstPreprocessor(val program: Program,
}
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") {
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
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
}

View File

@ -37,11 +37,6 @@ internal class BeforeAsmAstChanger(val program: Program,
}
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
if(options.dontReinitGlobals) {
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
@ -135,15 +130,20 @@ internal class BeforeAsmAstChanger(val program: Program,
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.
// 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.statements.isEmpty() ||
(!subroutine.hasRtsInAsm(options.compTarget)
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine
&& subroutine.statements.last() !is Return)) {
if(subroutine.isEmpty()) {
val returnStmt = Return(null, subroutine.position)
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.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
// (non-asm routines get a Return statement as needed, above)
val instruction = if(options.compTarget.name==VMTarget.NAME) " return\n" else " rts\n"
mods += IAstModification.InsertLast(InlineAssembly(instruction, Position.DUMMY), subroutine)
mods += if(options.compTarget.name==VMTarget.NAME)
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
}
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.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
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> {
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 noModifications

View File

@ -10,8 +10,7 @@ import prog8.code.core.IErrorReporter
import prog8.code.core.Position
internal class CodeDesugarer(val program: Program,
private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// 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 peek() and poke() by direct memory accesses.
// - repeat-forever loops replaced by label+jump.
// - pointer[word] replaced by @(pointer+word)
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
@ -135,4 +135,40 @@ _after:
}
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.expressions.*
import prog8.ast.statements.*
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.compiler.BuiltinFunctions
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.
*/
class IntermediateAstMaker(val program: Program) {
class IntermediateAstMaker(private val program: Program, private val symbolTable: SymbolTable, private val options: CompilationOptions) {
fun transform(): PtProgram {
val ptProgram = PtProgram(
program.name,
@ -109,19 +110,9 @@ class IntermediateAstMaker(val program: Program) {
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 {
val (target, type) = targetOf(identifier)
return PtIdentifier(identifier.nameInSource, target, type, identifier.position)
val (target, type) = identifier.targetNameAndType(program)
return PtIdentifier(target, type, identifier.position)
}
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 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)
block.add(transformStatement(stmt))
return block
}
private fun makeScopeVarsDecls(vardecls: List<Statement>, position: Position): PtNode {
val decls = PtScopeVarsDecls(position)
private fun makeScopeVarsDecls(vardecls: Iterable<Statement>): Iterable<PtNamedNode> {
val decls = mutableListOf<PtNamedNode>()
vardecls.forEach {
decls.add(transformStatement(it as VarDecl))
decls.add(transformStatement(it as VarDecl) as PtNamedNode)
}
return decls
}
@ -197,7 +188,7 @@ class IntermediateAstMaker(val program: Program) {
"%asminclude" -> {
val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source)
val assembly = result.getOrElse { throw it }
PtInlineAssembly(assembly, directive.position)
PtInlineAssembly(assembly.trimEnd().trimStart('\r', '\n'), false, directive.position)
}
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 {
val (target, type) = targetOf(srcCall.target)
val (target, type) = srcCall.target.targetNameAndType(program)
val call = PtFunctionCall(target,true, type, srcCall.position)
for (arg in srcCall.args)
call.add(transformExpression(arg))
@ -226,7 +217,7 @@ class IntermediateAstMaker(val program: Program) {
}
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
val (target, _) = targetOf(srcCall.target)
val (target, _) = srcCall.target.targetNameAndType(program)
val type = srcCall.inferType(program).getOrElse {
throw FatalAstException("unknown dt $srcCall")
}
@ -251,8 +242,10 @@ class IntermediateAstMaker(val program: Program) {
return ifelse
}
private fun transform(srcNode: InlineAssembly): PtInlineAssembly =
PtInlineAssembly(srcNode.assembly, srcNode.position)
private fun transform(srcNode: InlineAssembly): PtInlineAssembly {
val assembly = srcNode.assembly.trimEnd().trimStart('\r', '\n')
return PtInlineAssembly(assembly, srcNode.isIR, srcNode.position)
}
private fun transform(srcJump: Jump): PtJump {
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 }
if(srcSub.asmAddress==null) {
var combinedAsm = ""
for (asm in srcSub.statements)
combinedAsm += (asm as InlineAssembly).assembly + "\n"
if(combinedAsm.isNotEmpty())
sub.add(PtInlineAssembly(combinedAsm, srcSub.statements[0].position))
else
sub.add(PtInlineAssembly("", srcSub.position))
var combinedTrueAsm = ""
var combinedIrAsm = ""
for (asm in srcSub.statements) {
asm as InlineAssembly
if(asm.isIR)
combinedIrAsm += asm.assembly + "\n"
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
@ -329,8 +335,7 @@ class IntermediateAstMaker(val program: Program) {
srcSub.inline,
srcSub.position)
sub.parameters.forEach { it.parent=sub }
if(vardecls.isNotEmpty()) sub.add(makeScopeVarsDecls(vardecls, sub.position))
makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
for (statement in statements)
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)
}
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
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
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
if((expr.expression as? PrefixExpression)?.operator=="not")
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))

View File

@ -1,13 +1,11 @@
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.compiler.BuiltinFunctions
internal class StatementReorderer(val program: Program,
val errors: IErrorReporter,
@ -129,15 +127,6 @@ internal class StatementReorderer(val program: Program,
}
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 subs = subroutine.statements.filterIsInstance<Subroutine>()
@ -195,54 +184,6 @@ internal class StatementReorderer(val program: Program,
&& maySwapOperandOrder(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
}
@ -364,9 +305,7 @@ internal class StatementReorderer(val program: Program,
AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position),
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
),
true,
assign.position
), false, assign.position
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
}
@ -378,7 +317,7 @@ internal class StatementReorderer(val program: Program,
assign.value as? IdentifierReference ?: assign.value,
identifier
),
true,
false,
assign.position
)
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.code.*
import prog8.code.core.ArrayDatatypes
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.Position
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 scopestack = Stack<StNode>()
private var dontReinitGlobals = false
fun makeFrom(program: Program): SymbolTable {
fun make(): SymbolTable {
scopestack.clear()
st.children.clear()
dontReinitGlobals = options.dontReinitGlobals
this.visit(program)
program.builtinFunctions.names.forEach {
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
st.add(node)
}
require(scopestack.isEmpty())
return st
}
@ -38,7 +43,7 @@ internal class SymbolTableMaker: IAstVisitor {
override fun visit(subroutine: Subroutine) {
if(subroutine.asmAddress!=null) {
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)
// st.origAstLinks[subroutine] = node
} else {
@ -57,7 +62,9 @@ internal class SymbolTableMaker: IAstVisitor {
val node =
when(decl.type) {
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 initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
val initialArrayLit = decl.value as? ArrayLiteral
@ -71,7 +78,13 @@ internal class SymbolTableMaker: IAstVisitor {
initialStringLit.value.length+1 // include the terminating 0-byte
else
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.MEMORY -> {
@ -91,8 +104,14 @@ internal class SymbolTableMaker: IAstVisitor {
return null
return arrayLit.value.map {
when(it){
is AddressOf -> StArrayElement(null, it.identifier.nameInSource)
is IdentifierReference -> StArrayElement(null, it.nameInSource)
is AddressOf -> {
val scopedName = it.identifier.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is IdentifierReference -> {
val scopedName = it.targetNameAndType(program).first
StArrayElement(null, scopedName)
}
is NumericLiteral -> StArrayElement(it.number, null)
else -> throw FatalAstException("weird element dt in array literal")
}
@ -105,15 +124,15 @@ internal class SymbolTableMaker: IAstVisitor {
// st.origAstLinks[label] = node
}
override fun visit(fcall: BuiltinFunctionCall) {
if(fcall.name=="memory") {
override fun visit(bfc: BuiltinFunctionCall) {
if(bfc.name=="memory") {
// 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"}
val size = (fcall.args[1] as NumericLiteral).number.toUInt()
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, fcall.position))
val size = (bfc.args[1] as NumericLiteral).number.toUInt()
val align = (bfc.args[2] as NumericLiteral).number.toUInt()
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)
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) {
// see if any of the operands needs conversion to bool
val modifications = mutableListOf<IAstModification>()

View File

@ -6,8 +6,7 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
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> {
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))
// remove duplicated assignments, but not if it's a memory mapped IO register
val isIO = try {
assignment.target.isIOAddress(options.compTarget.machine)
} 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
@ -173,6 +180,19 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
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> {
if(stringVal.value.isEmpty())
return replaceWithFalse()
@ -196,8 +216,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return checkString(stringVal)
}
in ArrayDatatypes -> {
val array = (variable.value as ArrayLiteral).value
return checkArray(array)
return checkArray(variable)
}
else -> {}
}
@ -219,5 +238,22 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
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.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.ByteDatatypes
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
@ -82,7 +83,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
if(mismatch>=0) {
val actual = argtypes[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
else
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) {
// multiple return values will NOT work inside an expression.
// 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
if (call !is FunctionCallStatement) {
val checkParent =
@ -99,7 +106,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
else
parent
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
errors.errors.size shouldBe 2
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain ":7:28: assignment value is invalid"
errors.errors[1] shouldContain ":8:28: assignment value is invalid"
errors.errors[0] shouldContain ":7:28: invalid assignment value, maybe forgot '&'"
errors.errors[1] shouldContain ":8:28: invalid assignment value, maybe forgot '&'"
}
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
}
test("not-pure func with fixed type") {
val func = BuiltinFunctions.getValue("rnd")
func.name shouldBe "rnd"
func.parameters.size shouldBe 0
test("not-pure func with varying result value type") {
val func = BuiltinFunctions.getValue("cmp")
func.name shouldBe "cmp"
func.parameters.size shouldBe 2
func.pure shouldBe false
func.returnType shouldBe DataType.UBYTE
func.returnType shouldBe null
val conv = func.callConvention(emptyList())
conv.params.size shouldBe 0
conv.returns.dt shouldBe DataType.UBYTE
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UWORD))
conv.params.size shouldBe 2
conv.returns.dt shouldBe null
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe RegisterOrPair.A
conv.returns.reg shouldBe null
}
test("func without return type") {

View File

@ -112,7 +112,7 @@ class TestCallgraph: FunSpec({
"""
val result = compileText(C64Target(), false, sourcecode)!!
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") }
empties.size shouldBe 3
empties[0].position.line shouldBe 4

View File

@ -8,7 +8,6 @@ import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8tests.helpers.*
import prog8tests.helpers.compileText
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.exists
@ -34,7 +33,6 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
quietAssembler = true,
asmListfile = false,
experimentalCodegen = false,
keepIR = false,
compilationTarget = target.name,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),
@ -100,14 +98,19 @@ class TestCompilerOnExamplesCx16: FunSpec({
"circles",
"cobramk3-gfx",
"colorbars",
"cube3d",
"datetime",
"diskspeed",
"fileseek",
"highresbitmap",
"kefrenbars",
"keyboardhandler",
"mandelbrot",
"mandelbrot-gfx-colors",
"multipalette",
"rasterbars",
"sincos",
"snow",
"tehtriz",
"testgfx2",
),
@ -185,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val (displayName, filepath) = prepareTestFiles(it, false, target)
test(displayName) {
val src = filepath.readText()
compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null
compileText(target, false, src, writeAssembly = true, keepIR=true) shouldNotBe null
compileText(target, false, src, writeAssembly = 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[1].value shouldBe "foo.bar"
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,
asmListfile = false,
experimentalCodegen = false,
keepIR = false,
compilationTarget = Cx16Target.NAME,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),
@ -87,7 +86,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
val sourcedirs = listOf("${fixturesDir}")
val sourcedirs = listOf("$fixturesDir")
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") {
val target = VMTarget()
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>
<ASMSYMBOLS>
</ASMSYMBOLS>
<BSS>
</BSS>
<VARIABLES>
</VARIABLES>
@ -27,7 +33,7 @@ class TestLaunchEmu: FunSpec({
<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>
</PROGRAM>
""")

View File

@ -15,6 +15,7 @@ import prog8.ast.statements.*
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.compiler.printProgram
import prog8tests.helpers.*
@ -736,4 +737,60 @@ class TestOptimization: FunSpec({
val stmts = result.program.entrypoint.statements
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